Fix glitch when canceling a search and reopening search

This commit is contained in:
Andrew 2024-05-09 00:55:10 +09:30
parent a054fae758
commit 9935b641e1
6 changed files with 46 additions and 57 deletions

View File

@ -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)
}
}
}

View File

@ -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) {

View File

@ -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) {

View File

@ -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<CharSequence?>(null)
val query: StateFlow<CharSequence?> = _query
private val _query = MutableStateFlow<CharSequence>("")
val query: StateFlow<CharSequence> = _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<InputFilter>(LengthFilter(100)) ) // 100 char search limit
binding.searchCancel.setOnClickListener(this)
binding.searchClear.setOnClickListener(this)
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
binding.searchInput.filters = arrayOf<InputFilter>(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 {

View File

@ -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<GlobalSearchResult> = _result
val refreshes = Channel<Unit>()
private val refreshes = Channel<Unit>()
private val _queryText: MutableStateFlow<CharSequence> = 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<SearchResult>()
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 <T> Flow<T>.reEmit(refreshes: Channel<Unit>) = flatMapLatest { query -> merge(flowOf(query), refreshes.consumeAsFlow().map { query }) }

View File

@ -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)
}
})
}