diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt index 5d1a2c93bf..b2f7e2341d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt @@ -708,12 +708,9 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe viewContainer.setTypists(recipients) } if (textSecurePreferences.isTypingIndicatorsEnabled()) { - binding!!.inputBar.addTextChangedListener(object : SimpleTextWatcher() { - - override fun onTextChanged(text: String?) { - ApplicationContext.getInstance(this@ConversationActivityV2).typingStatusSender.onTypingStarted(viewModel.threadId) - } - }) + binding!!.inputBar.addTextChangedListener { + ApplicationContext.getInstance(this).typingStatusSender.onTypingStarted(viewModel.threadId) + } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt index 3544f11b10..e21796845d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt @@ -11,6 +11,7 @@ import android.view.KeyEvent import android.view.LayoutInflater import android.view.MotionEvent import android.view.inputmethod.EditorInfo +import android.widget.EditText import android.widget.RelativeLayout import android.widget.TextView import androidx.core.view.isVisible @@ -26,6 +27,8 @@ import org.thoughtcrime.securesms.conversation.v2.messages.QuoteViewDelegate import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.database.model.MmsMessageRecord import org.thoughtcrime.securesms.mms.GlideRequests +import org.thoughtcrime.securesms.util.SimpleTextWatcher +import org.thoughtcrime.securesms.util.addTextChangedListener import org.thoughtcrime.securesms.util.contains import org.thoughtcrime.securesms.util.toDp import org.thoughtcrime.securesms.util.toPx @@ -219,8 +222,8 @@ class InputBar : RelativeLayout, InputBarEditTextDelegate, QuoteViewDelegate, Li setOf(attachmentsButton, microphoneButton).forEach { it.snIsEnabled = showMediaControls } } - fun addTextChangedListener(textWatcher: TextWatcher) { - binding.inputBarEditText.addTextChangedListener(textWatcher) + fun addTextChangedListener(listener: (String) -> Unit) { + binding.inputBarEditText.addTextChangedListener(listener) } fun setSelection(index: Int) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt index 0d58587d80..13ca924e18 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt @@ -37,6 +37,8 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import androidx.core.os.bundleOf +import androidx.core.view.isGone +import androidx.core.view.isInvisible import androidx.core.view.isVisible import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope @@ -67,7 +69,6 @@ import org.session.libsession.utilities.Address import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.ProfilePictureModifiedEvent import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsession.utilities.associateByNotNull import org.session.libsession.utilities.groupByNotNull import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.utilities.Log @@ -278,8 +279,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), // monitor the global search VM query launch { binding.globalSearchInputLayout.query - .onEach(globalSearchViewModel::postQuery) - .collect() + .collect(globalSearchViewModel::setQuery) } // Get group results and display them launch { @@ -434,7 +434,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), if (hasFocus) { setSearchShown(true) } else { - setSearchShown(!binding.globalSearchInputLayout.query.value.isNullOrEmpty()) + setSearchShown(binding.globalSearchInputLayout.query.value.isNotEmpty()) } } @@ -444,7 +444,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), binding.recyclerView.isVisible = !isShown binding.emptyStateContainer.isVisible = (binding.recyclerView.adapter as HomeAdapter).itemCount == 0 && binding.recyclerView.isVisible binding.seedReminderView.isVisible = !TextSecurePreferences.getHasViewedSeed(this) && !isShown - binding.globalSearchRecycler.isVisible = isShown + binding.globalSearchRecycler.isInvisible = !isShown binding.newConversationButton.isVisible = !isShown } @@ -572,11 +572,8 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), // region Interaction @Deprecated("Deprecated in Java") override fun onBackPressed() { - if (binding.globalSearchRecycler.isVisible) { - binding.globalSearchInputLayout.clearSearch(true) - return - } - super.onBackPressed() + if (binding.globalSearchRecycler.isVisible) binding.globalSearchInputLayout.clearSearch(true) + else super.onBackPressed() } override fun onConversationClick(thread: ThreadRecord) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchInputLayout.kt b/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchInputLayout.kt index c22ccde1f1..442e6159fd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchInputLayout.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchInputLayout.kt @@ -16,42 +16,37 @@ import android.widget.TextView import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import network.loki.messenger.databinding.ViewGlobalSearchInputBinding +import org.thoughtcrime.securesms.util.SimpleTextWatcher +import org.thoughtcrime.securesms.util.addTextChangedListener class GlobalSearchInputLayout @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null ) : LinearLayout(context, attrs), View.OnFocusChangeListener, - View.OnClickListener, - TextWatcher, TextView.OnEditorActionListener { + TextView.OnEditorActionListener { var binding: ViewGlobalSearchInputBinding = ViewGlobalSearchInputBinding.inflate(LayoutInflater.from(context), this, true) var listener: GlobalSearchInputLayoutListener? = null - private val _query = MutableStateFlow(null) - val query: StateFlow = _query + private val _query = MutableStateFlow("") + val query: StateFlow = _query override fun onAttachedToWindow() { super.onAttachedToWindow() binding.searchInput.onFocusChangeListener = this - binding.searchInput.addTextChangedListener(this) + binding.searchInput.addTextChangedListener(::setQuery) binding.searchInput.setOnEditorActionListener(this) - binding.searchInput.setFilters( arrayOf(LengthFilter(100)) ) // 100 char search limit - binding.searchCancel.setOnClickListener(this) - binding.searchClear.setOnClickListener(this) - } - - override fun onDetachedFromWindow() { - super.onDetachedFromWindow() + binding.searchInput.filters = arrayOf(LengthFilter(100)) // 100 char search limit + binding.searchCancel.setOnClickListener { clearSearch(true) } + binding.searchClear.setOnClickListener { clearSearch(false) } } override fun onFocusChange(v: View?, hasFocus: Boolean) { if (v === binding.searchInput) { - val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager - if (!hasFocus) { - imm.hideSoftInputFromWindow(windowToken, 0) - } else { - imm.showSoftInput(v, 0) + (context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager).apply { + if (hasFocus) showSoftInput(v, 0) + else hideSoftInputFromWindow(windowToken, 0) } listener?.onInputFocusChanged(hasFocus) } @@ -65,27 +60,16 @@ class GlobalSearchInputLayout @JvmOverloads constructor( return false } - override fun onClick(v: View?) { - if (v === binding.searchCancel) { - clearSearch(true) - } else if (v === binding.searchClear) { - clearSearch(false) - } - } - fun clearSearch(clearFocus: Boolean) { binding.searchInput.text = null + setQuery("") if (clearFocus) { binding.searchInput.clearFocus() } } - override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} - - override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {} - - override fun afterTextChanged(s: Editable?) { - _query.value = s?.toString() + private fun setQuery(query: String) { + _query.value = query } interface GlobalSearchInputLayoutListener { diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchViewModel.kt index 380ec0609a..39c69f9e15 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchViewModel.kt @@ -29,6 +29,7 @@ import org.thoughtcrime.securesms.search.model.SearchResult import java.util.concurrent.TimeUnit import javax.inject.Inject +@OptIn(ExperimentalCoroutinesApi::class) @HiltViewModel class GlobalSearchViewModel @Inject constructor( private val searchRepository: SearchRepository, @@ -40,12 +41,11 @@ class GlobalSearchViewModel @Inject constructor( val result: StateFlow = _result - val refreshes = Channel() + private val refreshes = Channel() private val _queryText: MutableStateFlow = MutableStateFlow("") - fun postQuery(charSequence: CharSequence?) { - charSequence ?: return + fun setQuery(charSequence: CharSequence) { _queryText.value = charSequence } @@ -58,15 +58,14 @@ class GlobalSearchViewModel @Inject constructor( .reEmit(refreshes) .buffer(onBufferOverflow = BufferOverflow.DROP_OLDEST) .mapLatest { query -> - // User input delay in case we get a new query within a few hundred ms this - // coroutine will be cancelled and the expensive query will not be run. - delay(300) - if (query.trim().isEmpty()) { // searching for 05 as contactDb#getAllContacts was not returning contacts // without a nickname/name who haven't approved us. GlobalSearchResult(query.toString(), searchRepository.queryContacts("05").first.toList()) } else { + // User input delay in case we get a new query within a few hundred ms this + // coroutine will be cancelled and the expensive query will not be run. + delay(300) val settableFuture = SettableFuture() searchRepository.query(query.toString(), settableFuture::set) try { @@ -84,7 +83,7 @@ class GlobalSearchViewModel @Inject constructor( } /** - * Re-emit whenevr refreshes emits. + * Re-emit whenever refreshes emits. * */ @OptIn(ExperimentalCoroutinesApi::class) private fun Flow.reEmit(refreshes: Channel) = flatMapLatest { query -> merge(flowOf(query), refreshes.consumeAsFlow().map { query }) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/ViewUtilities.kt b/app/src/main/java/org/thoughtcrime/securesms/util/ViewUtilities.kt index c0477825fd..a7ba6027d5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/ViewUtilities.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/ViewUtilities.kt @@ -16,6 +16,7 @@ import androidx.annotation.DimenRes import network.loki.messenger.R import org.session.libsession.utilities.getColorFromAttr import android.view.inputmethod.InputMethodManager +import android.widget.EditText import androidx.annotation.AttrRes import androidx.annotation.ColorRes import androidx.core.graphics.applyCanvas @@ -111,3 +112,11 @@ fun Size.coerceAtMost(longestWidth: Int): Size = height.coerceAtMost(longestWidth).let { Size((it * aspect).roundToInt(), it) } } } + +fun EditText.addTextChangedListener(listener: (String) -> Unit) { + addTextChangedListener(object: SimpleTextWatcher() { + override fun onTextChanged(text: String) { + listener(text) + } + }) +}