From 31f4de22cd7ed8594be54e01516df1883d995df1 Mon Sep 17 00:00:00 2001 From: fanchao Date: Thu, 23 May 2024 14:17:38 +1000 Subject: [PATCH] More changes --- .../securesms/home/HomeActivity.kt | 4 -- .../securesms/home/HomeAdapter.kt | 19 ++---- .../securesms/home/HomeDiffUtil.kt | 17 ++--- .../securesms/home/HomeViewModel.kt | 63 +++++++++++++------ 4 files changed, 58 insertions(+), 45 deletions(-) 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 1ade41587e..ada4ceda66 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt @@ -202,10 +202,6 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), binding.createNewPrivateChatButton.setOnClickListener { showNewConversation() } IP2Country.configureIfNeeded(this@HomeActivity) - ApplicationContext.getInstance(this@HomeActivity).typingStatusRepository.typingThreads.observe(this) { threadIds -> - homeAdapter.typingThreadIDs = (threadIds ?: setOf()) - } - // Set up new conversation button binding.newConversationButton.setOnClickListener { showNewConversation() } // Observe blocked contacts changed events 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 d7f887ed00..071e421bf8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeAdapter.kt @@ -9,7 +9,6 @@ import androidx.recyclerview.widget.ListUpdateCallback import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView.NO_ID import network.loki.messenger.R -import org.thoughtcrime.securesms.database.model.ThreadRecord import org.thoughtcrime.securesms.dependencies.ConfigFactory import org.thoughtcrime.securesms.mms.GlideRequests @@ -26,7 +25,7 @@ class HomeAdapter( var header: View? = null - var data: List = emptyList() + var data: HomeViewModel.HomeData = HomeViewModel.HomeData(emptyList(), emptySet()) set(newData) { if (field !== newData) { val diff = HomeDiffUtil(field, newData, context, configFactory) @@ -60,18 +59,10 @@ class HomeAdapter( override fun getItemId(position: Int): Long { if (hasHeaderView() && position == 0) return NO_ID val offsetPosition = if (hasHeaderView()) position-1 else position - return data[offsetPosition].threadId + return data.threads[offsetPosition].threadId } lateinit var glide: GlideRequests - var typingThreadIDs = setOf() - set(value) { - if (field == value) { return } - - field = value - // TODO: replace this with a diffed update or a partial change set with payloads - notifyDataSetChanged() - } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder = when (viewType) { @@ -94,8 +85,8 @@ class HomeAdapter( override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { if (holder is ConversationViewHolder) { val offset = if (hasHeaderView()) position - 1 else position - val thread = data[offset] - val isTyping = typingThreadIDs.contains(thread.threadId) + val thread = data.threads[offset] + val isTyping = data.typingThreadIDs.contains(thread.threadId) holder.view.bind(thread, isTyping, glide) } } @@ -112,7 +103,7 @@ class HomeAdapter( if (hasHeaderView() && position == 0) HEADER else ITEM - override fun getItemCount(): Int = data.size + if (hasHeaderView()) 1 else 0 + override fun getItemCount(): Int = data.threads.size + if (hasHeaderView()) 1 else 0 class ConversationViewHolder(val view: ConversationView) : RecyclerView.ViewHolder(view) diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeDiffUtil.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeDiffUtil.kt index 0fe93d41de..41b7e581fb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeDiffUtil.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeDiffUtil.kt @@ -7,22 +7,22 @@ import org.thoughtcrime.securesms.dependencies.ConfigFactory import org.thoughtcrime.securesms.util.getConversationUnread class HomeDiffUtil( - private val old: List, - private val new: List, + private val old: HomeViewModel.HomeData, + private val new: HomeViewModel.HomeData, private val context: Context, private val configFactory: ConfigFactory ): DiffUtil.Callback() { - override fun getOldListSize(): Int = old.size + override fun getOldListSize(): Int = old.threads.size - override fun getNewListSize(): Int = new.size + override fun getNewListSize(): Int = new.threads.size override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean = - old[oldItemPosition].threadId == new[newItemPosition].threadId + old.threads[oldItemPosition].threadId == new.threads[newItemPosition].threadId override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { - val oldItem = old[oldItemPosition] - val newItem = new[newItemPosition] + val oldItem = old.threads[oldItemPosition] + val newItem = new.threads[newItemPosition] // return early to save getDisplayBody or expensive calls var isSameItem = true @@ -47,7 +47,8 @@ class HomeDiffUtil( oldItem.isSent == newItem.isSent && oldItem.isPending == newItem.isPending && oldItem.lastSeen == newItem.lastSeen && - configFactory.convoVolatile?.getConversationUnread(newItem) != true + configFactory.convoVolatile?.getConversationUnread(newItem) != true && + old.typingThreadIDs.contains(oldItem.threadId) == new.typingThreadIDs.contains(newItem.threadId) ) } 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 093cf6e21c..0e4817994c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt @@ -1,31 +1,37 @@ package org.thoughtcrime.securesms.home +import android.content.ContentResolver import android.content.Context import androidx.lifecycle.ViewModel +import androidx.lifecycle.asFlow import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel -import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.distinctUntilChanged 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.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.database.DatabaseContentProviders import org.thoughtcrime.securesms.database.ThreadDatabase import org.thoughtcrime.securesms.database.model.ThreadRecord import org.thoughtcrime.securesms.util.observeChanges import javax.inject.Inject +import dagger.hilt.android.qualifiers.ApplicationContext as ApplicationContextQualifier @HiltViewModel class HomeViewModel @Inject constructor( private val threadDb: ThreadDatabase, - @ApplicationContext appContext: Context, + contentResolver: ContentResolver, + @ApplicationContextQualifier context: Context, ) : ViewModel() { // SharedFlow that emits whenever the user asks us to reload the conversation private val manualReloadTrigger = MutableSharedFlow( @@ -34,33 +40,52 @@ class HomeViewModel @Inject constructor( ) /** - * A [StateFlow] that emits the list of threads in the conversation list. + * A [StateFlow] that emits the list of threads and the typing status of each thread. * * This flow will emit whenever the user asks us to reload the conversation list or * whenever the conversation list changes. */ @Suppress("OPT_IN_USAGE") - val threads: StateFlow?> = merge( - manualReloadTrigger, - appContext.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.length) { - while (true) { - add(reader.next ?: break) + val threads: StateFlow = + combine( + // The conversation list data + 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.length) { + while (true) { + add(reader.next ?: break) + } + } + } } - } - } - } - } + }, + + // The typing status of each thread + ApplicationContext.getInstance(context).typingStatusRepository + .typingThreads + .asFlow() + .onStart { emit(emptySet()) } + .distinctUntilChanged(), + + // The final result that we emit to the UI + ::HomeData + ) .stateIn(viewModelScope, SharingStarted.Eagerly, null) fun tryReload() = manualReloadTrigger.tryEmit(Unit) + data class HomeData( + val threads: List, + val typingThreadIDs: Set + ) + companion object { private const val CHANGE_NOTIFICATION_DEBOUNCE_MILLS = 100L }