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 ada4ceda66..79684d83d5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt @@ -185,7 +185,6 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), binding.seedReminderView.isVisible = false } } - setupMessageRequestsBanner() // Set up recycler view binding.globalSearchInputLayout.listener = this homeAdapter.setHasStableIds(true) @@ -218,9 +217,9 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), // Subscribe to threads and update the UI lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { - homeViewModel.threads + homeViewModel.data .filterNotNull() // We don't actually want the null value here as it indicates a loading state (maybe we need a loading state?) - .collectLatest { threads -> + .collectLatest { data -> val manager = binding.recyclerView.layoutManager as LinearLayoutManager val firstPos = manager.findFirstCompletelyVisibleItemPosition() val offsetTop = if(firstPos >= 0) { @@ -228,9 +227,9 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), manager.getDecoratedTop(view) - manager.getTopDecorationHeight(view) } ?: 0 } else 0 - homeAdapter.data = threads + homeAdapter.data = data if(firstPos >= 0) { manager.scrollToPositionWithOffset(firstPos, offsetTop) } - setupMessageRequestsBanner() + setupMessageRequestsBanner(data.unapprovedConversationCount, data.hasHiddenMessageRequests) updateEmptyState() } } @@ -341,10 +340,9 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), binding.newConversationButton.isVisible = !isShown } - private fun setupMessageRequestsBanner() { - val messageRequestCount = threadDb.unapprovedConversationCount + private fun setupMessageRequestsBanner(messageRequestCount: Int, hasHiddenMessageRequests: Boolean) { // Set up message requests - if (messageRequestCount > 0 && !textSecurePreferences.hasHiddenMessageRequests()) { + if (messageRequestCount > 0 && !hasHiddenMessageRequests) { with(ViewMessageRequestBannerBinding.inflate(layoutInflater)) { unreadCountTextView.text = messageRequestCount.toString() timestampTextView.text = DateUtils.getDisplayFormattedTimeSpanString( @@ -664,7 +662,6 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), text("Hide message requests?") button(R.string.yes) { textSecurePreferences.setHasHiddenMessageRequests() - setupMessageRequestsBanner() homeViewModel.tryReload() } button(R.string.no) diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeAdapter.kt index 63e0fed5b6..d94037f28c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeAdapter.kt @@ -25,11 +25,9 @@ class HomeAdapter( var header: View? = null - var data: HomeViewModel.Data = HomeViewModel.Data(emptyList(), emptySet()) + var data: HomeViewModel.Data = HomeViewModel.Data(emptyList(), 0, false, emptySet()) set(newData) { - if (field === newData) { - return - } + if (field === newData) return val diff = HomeDiffUtil(field, newData, context, configFactory) val diffResult = DiffUtil.calculateDiff(diff) diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt index dccebd4156..09da7c80a0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt @@ -15,11 +15,14 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn -import kotlinx.coroutines.withContext +import org.session.libsession.utilities.TextSecurePreferences import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.database.DatabaseContentProviders import org.thoughtcrime.securesms.database.ThreadDatabase @@ -30,9 +33,10 @@ import dagger.hilt.android.qualifiers.ApplicationContext as ApplicationContextQu @HiltViewModel class HomeViewModel @Inject constructor( - private val threadDb: ThreadDatabase, - private val contentResolver: ContentResolver, - @ApplicationContextQualifier private val context: Context, + private val threadDb: ThreadDatabase, + private val contentResolver: ContentResolver, + private val prefs: TextSecurePreferences, + @ApplicationContextQualifier private val context: Context, ) : ViewModel() { // SharedFlow that emits whenever the user asks us to reload the conversation private val manualReloadTrigger = MutableSharedFlow( @@ -46,8 +50,18 @@ class HomeViewModel @Inject constructor( * This flow will emit whenever the user asks us to reload the conversation list or * whenever the conversation list changes. */ - val threads: StateFlow = combine(observeConversationList(), observeTypingStatus(), ::Data) - .stateIn(viewModelScope, SharingStarted.Eagerly, null) + val data: StateFlow = combine( + observeConversationList(), + unapprovedConversationCount(), + hasHiddenMessageRequestsFlow(), + observeTypingStatus(), + ::Data + ).stateIn(viewModelScope, SharingStarted.Eagerly, null) + + private fun hasHiddenMessageRequestsFlow() = TextSecurePreferences.events + .filter { it == TextSecurePreferences.HAS_HIDDEN_MESSAGE_REQUESTS } + .map { prefs.hasHiddenMessageRequests() } + .onStart { emit(prefs.hasHiddenMessageRequests()) } private fun observeTypingStatus(): Flow> = ApplicationContext.getInstance(context).typingStatusRepository @@ -56,30 +70,38 @@ class HomeViewModel @Inject constructor( .onStart { emit(emptySet()) } .distinctUntilChanged() + private fun unapprovedConversationCount() = + contentResolver.observeChanges(DatabaseContentProviders.ConversationList.CONTENT_URI) + .flowOn(Dispatchers.IO) + .map { threadDb.unapprovedConversationCount } + .onStart { emit(threadDb.unapprovedConversationCount) } + @Suppress("OPT_IN_USAGE") private fun observeConversationList(): Flow> = merge( - manualReloadTrigger, - contentResolver.observeChanges(DatabaseContentProviders.ConversationList.CONTENT_URI)) - .debounce(CHANGE_NOTIFICATION_DEBOUNCE_MILLS) - .onStart { emit(Unit) } - .mapLatest { _ -> - withContext(Dispatchers.IO) { - threadDb.approvedConversationList.use { openCursor -> - val reader = threadDb.readerFor(openCursor) - buildList(reader.count) { - while (true) { - add(reader.next ?: break) - } - } + manualReloadTrigger, + contentResolver.observeChanges(DatabaseContentProviders.ConversationList.CONTENT_URI) + ) + .flowOn(Dispatchers.IO) + .debounce(CHANGE_NOTIFICATION_DEBOUNCE_MILLS) + .onStart { emit(Unit) } + .mapLatest { _ -> + threadDb.approvedConversationList.use { openCursor -> + val reader = threadDb.readerFor(openCursor) + buildList(reader.count) { + while (true) { + add(reader.next ?: break) } } } + } fun tryReload() = manualReloadTrigger.tryEmit(Unit) data class Data( - val threads: List, - val typingThreadIDs: Set + val threads: List, + val unapprovedConversationCount: Int, + val hasHiddenMessageRequests: Boolean, + val typingThreadIDs: Set ) companion object {