mirror of
https://github.com/oxen-io/session-android.git
synced 2024-12-24 16:57:50 +00:00
Fix glitch when canceling a search and reopening search
This commit is contained in:
parent
a054fae758
commit
9935b641e1
@ -708,12 +708,9 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
viewContainer.setTypists(recipients)
|
viewContainer.setTypists(recipients)
|
||||||
}
|
}
|
||||||
if (textSecurePreferences.isTypingIndicatorsEnabled()) {
|
if (textSecurePreferences.isTypingIndicatorsEnabled()) {
|
||||||
binding!!.inputBar.addTextChangedListener(object : SimpleTextWatcher() {
|
binding!!.inputBar.addTextChangedListener {
|
||||||
|
ApplicationContext.getInstance(this).typingStatusSender.onTypingStarted(viewModel.threadId)
|
||||||
override fun onTextChanged(text: String?) {
|
}
|
||||||
ApplicationContext.getInstance(this@ConversationActivityV2).typingStatusSender.onTypingStarted(viewModel.threadId)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ import android.view.KeyEvent
|
|||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import android.view.inputmethod.EditorInfo
|
import android.view.inputmethod.EditorInfo
|
||||||
|
import android.widget.EditText
|
||||||
import android.widget.RelativeLayout
|
import android.widget.RelativeLayout
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.core.view.isVisible
|
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.MessageRecord
|
||||||
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
|
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
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.contains
|
||||||
import org.thoughtcrime.securesms.util.toDp
|
import org.thoughtcrime.securesms.util.toDp
|
||||||
import org.thoughtcrime.securesms.util.toPx
|
import org.thoughtcrime.securesms.util.toPx
|
||||||
@ -219,8 +222,8 @@ class InputBar : RelativeLayout, InputBarEditTextDelegate, QuoteViewDelegate, Li
|
|||||||
setOf(attachmentsButton, microphoneButton).forEach { it.snIsEnabled = showMediaControls }
|
setOf(attachmentsButton, microphoneButton).forEach { it.snIsEnabled = showMediaControls }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addTextChangedListener(textWatcher: TextWatcher) {
|
fun addTextChangedListener(listener: (String) -> Unit) {
|
||||||
binding.inputBarEditText.addTextChangedListener(textWatcher)
|
binding.inputBarEditText.addTextChangedListener(listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setSelection(index: Int) {
|
fun setSelection(index: Int) {
|
||||||
|
@ -37,6 +37,8 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
|
import androidx.core.view.isGone
|
||||||
|
import androidx.core.view.isInvisible
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.lifecycleScope
|
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.GroupUtil
|
||||||
import org.session.libsession.utilities.ProfilePictureModifiedEvent
|
import org.session.libsession.utilities.ProfilePictureModifiedEvent
|
||||||
import org.session.libsession.utilities.TextSecurePreferences
|
import org.session.libsession.utilities.TextSecurePreferences
|
||||||
import org.session.libsession.utilities.associateByNotNull
|
|
||||||
import org.session.libsession.utilities.groupByNotNull
|
import org.session.libsession.utilities.groupByNotNull
|
||||||
import org.session.libsession.utilities.recipients.Recipient
|
import org.session.libsession.utilities.recipients.Recipient
|
||||||
import org.session.libsignal.utilities.Log
|
import org.session.libsignal.utilities.Log
|
||||||
@ -278,8 +279,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
|
|||||||
// monitor the global search VM query
|
// monitor the global search VM query
|
||||||
launch {
|
launch {
|
||||||
binding.globalSearchInputLayout.query
|
binding.globalSearchInputLayout.query
|
||||||
.onEach(globalSearchViewModel::postQuery)
|
.collect(globalSearchViewModel::setQuery)
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
// Get group results and display them
|
// Get group results and display them
|
||||||
launch {
|
launch {
|
||||||
@ -434,7 +434,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
|
|||||||
if (hasFocus) {
|
if (hasFocus) {
|
||||||
setSearchShown(true)
|
setSearchShown(true)
|
||||||
} else {
|
} 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.recyclerView.isVisible = !isShown
|
||||||
binding.emptyStateContainer.isVisible = (binding.recyclerView.adapter as HomeAdapter).itemCount == 0 && binding.recyclerView.isVisible
|
binding.emptyStateContainer.isVisible = (binding.recyclerView.adapter as HomeAdapter).itemCount == 0 && binding.recyclerView.isVisible
|
||||||
binding.seedReminderView.isVisible = !TextSecurePreferences.getHasViewedSeed(this) && !isShown
|
binding.seedReminderView.isVisible = !TextSecurePreferences.getHasViewedSeed(this) && !isShown
|
||||||
binding.globalSearchRecycler.isVisible = isShown
|
binding.globalSearchRecycler.isInvisible = !isShown
|
||||||
binding.newConversationButton.isVisible = !isShown
|
binding.newConversationButton.isVisible = !isShown
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -572,11 +572,8 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
|
|||||||
// region Interaction
|
// region Interaction
|
||||||
@Deprecated("Deprecated in Java")
|
@Deprecated("Deprecated in Java")
|
||||||
override fun onBackPressed() {
|
override fun onBackPressed() {
|
||||||
if (binding.globalSearchRecycler.isVisible) {
|
if (binding.globalSearchRecycler.isVisible) binding.globalSearchInputLayout.clearSearch(true)
|
||||||
binding.globalSearchInputLayout.clearSearch(true)
|
else super.onBackPressed()
|
||||||
return
|
|
||||||
}
|
|
||||||
super.onBackPressed()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onConversationClick(thread: ThreadRecord) {
|
override fun onConversationClick(thread: ThreadRecord) {
|
||||||
|
@ -16,42 +16,37 @@ import android.widget.TextView
|
|||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import network.loki.messenger.databinding.ViewGlobalSearchInputBinding
|
import network.loki.messenger.databinding.ViewGlobalSearchInputBinding
|
||||||
|
import org.thoughtcrime.securesms.util.SimpleTextWatcher
|
||||||
|
import org.thoughtcrime.securesms.util.addTextChangedListener
|
||||||
|
|
||||||
class GlobalSearchInputLayout @JvmOverloads constructor(
|
class GlobalSearchInputLayout @JvmOverloads constructor(
|
||||||
context: Context, attrs: AttributeSet? = null
|
context: Context, attrs: AttributeSet? = null
|
||||||
) : LinearLayout(context, attrs),
|
) : LinearLayout(context, attrs),
|
||||||
View.OnFocusChangeListener,
|
View.OnFocusChangeListener,
|
||||||
View.OnClickListener,
|
TextView.OnEditorActionListener {
|
||||||
TextWatcher, TextView.OnEditorActionListener {
|
|
||||||
|
|
||||||
var binding: ViewGlobalSearchInputBinding = ViewGlobalSearchInputBinding.inflate(LayoutInflater.from(context), this, true)
|
var binding: ViewGlobalSearchInputBinding = ViewGlobalSearchInputBinding.inflate(LayoutInflater.from(context), this, true)
|
||||||
|
|
||||||
var listener: GlobalSearchInputLayoutListener? = null
|
var listener: GlobalSearchInputLayoutListener? = null
|
||||||
|
|
||||||
private val _query = MutableStateFlow<CharSequence?>(null)
|
private val _query = MutableStateFlow<CharSequence>("")
|
||||||
val query: StateFlow<CharSequence?> = _query
|
val query: StateFlow<CharSequence> = _query
|
||||||
|
|
||||||
override fun onAttachedToWindow() {
|
override fun onAttachedToWindow() {
|
||||||
super.onAttachedToWindow()
|
super.onAttachedToWindow()
|
||||||
binding.searchInput.onFocusChangeListener = this
|
binding.searchInput.onFocusChangeListener = this
|
||||||
binding.searchInput.addTextChangedListener(this)
|
binding.searchInput.addTextChangedListener(::setQuery)
|
||||||
binding.searchInput.setOnEditorActionListener(this)
|
binding.searchInput.setOnEditorActionListener(this)
|
||||||
binding.searchInput.setFilters( arrayOf<InputFilter>(LengthFilter(100)) ) // 100 char search limit
|
binding.searchInput.filters = arrayOf<InputFilter>(LengthFilter(100)) // 100 char search limit
|
||||||
binding.searchCancel.setOnClickListener(this)
|
binding.searchCancel.setOnClickListener { clearSearch(true) }
|
||||||
binding.searchClear.setOnClickListener(this)
|
binding.searchClear.setOnClickListener { clearSearch(false) }
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDetachedFromWindow() {
|
|
||||||
super.onDetachedFromWindow()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFocusChange(v: View?, hasFocus: Boolean) {
|
override fun onFocusChange(v: View?, hasFocus: Boolean) {
|
||||||
if (v === binding.searchInput) {
|
if (v === binding.searchInput) {
|
||||||
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
(context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager).apply {
|
||||||
if (!hasFocus) {
|
if (hasFocus) showSoftInput(v, 0)
|
||||||
imm.hideSoftInputFromWindow(windowToken, 0)
|
else hideSoftInputFromWindow(windowToken, 0)
|
||||||
} else {
|
|
||||||
imm.showSoftInput(v, 0)
|
|
||||||
}
|
}
|
||||||
listener?.onInputFocusChanged(hasFocus)
|
listener?.onInputFocusChanged(hasFocus)
|
||||||
}
|
}
|
||||||
@ -65,27 +60,16 @@ class GlobalSearchInputLayout @JvmOverloads constructor(
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onClick(v: View?) {
|
|
||||||
if (v === binding.searchCancel) {
|
|
||||||
clearSearch(true)
|
|
||||||
} else if (v === binding.searchClear) {
|
|
||||||
clearSearch(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun clearSearch(clearFocus: Boolean) {
|
fun clearSearch(clearFocus: Boolean) {
|
||||||
binding.searchInput.text = null
|
binding.searchInput.text = null
|
||||||
|
setQuery("")
|
||||||
if (clearFocus) {
|
if (clearFocus) {
|
||||||
binding.searchInput.clearFocus()
|
binding.searchInput.clearFocus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
|
private fun setQuery(query: String) {
|
||||||
|
_query.value = query
|
||||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
|
|
||||||
|
|
||||||
override fun afterTextChanged(s: Editable?) {
|
|
||||||
_query.value = s?.toString()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GlobalSearchInputLayoutListener {
|
interface GlobalSearchInputLayoutListener {
|
||||||
|
@ -29,6 +29,7 @@ import org.thoughtcrime.securesms.search.model.SearchResult
|
|||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class GlobalSearchViewModel @Inject constructor(
|
class GlobalSearchViewModel @Inject constructor(
|
||||||
private val searchRepository: SearchRepository,
|
private val searchRepository: SearchRepository,
|
||||||
@ -40,12 +41,11 @@ class GlobalSearchViewModel @Inject constructor(
|
|||||||
|
|
||||||
val result: StateFlow<GlobalSearchResult> = _result
|
val result: StateFlow<GlobalSearchResult> = _result
|
||||||
|
|
||||||
val refreshes = Channel<Unit>()
|
private val refreshes = Channel<Unit>()
|
||||||
|
|
||||||
private val _queryText: MutableStateFlow<CharSequence> = MutableStateFlow("")
|
private val _queryText: MutableStateFlow<CharSequence> = MutableStateFlow("")
|
||||||
|
|
||||||
fun postQuery(charSequence: CharSequence?) {
|
fun setQuery(charSequence: CharSequence) {
|
||||||
charSequence ?: return
|
|
||||||
_queryText.value = charSequence
|
_queryText.value = charSequence
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,15 +58,14 @@ class GlobalSearchViewModel @Inject constructor(
|
|||||||
.reEmit(refreshes)
|
.reEmit(refreshes)
|
||||||
.buffer(onBufferOverflow = BufferOverflow.DROP_OLDEST)
|
.buffer(onBufferOverflow = BufferOverflow.DROP_OLDEST)
|
||||||
.mapLatest { query ->
|
.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()) {
|
if (query.trim().isEmpty()) {
|
||||||
// searching for 05 as contactDb#getAllContacts was not returning contacts
|
// searching for 05 as contactDb#getAllContacts was not returning contacts
|
||||||
// without a nickname/name who haven't approved us.
|
// without a nickname/name who haven't approved us.
|
||||||
GlobalSearchResult(query.toString(), searchRepository.queryContacts("05").first.toList())
|
GlobalSearchResult(query.toString(), searchRepository.queryContacts("05").first.toList())
|
||||||
} else {
|
} 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>()
|
val settableFuture = SettableFuture<SearchResult>()
|
||||||
searchRepository.query(query.toString(), settableFuture::set)
|
searchRepository.query(query.toString(), settableFuture::set)
|
||||||
try {
|
try {
|
||||||
@ -84,7 +83,7 @@ class GlobalSearchViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Re-emit whenevr refreshes emits.
|
* Re-emit whenever refreshes emits.
|
||||||
* */
|
* */
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
private fun <T> Flow<T>.reEmit(refreshes: Channel<Unit>) = flatMapLatest { query -> merge(flowOf(query), refreshes.consumeAsFlow().map { query }) }
|
private fun <T> Flow<T>.reEmit(refreshes: Channel<Unit>) = flatMapLatest { query -> merge(flowOf(query), refreshes.consumeAsFlow().map { query }) }
|
||||||
|
@ -16,6 +16,7 @@ import androidx.annotation.DimenRes
|
|||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
import org.session.libsession.utilities.getColorFromAttr
|
import org.session.libsession.utilities.getColorFromAttr
|
||||||
import android.view.inputmethod.InputMethodManager
|
import android.view.inputmethod.InputMethodManager
|
||||||
|
import android.widget.EditText
|
||||||
import androidx.annotation.AttrRes
|
import androidx.annotation.AttrRes
|
||||||
import androidx.annotation.ColorRes
|
import androidx.annotation.ColorRes
|
||||||
import androidx.core.graphics.applyCanvas
|
import androidx.core.graphics.applyCanvas
|
||||||
@ -111,3 +112,11 @@ fun Size.coerceAtMost(longestWidth: Int): Size =
|
|||||||
height.coerceAtMost(longestWidth).let { Size((it * aspect).roundToInt(), it) }
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user