|
|
|
@ -2,11 +2,13 @@ package org.thoughtcrime.securesms.home
|
|
|
|
|
|
|
|
|
|
import android.content.ContentResolver
|
|
|
|
|
import android.content.Context
|
|
|
|
|
import android.util.Log
|
|
|
|
|
import androidx.lifecycle.ViewModel
|
|
|
|
|
import androidx.lifecycle.asFlow
|
|
|
|
|
import androidx.lifecycle.viewModelScope
|
|
|
|
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
|
|
|
|
import kotlinx.coroutines.Dispatchers
|
|
|
|
|
import kotlinx.coroutines.FlowPreview
|
|
|
|
|
import kotlinx.coroutines.channels.BufferOverflow
|
|
|
|
|
import kotlinx.coroutines.flow.Flow
|
|
|
|
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
|
|
|
@ -20,6 +22,7 @@ import kotlinx.coroutines.flow.flowOn
|
|
|
|
|
import kotlinx.coroutines.flow.map
|
|
|
|
|
import kotlinx.coroutines.flow.mapLatest
|
|
|
|
|
import kotlinx.coroutines.flow.merge
|
|
|
|
|
import kotlinx.coroutines.flow.onEach
|
|
|
|
|
import kotlinx.coroutines.flow.onStart
|
|
|
|
|
import kotlinx.coroutines.flow.stateIn
|
|
|
|
|
import org.session.libsession.utilities.TextSecurePreferences
|
|
|
|
@ -27,7 +30,9 @@ 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.DateUtils
|
|
|
|
|
import org.thoughtcrime.securesms.util.observeChanges
|
|
|
|
|
import java.util.Locale
|
|
|
|
|
import javax.inject.Inject
|
|
|
|
|
import dagger.hilt.android.qualifiers.ApplicationContext as ApplicationContextQualifier
|
|
|
|
|
|
|
|
|
@ -52,13 +57,13 @@ class HomeViewModel @Inject constructor(
|
|
|
|
|
*/
|
|
|
|
|
val data: StateFlow<Data?> = combine(
|
|
|
|
|
observeConversationList(),
|
|
|
|
|
unapprovedConversationCount(),
|
|
|
|
|
hasHiddenMessageRequestsFlow(),
|
|
|
|
|
observeTypingStatus(),
|
|
|
|
|
messageRequests(),
|
|
|
|
|
::Data
|
|
|
|
|
).stateIn(viewModelScope, SharingStarted.Eagerly, null)
|
|
|
|
|
)
|
|
|
|
|
.stateIn(viewModelScope, SharingStarted.Eagerly, null)
|
|
|
|
|
|
|
|
|
|
private fun hasHiddenMessageRequestsFlow() = TextSecurePreferences.events
|
|
|
|
|
private fun hasHiddenMessageRequests() = TextSecurePreferences.events
|
|
|
|
|
.filter { it == TextSecurePreferences.HAS_HIDDEN_MESSAGE_REQUESTS }
|
|
|
|
|
.map { prefs.hasHiddenMessageRequests() }
|
|
|
|
|
.onStart { emit(prefs.hasHiddenMessageRequests()) }
|
|
|
|
@ -70,40 +75,55 @@ class HomeViewModel @Inject constructor(
|
|
|
|
|
.onStart { emit(emptySet()) }
|
|
|
|
|
.distinctUntilChanged()
|
|
|
|
|
|
|
|
|
|
private fun unapprovedConversationCount() =
|
|
|
|
|
contentResolver.observeChanges(DatabaseContentProviders.ConversationList.CONTENT_URI)
|
|
|
|
|
.flowOn(Dispatchers.IO)
|
|
|
|
|
private fun messageRequests() = combine(
|
|
|
|
|
unapprovedConversationCount(),
|
|
|
|
|
hasHiddenMessageRequests(),
|
|
|
|
|
latestUnapprovedConversationTimestamp(),
|
|
|
|
|
::createMessageRequests
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
private fun unapprovedConversationCount() = reloadTriggersAndContentChanges()
|
|
|
|
|
.map { threadDb.unapprovedConversationCount }
|
|
|
|
|
.onStart { emit(threadDb.unapprovedConversationCount) }
|
|
|
|
|
|
|
|
|
|
private fun latestUnapprovedConversationTimestamp() = reloadTriggersAndContentChanges()
|
|
|
|
|
.map { threadDb.latestUnapprovedConversationTimestamp }
|
|
|
|
|
|
|
|
|
|
@Suppress("OPT_IN_USAGE")
|
|
|
|
|
private fun observeConversationList(): Flow<List<ThreadRecord>> = merge(
|
|
|
|
|
private fun observeConversationList(): Flow<List<ThreadRecord>> = reloadTriggersAndContentChanges()
|
|
|
|
|
.mapLatest { _ ->
|
|
|
|
|
threadDb.approvedConversationList.use { openCursor ->
|
|
|
|
|
threadDb.readerFor(openCursor).run { generateSequence { next }.toList() }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@OptIn(FlowPreview::class)
|
|
|
|
|
private fun reloadTriggersAndContentChanges() = merge(
|
|
|
|
|
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<ThreadRecord>,
|
|
|
|
|
val unapprovedConversationCount: Int,
|
|
|
|
|
val hasHiddenMessageRequests: Boolean,
|
|
|
|
|
val typingThreadIDs: Set<Long>
|
|
|
|
|
val threads: List<ThreadRecord> = emptyList(),
|
|
|
|
|
val typingThreadIDs: Set<Long> = emptySet(),
|
|
|
|
|
val messageRequests: MessageRequests? = null
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
fun createMessageRequests(
|
|
|
|
|
count: Int,
|
|
|
|
|
hidden: Boolean,
|
|
|
|
|
timestamp: Long
|
|
|
|
|
) = if (count > 0 && !hidden) MessageRequests(
|
|
|
|
|
count.toString(),
|
|
|
|
|
DateUtils.getDisplayFormattedTimeSpanString(context, Locale.getDefault(), timestamp)
|
|
|
|
|
) else null
|
|
|
|
|
|
|
|
|
|
data class MessageRequests(val count: String, val timestamp: String)
|
|
|
|
|
|
|
|
|
|
companion object {
|
|
|
|
|
private const val CHANGE_NOTIFICATION_DEBOUNCE_MILLS = 100L
|
|
|
|
|
}
|
|
|
|
|