diff --git a/app/src/main/kotlin/com/fox2code/mmm/MainActivity.kt b/app/src/main/kotlin/com/fox2code/mmm/MainActivity.kt index dc577ac..78b8cc2 100644 --- a/app/src/main/kotlin/com/fox2code/mmm/MainActivity.kt +++ b/app/src/main/kotlin/com/fox2code/mmm/MainActivity.kt @@ -12,20 +12,23 @@ import android.content.DialogInterface import android.content.Intent import android.content.res.Configuration import android.graphics.Color +import android.graphics.Rect import android.os.Build import android.os.Bundle import android.os.Handler import android.os.Looper -import android.util.TypedValue +import android.text.Editable +import android.text.TextWatcher import android.view.MenuItem +import android.view.MotionEvent import android.view.View import android.view.ViewGroup.MarginLayoutParams import android.view.WindowManager import android.view.animation.DecelerateInterpolator import android.view.inputmethod.EditorInfo +import android.view.inputmethod.InputMethodManager +import android.widget.EditText import android.widget.Toast -import androidx.appcompat.widget.SearchView -import androidx.cardview.widget.CardView import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import androidx.room.Room @@ -60,12 +63,13 @@ import com.google.android.material.bottomnavigation.BottomNavigationView import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.floatingactionbutton.FloatingActionButton import com.google.android.material.progressindicator.LinearProgressIndicator +import com.google.android.material.textfield.TextInputEditText import org.matomo.sdk.extra.TrackHelper import timber.log.Timber import java.sql.Timestamp -class MainActivity : FoxActivity(), OnRefreshListener, SearchView.OnQueryTextListener, - SearchView.OnCloseListener, OverScrollHelper { + +class MainActivity : FoxActivity(), OnRefreshListener, OverScrollHelper { val moduleViewListBuilder: ModuleViewListBuilder = ModuleViewListBuilder(this) val moduleViewListBuilderOnline: ModuleViewListBuilder = ModuleViewListBuilder(this) var progressIndicator: LinearProgressIndicator? = null @@ -81,8 +85,7 @@ class MainActivity : FoxActivity(), OnRefreshListener, SearchView.OnQueryTextLis private set private var moduleList: RecyclerView? = null private var moduleListOnline: RecyclerView? = null - private var searchCard: CardView? = null - private var searchView: SearchView? = null + private var searchTextInputEditText: TextInputEditText? = null private var rebootFab: FloatingActionButton? = null private var initMode = false private var runtimeUtils: RuntimeUtils? = null @@ -180,15 +183,84 @@ class MainActivity : FoxActivity(), OnRefreshListener, SearchView.OnQueryTextLis swipeRefreshBlocker = Long.MAX_VALUE moduleList = findViewById(R.id.module_list) moduleListOnline = findViewById(R.id.module_list_online) - searchCard = findViewById(R.id.search_card) - searchView = findViewById(R.id.search_bar) - val searchView = searchView!! - searchView.isIconified = true - // when the search view is collapsed or user hits x, hide the search view - searchView.setOnCloseListener { - searchView.visibility = View.GONE - false + searchTextInputEditText = findViewById(R.id.search_input) + val textInputEditText = searchTextInputEditText!! + // set search view listeners for text edit. filter the appropriate list based on visibility. do the filtering as the user types not just on submit as a background task + textInputEditText.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged( + s: CharSequence, start: Int, count: Int, after: Int + ) { + // do nothing + } + + override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { + // do nothing + } + + override fun afterTextChanged(s: Editable) { + // filter the appropriate list based on visibility + if (initMode) return + val query = s.toString() + TrackHelper.track().search(query).with(MainApplication.INSTANCE!!.tracker) + Thread { + if (moduleViewListBuilder.setQueryChange(query)) { + Timber.i("Query submit: %s on offline list", query) + Thread( + { moduleViewListBuilder.applyTo(moduleList!!, moduleViewAdapter!!) }, + "Query update thread" + ).start() + } + // same for online list + if (moduleViewListBuilderOnline.setQueryChange(query)) { + Timber.i("Query submit: %s on online list", query) + Thread({ + moduleViewListBuilderOnline.applyTo( + moduleListOnline!!, moduleViewAdapterOnline!! + ) + }, "Query update thread").start() + } + }.start() + } + }) + // set on submit listener for search view. filter the appropriate list based on visibility + textInputEditText.setOnEditorActionListener { _, actionId, _ -> + if (actionId == EditorInfo.IME_ACTION_SEARCH) { + // filter the appropriate list based on visibility + val query = textInputEditText.text.toString() + TrackHelper.track().search(query).with(MainApplication.INSTANCE!!.tracker) + Thread { + if (moduleViewListBuilder.setQueryChange(query)) { + Timber.i("Query submit: %s on offline list", query) + Thread( + { moduleViewListBuilder.applyTo(moduleList!!, moduleViewAdapter!!) }, + "Query update thread" + ).start() + } + // same for online list + if (moduleViewListBuilderOnline.setQueryChange(query)) { + Timber.i("Query submit: %s on online list", query) + Thread({ + moduleViewListBuilderOnline.applyTo( + moduleListOnline!!, moduleViewAdapterOnline!! + ) + }, "Query update thread").start() + } + }.start() + // hide keyboard + val imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager + imm.hideSoftInputFromWindow(textInputEditText.windowToken, 0) + true + } else { + false + } } + // set listener so when user clicks outside of search view, it loses focus + textInputEditText.setOnFocusChangeListener { _, hasFocus -> + if (!hasFocus) { + textInputEditText.clearFocus() + } + } + moduleViewAdapter = ModuleViewAdapter() moduleViewAdapterOnline = ModuleViewAdapter() val moduleList = moduleList!! @@ -222,8 +294,16 @@ class MainActivity : FoxActivity(), OnRefreshListener, SearchView.OnQueryTextLis ) { _: DialogInterface?, which: Int -> when (which) { 0 -> RuntimeUtils.reboot(this@MainActivity, RuntimeUtils.RebootMode.REBOOT) - 1 -> RuntimeUtils.reboot(this@MainActivity, RuntimeUtils.RebootMode.RECOVERY) - 2 -> RuntimeUtils.reboot(this@MainActivity, RuntimeUtils.RebootMode.BOOTLOADER) + 1 -> RuntimeUtils.reboot( + this@MainActivity, + RuntimeUtils.RebootMode.RECOVERY + ) + + 2 -> RuntimeUtils.reboot( + this@MainActivity, + RuntimeUtils.RebootMode.BOOTLOADER + ) + 3 -> RuntimeUtils.reboot(this@MainActivity, RuntimeUtils.RebootMode.EDL) } } @@ -232,14 +312,11 @@ class MainActivity : FoxActivity(), OnRefreshListener, SearchView.OnQueryTextLis rebootDialog.show() } // get background color and elevation of reboot fab - val searchCard = searchCard!! moduleList.addOnScrollListener(object : RecyclerView.OnScrollListener() { override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { - if (newState != RecyclerView.SCROLL_STATE_IDLE) searchView.clearFocus() + if (newState != RecyclerView.SCROLL_STATE_IDLE) textInputEditText.clearFocus() // hide search view and reboot fab when scrolling - we have to account for padding, corners, and shadows if (newState == RecyclerView.SCROLL_STATE_DRAGGING) { - searchCard.animate().translationY(-searchCard.height.toFloat() - 2 * 8 - 2 * 2) - .setInterpolator(DecelerateInterpolator(2f)).start() rebootFab.animate().translationY(rebootFab.height.toFloat() + 2 * 8 + 2 * 2) .setInterpolator(DecelerateInterpolator(2f)).start() } @@ -249,8 +326,6 @@ class MainActivity : FoxActivity(), OnRefreshListener, SearchView.OnQueryTextLis super.onScrolled(recyclerView, dx, dy) // if the user scrolled up, show the search bar if (dy < 0) { - searchCard.animate().translationY(0f).setInterpolator(DecelerateInterpolator(2f)) - .start() rebootFab.animate().translationY(0f).setInterpolator(DecelerateInterpolator(2f)) .start() } @@ -259,11 +334,9 @@ class MainActivity : FoxActivity(), OnRefreshListener, SearchView.OnQueryTextLis // same for online moduleListOnline.addOnScrollListener(object : RecyclerView.OnScrollListener() { override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { - if (newState != RecyclerView.SCROLL_STATE_IDLE) searchView.clearFocus() + if (newState != RecyclerView.SCROLL_STATE_IDLE) textInputEditText.clearFocus() // hide search view when scrolling if (newState == RecyclerView.SCROLL_STATE_DRAGGING) { - searchCard.animate().translationY(-searchCard.height.toFloat() - 2 * 8 - 2 * 2) - .setInterpolator(DecelerateInterpolator(2f)).start() rebootFab.animate().translationY(rebootFab.height.toFloat() + 2 * 8 + 2 * 2) .setInterpolator(DecelerateInterpolator(2f)).start() } @@ -273,27 +346,14 @@ class MainActivity : FoxActivity(), OnRefreshListener, SearchView.OnQueryTextLis super.onScrolled(recyclerView, dx, dy) // if the user scrolled up, show the search bar if (dy < 0) { - searchCard.animate().translationY(0f) - .setInterpolator(DecelerateInterpolator(2f)).start() rebootFab.animate().translationY(0f) } } }) - searchView.minimumHeight = FoxDisplay.dpToPixel(16f) - searchView.imeOptions = EditorInfo.IME_ACTION_SEARCH or EditorInfo.IME_FLAG_NO_FULLSCREEN - searchView.setOnQueryTextListener(this) - searchView.setOnCloseListener(this) - searchView.setOnQueryTextFocusChangeListener { _: View?, h: Boolean -> - if (!h) { - val query = searchView.query.toString() - if (query.isEmpty()) { - searchView.isIconified = true - } - } - cardIconifyUpdate() - } - searchView.isEnabled = false // Enabled later - cardIconifyUpdate() + textInputEditText.minimumHeight = FoxDisplay.dpToPixel(16f) + textInputEditText.imeOptions = + EditorInfo.IME_ACTION_SEARCH or EditorInfo.IME_FLAG_NO_FULLSCREEN + textInputEditText.isEnabled = false // Enabled later this.updateScreenInsets(this.resources.configuration) // on the bottom nav, there's a settings item. open the settings activity when it's clicked. @@ -309,6 +369,8 @@ class MainActivity : FoxActivity(), OnRefreshListener, SearchView.OnQueryTextLis R.id.online_menu_item -> { TrackHelper.track().event("view_list", "online_modules") .with(MainApplication.INSTANCE!!.tracker) + searchTextInputEditText!!.clearFocus() + searchTextInputEditText!!.text?.clear() // set module_list_online as visible and module_list as gone. fade in/out moduleListOnline.alpha = 0f moduleListOnline.visibility = View.VISIBLE @@ -319,18 +381,18 @@ class MainActivity : FoxActivity(), OnRefreshListener, SearchView.OnQueryTextLis moduleList.visibility = View.GONE } }) - // clear search view - searchView.setQuery("", false) - searchView.clearFocus() + textInputEditText.clearFocus() + // empty input for text input + textInputEditText.text?.clear() // reset reboot and search card - searchCard.animate().translationY(0f).setInterpolator(DecelerateInterpolator(2f)) - .start() rebootFab.animate().translationY(0f).setInterpolator(DecelerateInterpolator(2f)) } R.id.installed_menu_item -> { TrackHelper.track().event("view_list", "installed_modules") .with(MainApplication.INSTANCE!!.tracker) + searchTextInputEditText!!.clearFocus() + searchTextInputEditText!!.text?.clear() // set module_list_online as gone and module_list as visible. fade in/out moduleList.alpha = 0f moduleList.visibility = View.VISIBLE @@ -342,11 +404,9 @@ class MainActivity : FoxActivity(), OnRefreshListener, SearchView.OnQueryTextLis } }) // set search view to cleared - searchView.setQuery("", false) - searchView.clearFocus() + textInputEditText.clearFocus() + textInputEditText.text?.clear() // reset reboot and search card - searchCard.animate().translationY(0f).setInterpolator(DecelerateInterpolator(2f)) - .start() rebootFab.animate().translationY(0f).setInterpolator(DecelerateInterpolator(2f)) } } @@ -488,7 +548,7 @@ class MainActivity : FoxActivity(), OnRefreshListener, SearchView.OnQueryTextLis runOnUiThread { progressIndicator.setProgressCompat(PRECISION, true) progressIndicator.visibility = View.GONE - searchView.isEnabled = false + textInputEditText.isEnabled = false updateScreenInsets(resources.configuration) } return @@ -546,7 +606,7 @@ class MainActivity : FoxActivity(), OnRefreshListener, SearchView.OnQueryTextLis runOnUiThread { progressIndicator.setProgressCompat(PRECISION, true) progressIndicator.visibility = View.GONE - searchView.isEnabled = true + textInputEditText.isEnabled = true updateScreenInsets(resources.configuration) } maybeShowUpgrade() @@ -567,18 +627,6 @@ class MainActivity : FoxActivity(), OnRefreshListener, SearchView.OnQueryTextLis initMode = false } - private fun cardIconifyUpdate() { - val iconified = searchView!!.isIconified - val backgroundAttr = - if (iconified) if (MainApplication.isMonetEnabled) com.google.android.material.R.attr.colorSecondaryContainer else // Monet is special... - com.google.android.material.R.attr.colorSecondary else com.google.android.material.R.attr.colorPrimarySurface - val theme = searchCard!!.context.theme - val value = TypedValue() - theme.resolveAttribute(backgroundAttr, value, true) - searchCard!!.setCardBackgroundColor(value.data) - searchCard!!.alpha = if (iconified) 0.80f else 1f - } - fun updateScreenInsets() { runOnUiThread { this.updateScreenInsets(this.resources.configuration) } } @@ -594,8 +642,6 @@ class MainActivity : FoxActivity(), OnRefreshListener, SearchView.OnQueryTextLis ) moduleViewListBuilder.setHeaderPx(statusBarHeight) moduleViewListBuilderOnline.setHeaderPx(statusBarHeight) - moduleViewListBuilder.setFooterPx(FoxDisplay.dpToPixel(4f) + bottomInset + searchCard!!.height) - moduleViewListBuilderOnline.setFooterPx(FoxDisplay.dpToPixel(4f) + bottomInset + searchCard!!.height) moduleViewListBuilder.updateInsets() //this.actionBarBlur.invalidate(); overScrollInsetTop = statusBarHeight @@ -624,10 +670,8 @@ class MainActivity : FoxActivity(), OnRefreshListener, SearchView.OnQueryTextLis if (initMode) return initMode = true Timber.i("Item Before") - searchView!!.setQuery("", false) - searchView!!.clearFocus() - searchView!!.isIconified = true - cardIconifyUpdate() + searchTextInputEditText!!.clearFocus() + searchTextInputEditText!!.text?.clear() this.updateScreenInsets() updateBlurState() moduleViewListBuilder.setQuery(null) @@ -793,70 +837,6 @@ class MainActivity : FoxActivity(), OnRefreshListener, SearchView.OnQueryTextLis }, "Repo update thread").start() } - override fun onQueryTextSubmit(query: String): Boolean { - searchView!!.clearFocus() - if (initMode) return false - TrackHelper.track().search(query).with(MainApplication.INSTANCE!!.tracker) - if (moduleViewListBuilder.setQueryChange(query)) { - Timber.i("Query submit: %s on offline list", query) - Thread( - { moduleViewListBuilder.applyTo(moduleList!!, moduleViewAdapter!!) }, - "Query update thread" - ).start() - } - // same for online list - if (moduleViewListBuilderOnline.setQueryChange(query)) { - Timber.i("Query submit: %s on online list", query) - Thread({ - moduleViewListBuilderOnline.applyTo( - moduleListOnline!!, moduleViewAdapterOnline!! - ) - }, "Query update thread").start() - } - return true - } - - override fun onQueryTextChange(query: String): Boolean { - if (initMode) return false - TrackHelper.track().search(query).with(MainApplication.INSTANCE!!.tracker) - if (moduleViewListBuilder.setQueryChange(query)) { - Timber.i("Query submit: %s on offline list", query) - Thread( - { moduleViewListBuilder.applyTo(moduleList!!, moduleViewAdapter!!) }, - "Query update thread" - ).start() - } - // same for online list - if (moduleViewListBuilderOnline.setQueryChange(query)) { - Timber.i("Query submit: %s on online list", query) - Thread({ - moduleViewListBuilderOnline.applyTo( - moduleListOnline!!, moduleViewAdapterOnline!! - ) - }, "Query update thread").start() - } - return false - } - - override fun onClose(): Boolean { - if (initMode) return false - if (moduleViewListBuilder.setQueryChange(null)) { - Thread( - { moduleViewListBuilder.applyTo(moduleList!!, moduleViewAdapter!!) }, - "Query update thread" - ).start() - } - // same for online list - if (moduleViewListBuilderOnline.setQueryChange(null)) { - Thread({ - moduleViewListBuilderOnline.applyTo( - moduleListOnline!!, moduleViewAdapterOnline!! - ) - }, "Query update thread").start() - } - return false - } - override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) this.updateScreenInsets() @@ -917,6 +897,22 @@ class MainActivity : FoxActivity(), OnRefreshListener, SearchView.OnQueryTextLis } } + override fun dispatchTouchEvent(event: MotionEvent): Boolean { + if (event.action == MotionEvent.ACTION_DOWN) { + val v = currentFocus + if (v is EditText) { + val outRect = Rect() + v.getGlobalVisibleRect(outRect) + if (!outRect.contains(event.rawX.toInt(), event.rawY.toInt())) { + v.clearFocus() + val imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager + imm.hideSoftInputFromWindow(v.getWindowToken(), 0) + } + } + } + return super.dispatchTouchEvent(event) + } + companion object { fun getFoxActivity(activity: FoxActivity): FoxActivity { return activity diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 7163de5..09ea40e 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -12,15 +12,42 @@ app:layout_constraintTop_toTopOf="parent" tools:context=".MainActivity"> + + + + + + + + - + app:layout_constraintTop_toBottomOf="@+id/search_input_layout"> + - - - - - - + app:layout_constraintTop_toTopOf="parent" />