More changes

pull/1493/head
fanchao 1 month ago
parent c1d82cc574
commit 31f4de22cd

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

@ -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<ThreadRecord> = 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<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 =
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)

@ -7,22 +7,22 @@ import org.thoughtcrime.securesms.dependencies.ConfigFactory
import org.thoughtcrime.securesms.util.getConversationUnread
class HomeDiffUtil(
private val old: List<ThreadRecord>,
private val new: List<ThreadRecord>,
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)
)
}

@ -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<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
* whenever the conversation list changes.
*/
@Suppress("OPT_IN_USAGE")
val threads: StateFlow<List<ThreadRecord>?> = 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<HomeData?> =
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<ThreadRecord>,
val typingThreadIDs: Set<Long>
)
companion object {
private const val CHANGE_NOTIFICATION_DEBOUNCE_MILLS = 100L
}

Loading…
Cancel
Save