More changes

pull/1493/head
fanchao 2 months ago
parent c1d82cc574
commit 31f4de22cd

@ -202,10 +202,6 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
binding.createNewPrivateChatButton.setOnClickListener { showNewConversation() } binding.createNewPrivateChatButton.setOnClickListener { showNewConversation() }
IP2Country.configureIfNeeded(this@HomeActivity) IP2Country.configureIfNeeded(this@HomeActivity)
ApplicationContext.getInstance(this@HomeActivity).typingStatusRepository.typingThreads.observe(this) { threadIds ->
homeAdapter.typingThreadIDs = (threadIds ?: setOf())
}
// Set up new conversation button // Set up new conversation button
binding.newConversationButton.setOnClickListener { showNewConversation() } binding.newConversationButton.setOnClickListener { showNewConversation() }
// Observe blocked contacts changed events // Observe blocked contacts changed events

@ -9,7 +9,6 @@ import androidx.recyclerview.widget.ListUpdateCallback
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.NO_ID import androidx.recyclerview.widget.RecyclerView.NO_ID
import network.loki.messenger.R import network.loki.messenger.R
import org.thoughtcrime.securesms.database.model.ThreadRecord
import org.thoughtcrime.securesms.dependencies.ConfigFactory import org.thoughtcrime.securesms.dependencies.ConfigFactory
import org.thoughtcrime.securesms.mms.GlideRequests import org.thoughtcrime.securesms.mms.GlideRequests
@ -26,7 +25,7 @@ class HomeAdapter(
var header: View? = null var header: View? = null
var data: List<ThreadRecord> = emptyList() var data: HomeViewModel.HomeData = HomeViewModel.HomeData(emptyList(), emptySet())
set(newData) { set(newData) {
if (field !== newData) { if (field !== newData) {
val diff = HomeDiffUtil(field, newData, context, configFactory) val diff = HomeDiffUtil(field, newData, context, configFactory)
@ -60,18 +59,10 @@ class HomeAdapter(
override fun getItemId(position: Int): Long { override fun getItemId(position: Int): Long {
if (hasHeaderView() && position == 0) return NO_ID if (hasHeaderView() && position == 0) return NO_ID
val offsetPosition = if (hasHeaderView()) position-1 else position val offsetPosition = if (hasHeaderView()) position-1 else position
return data[offsetPosition].threadId return data.threads[offsetPosition].threadId
} }
lateinit var glide: GlideRequests lateinit var glide: GlideRequests
var typingThreadIDs = setOf<Long>()
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 = override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder =
when (viewType) { when (viewType) {
@ -94,8 +85,8 @@ class HomeAdapter(
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder is ConversationViewHolder) { if (holder is ConversationViewHolder) {
val offset = if (hasHeaderView()) position - 1 else position val offset = if (hasHeaderView()) position - 1 else position
val thread = data[offset] val thread = data.threads[offset]
val isTyping = typingThreadIDs.contains(thread.threadId) val isTyping = data.typingThreadIDs.contains(thread.threadId)
holder.view.bind(thread, isTyping, glide) holder.view.bind(thread, isTyping, glide)
} }
} }
@ -112,7 +103,7 @@ class HomeAdapter(
if (hasHeaderView() && position == 0) HEADER if (hasHeaderView() && position == 0) HEADER
else ITEM 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) class ConversationViewHolder(val view: ConversationView) : RecyclerView.ViewHolder(view)

@ -7,22 +7,22 @@ import org.thoughtcrime.securesms.dependencies.ConfigFactory
import org.thoughtcrime.securesms.util.getConversationUnread import org.thoughtcrime.securesms.util.getConversationUnread
class HomeDiffUtil( class HomeDiffUtil(
private val old: List<ThreadRecord>, private val old: HomeViewModel.HomeData,
private val new: List<ThreadRecord>, private val new: HomeViewModel.HomeData,
private val context: Context, private val context: Context,
private val configFactory: ConfigFactory private val configFactory: ConfigFactory
): DiffUtil.Callback() { ): 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 = 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 { override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val oldItem = old[oldItemPosition] val oldItem = old.threads[oldItemPosition]
val newItem = new[newItemPosition] val newItem = new.threads[newItemPosition]
// return early to save getDisplayBody or expensive calls // return early to save getDisplayBody or expensive calls
var isSameItem = true var isSameItem = true
@ -47,7 +47,8 @@ class HomeDiffUtil(
oldItem.isSent == newItem.isSent && oldItem.isSent == newItem.isSent &&
oldItem.isPending == newItem.isPending && oldItem.isPending == newItem.isPending &&
oldItem.lastSeen == newItem.lastSeen && oldItem.lastSeen == newItem.lastSeen &&
configFactory.convoVolatile?.getConversationUnread(newItem) != true configFactory.convoVolatile?.getConversationUnread(newItem) != true &&
old.typingThreadIDs.contains(oldItem.threadId) == new.typingThreadIDs.contains(newItem.threadId)
) )
} }

@ -1,31 +1,37 @@
package org.thoughtcrime.securesms.home package org.thoughtcrime.securesms.home
import android.content.ContentResolver
import android.content.Context import android.content.Context
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.asFlow
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.database.DatabaseContentProviders import org.thoughtcrime.securesms.database.DatabaseContentProviders
import org.thoughtcrime.securesms.database.ThreadDatabase import org.thoughtcrime.securesms.database.ThreadDatabase
import org.thoughtcrime.securesms.database.model.ThreadRecord import org.thoughtcrime.securesms.database.model.ThreadRecord
import org.thoughtcrime.securesms.util.observeChanges import org.thoughtcrime.securesms.util.observeChanges
import javax.inject.Inject import javax.inject.Inject
import dagger.hilt.android.qualifiers.ApplicationContext as ApplicationContextQualifier
@HiltViewModel @HiltViewModel
class HomeViewModel @Inject constructor( class HomeViewModel @Inject constructor(
private val threadDb: ThreadDatabase, private val threadDb: ThreadDatabase,
@ApplicationContext appContext: Context, contentResolver: ContentResolver,
@ApplicationContextQualifier context: Context,
) : ViewModel() { ) : ViewModel() {
// SharedFlow that emits whenever the user asks us to reload the conversation // SharedFlow that emits whenever the user asks us to reload the conversation
private val manualReloadTrigger = MutableSharedFlow<Unit>( private val manualReloadTrigger = MutableSharedFlow<Unit>(
@ -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 * This flow will emit whenever the user asks us to reload the conversation list or
* whenever the conversation list changes. * whenever the conversation list changes.
*/ */
@Suppress("OPT_IN_USAGE") @Suppress("OPT_IN_USAGE")
val threads: StateFlow<List<ThreadRecord>?> = merge( val threads: StateFlow<HomeData?> =
manualReloadTrigger, combine(
appContext.contentResolver.observeChanges(DatabaseContentProviders.ConversationList.CONTENT_URI)) // The conversation list data
.debounce(CHANGE_NOTIFICATION_DEBOUNCE_MILLS) merge(
.onStart { emit(Unit) } manualReloadTrigger,
.mapLatest { _ -> contentResolver.observeChanges(DatabaseContentProviders.ConversationList.CONTENT_URI))
withContext(Dispatchers.IO) { .debounce(CHANGE_NOTIFICATION_DEBOUNCE_MILLS)
threadDb.approvedConversationList.use { openCursor -> .onStart { emit(Unit) }
val reader = threadDb.readerFor(openCursor) .mapLatest { _ ->
buildList(reader.length) { withContext(Dispatchers.IO) {
while (true) { threadDb.approvedConversationList.use { openCursor ->
add(reader.next ?: break) 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) .stateIn(viewModelScope, SharingStarted.Eagerly, null)
fun tryReload() = manualReloadTrigger.tryEmit(Unit) fun tryReload() = manualReloadTrigger.tryEmit(Unit)
data class HomeData(
val threads: List<ThreadRecord>,
val typingThreadIDs: Set<Long>
)
companion object { companion object {
private const val CHANGE_NOTIFICATION_DEBOUNCE_MILLS = 100L private const val CHANGE_NOTIFICATION_DEBOUNCE_MILLS = 100L
} }

Loading…
Cancel
Save