feat: separate unread items (#233); closes #216

This commit is contained in:
Diego Beraldin 2023-12-06 21:21:34 +01:00 committed by GitHub
parent d153083819
commit dfbc3eea3b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 159 additions and 93 deletions

View File

@ -1,13 +1,50 @@
package com.github.diegoberaldin.raccoonforlemmy.feature.inbox
import com.github.diegoberaldin.raccoonforlemmy.domain.identity.repository.IdentityRepository
import com.github.diegoberaldin.raccoonforlemmy.feature.inbox.main.InboxMviModel
import com.github.diegoberaldin.raccoonforlemmy.feature.inbox.usecase.GetUnreadItemsUseCase
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
internal class DefaultInboxCoordinator : InboxCoordinator {
internal class DefaultInboxCoordinator(
private val identityRepository: IdentityRepository,
private val getUnreadItemsUseCase: GetUnreadItemsUseCase,
) : InboxCoordinator {
private val scope = CoroutineScope(SupervisorJob())
override val unreadOnly = MutableStateFlow(true)
override val effects = MutableSharedFlow<InboxMviModel.Effect>()
override val unreadReplies = MutableStateFlow(0)
override val unreadMentions = MutableStateFlow(0)
override val unreadMessages = MutableStateFlow(0)
override val totalUnread = combine(
unreadMentions,
unreadMessages,
unreadReplies,
) { res1, res2, res3 ->
res1 + res2 + res3
}.stateIn(
scope = scope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = 0
)
init {
scope.launch {
identityRepository.isLogged.onEach {
updateCounters()
}.launchIn(this)
}
}
override fun setUnreadOnly(value: Boolean) {
unreadOnly.value = value
@ -16,4 +53,21 @@ internal class DefaultInboxCoordinator : InboxCoordinator {
override suspend fun emitEffect(effect: InboxMviModel.Effect) {
effects.emit(effect)
}
override suspend fun updateUnreadCount(): Int {
updateCounters()
return totalUnread.value
}
private suspend fun updateCounters() {
if (!identityRepository.authToken.value.isNullOrEmpty()) {
unreadMentions.value = getUnreadItemsUseCase.getUnreadMentions()
unreadReplies.value = getUnreadItemsUseCase.getUnreadReplies()
unreadMessages.value = getUnreadItemsUseCase.getUnreadMessages()
} else {
unreadReplies.value = 0
unreadMentions.value = 0
unreadMessages.value = 0
}
}
}

View File

@ -7,8 +7,14 @@ import kotlinx.coroutines.flow.StateFlow
interface InboxCoordinator {
val unreadOnly: StateFlow<Boolean>
val effects: SharedFlow<InboxMviModel.Effect>
val unreadReplies: StateFlow<Int>
val unreadMentions: StateFlow<Int>
val unreadMessages: StateFlow<Int>
val totalUnread: StateFlow<Int>
fun setUnreadOnly(value: Boolean)
suspend fun emitEffect(effect: InboxMviModel.Effect)
suspend fun updateUnreadCount(): Int
}

View File

@ -11,11 +11,23 @@ import com.github.diegoberaldin.raccoonforlemmy.feature.inbox.messages.InboxMess
import com.github.diegoberaldin.raccoonforlemmy.feature.inbox.messages.InboxMessagesViewModel
import com.github.diegoberaldin.raccoonforlemmy.feature.inbox.replies.InboxRepliesMviModel
import com.github.diegoberaldin.raccoonforlemmy.feature.inbox.replies.InboxRepliesViewModel
import com.github.diegoberaldin.raccoonforlemmy.feature.inbox.usecase.DefaultGetUnreadItemsUseCase
import com.github.diegoberaldin.raccoonforlemmy.feature.inbox.usecase.GetUnreadItemsUseCase
import org.koin.dsl.module
val inboxTabModule = module {
single<InboxCoordinator> {
DefaultInboxCoordinator()
DefaultInboxCoordinator(
identityRepository = get(),
getUnreadItemsUseCase = get(),
)
}
single<GetUnreadItemsUseCase> {
DefaultGetUnreadItemsUseCase(
identityRepository = get(),
userRepository = get(),
messageRepository = get(),
)
}
factory<InboxMviModel> {
InboxViewModel(
@ -24,7 +36,6 @@ val inboxTabModule = module {
userRepository = get(),
coordinator = get(),
settingsRepository = get(),
contentResetCoordinator = get(),
)
}
factory<InboxRepliesMviModel> {
@ -34,7 +45,6 @@ val inboxTabModule = module {
identityRepository = get(),
siteRepository = get(),
commentRepository = get(),
messageRepository = get(),
themeRepository = get(),
settingsRepository = get(),
hapticFeedback = get(),
@ -48,7 +58,6 @@ val inboxTabModule = module {
userRepository = get(),
identityRepository = get(),
commentRepository = get(),
messageRepository = get(),
themeRepository = get(),
settingsRepository = get(),
hapticFeedback = get(),
@ -64,7 +73,6 @@ val inboxTabModule = module {
messageRepository = get(),
coordinator = get(),
notificationCenter = get(),
userRepository = get(),
settingsRepository = get(),
)
}

View File

@ -16,6 +16,9 @@ interface InboxMviModel :
val isLogged: Boolean? = null,
val section: InboxSection = InboxSection.Replies,
val unreadOnly: Boolean = true,
val unreadReplies: Int = 0,
val unreadMentions: Int = 0,
val unreadMessages: Int = 0,
)
sealed interface Effect {

View File

@ -173,9 +173,30 @@ object InboxScreen : Tab {
SectionSelector(
modifier = Modifier.padding(vertical = Spacing.s),
titles = listOf(
stringResource(MR.strings.inbox_section_replies),
stringResource(MR.strings.inbox_section_mentions),
stringResource(MR.strings.inbox_section_messages),
buildString {
append(stringResource(MR.strings.inbox_section_replies))
if (uiState.unreadReplies > 0) {
append(" (")
append(uiState.unreadReplies)
append(")")
}
},
buildString {
append(stringResource(MR.strings.inbox_section_mentions))
if (uiState.unreadMentions > 0) {
append(" (")
append(uiState.unreadMentions)
append(")")
}
},
buildString {
append(stringResource(MR.strings.inbox_section_messages))
if (uiState.unreadMessages > 0) {
append(" (")
append(uiState.unreadMessages)
append(")")
}
},
),
currentSection = when (uiState.section) {
InboxSection.Mentions -> 1

View File

@ -2,7 +2,6 @@ package com.github.diegoberaldin.raccoonforlemmy.feature.inbox.main
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.DefaultMviModel
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.MviModel
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.ContentResetCoordinator
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.SettingsRepository
import com.github.diegoberaldin.raccoonforlemmy.core.utils.toInboxUnreadOnly
import com.github.diegoberaldin.raccoonforlemmy.domain.identity.repository.IdentityRepository
@ -20,16 +19,26 @@ class InboxViewModel(
private val userRepository: UserRepository,
private val coordinator: InboxCoordinator,
private val settingsRepository: SettingsRepository,
private val contentResetCoordinator: ContentResetCoordinator,
) : InboxMviModel,
MviModel<InboxMviModel.Intent, InboxMviModel.UiState, InboxMviModel.Effect> by mvi {
override fun onStarted() {
mvi.onStarted()
mvi.scope?.launch {
mvi.scope?.launch(Dispatchers.IO) {
identityRepository.isLogged.onEach { logged ->
mvi.updateState { it.copy(isLogged = logged) }
}.launchIn(this)
coordinator.unreadMentions.onEach { value ->
mvi.updateState { it.copy(unreadMentions = value) }
}.launchIn(this)
coordinator.unreadReplies.onEach { value ->
mvi.updateState { it.copy(unreadReplies = value) }
}.launchIn(this)
coordinator.unreadMessages.onEach { value ->
mvi.updateState { it.copy(unreadMessages = value) }
}.launchIn(this)
val settingsUnreadOnly =
settingsRepository.currentSettings.value.defaultInboxType.toInboxUnreadOnly()
if (uiState.value.unreadOnly != settingsUnreadOnly) {

View File

@ -11,7 +11,6 @@ import com.github.diegoberaldin.raccoonforlemmy.domain.identity.repository.Ident
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.PersonMentionModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.SortType
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.CommentRepository
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.PrivateMessageRepository
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.UserRepository
import com.github.diegoberaldin.raccoonforlemmy.feature.inbox.InboxCoordinator
import com.github.diegoberaldin.raccoonforlemmy.feature.inbox.main.InboxMviModel
@ -28,7 +27,6 @@ class InboxMentionsViewModel(
private val commentRepository: CommentRepository,
private val themeRepository: ThemeRepository,
private val settingsRepository: SettingsRepository,
private val messageRepository: PrivateMessageRepository,
private val hapticFeedback: HapticFeedback,
private val coordinator: InboxCoordinator,
private val notificationCenter: NotificationCenter,
@ -296,21 +294,7 @@ class InboxMentionsViewModel(
private fun updateUnreadItems() {
mvi.scope?.launch(Dispatchers.IO) {
val auth = identityRepository.authToken.value
val unreadCount = if (!auth.isNullOrEmpty()) {
val mentionCount =
userRepository.getMentions(auth, page = 1, limit = 50).orEmpty().count()
val replyCount =
userRepository.getReplies(auth, page = 1, limit = 50).orEmpty().count()
val messageCount =
messageRepository.getAll(auth, page = 1, limit = 50).orEmpty().groupBy {
listOf(it.creator?.id ?: 0, it.recipient?.id ?: 0).sorted()
.joinToString()
}.count()
mentionCount + replyCount
} else {
0
}
val unreadCount = coordinator.updateUnreadCount()
mvi.emitEffect(InboxMentionsMviModel.Effect.UpdateUnreadItems(unreadCount))
}
}

View File

@ -9,7 +9,6 @@ import com.github.diegoberaldin.raccoonforlemmy.domain.identity.repository.Ident
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.otherUser
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.PrivateMessageRepository
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.SiteRepository
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.UserRepository
import com.github.diegoberaldin.raccoonforlemmy.feature.inbox.InboxCoordinator
import com.github.diegoberaldin.raccoonforlemmy.feature.inbox.main.InboxMviModel
import kotlinx.coroutines.Dispatchers
@ -23,7 +22,6 @@ class InboxMessagesViewModel(
private val identityRepository: IdentityRepository,
private val siteRepository: SiteRepository,
private val messageRepository: PrivateMessageRepository,
private val userRepository: UserRepository,
private val settingsRepository: SettingsRepository,
private val coordinator: InboxCoordinator,
private val notificationCenter: NotificationCenter,
@ -62,6 +60,8 @@ class InboxMessagesViewModel(
changeUnreadOnly(value)
}
}
updateUnreadItems()
}
}
@ -149,21 +149,7 @@ class InboxMessagesViewModel(
private fun updateUnreadItems() {
mvi.scope?.launch(Dispatchers.IO) {
val auth = identityRepository.authToken.value
val unreadCount = if (!auth.isNullOrEmpty()) {
val mentionCount =
userRepository.getMentions(auth, page = 1, limit = 50).orEmpty().count()
val replyCount =
userRepository.getReplies(auth, page = 1, limit = 50).orEmpty().count()
val messageCount =
messageRepository.getAll(auth, page = 1, limit = 50).orEmpty().groupBy {
listOf(it.creator?.id ?: 0, it.recipient?.id ?: 0).sorted()
.joinToString()
}.count()
mentionCount + replyCount + messageCount
} else {
0
}
val unreadCount = coordinator.updateUnreadCount()
mvi.emitEffect(InboxMessagesMviModel.Effect.UpdateUnreadItems(unreadCount))
}
}

View File

@ -11,7 +11,6 @@ import com.github.diegoberaldin.raccoonforlemmy.domain.identity.repository.Ident
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.PersonMentionModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.SortType
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.CommentRepository
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.PrivateMessageRepository
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.SiteRepository
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.UserRepository
import com.github.diegoberaldin.raccoonforlemmy.feature.inbox.InboxCoordinator
@ -28,7 +27,6 @@ class InboxRepliesViewModel(
private val userRepository: UserRepository,
private val siteRepository: SiteRepository,
private val commentRepository: CommentRepository,
private val messageRepository: PrivateMessageRepository,
private val themeRepository: ThemeRepository,
private val hapticFeedback: HapticFeedback,
private val coordinator: InboxCoordinator,
@ -302,21 +300,7 @@ class InboxRepliesViewModel(
}
private suspend fun updateUnreadItems() {
val auth = identityRepository.authToken.value
val unreadCount = if (!auth.isNullOrEmpty()) {
val mentionCount =
userRepository.getMentions(auth, page = 1, limit = 50).orEmpty().count()
val replyCount =
userRepository.getReplies(auth, page = 1, limit = 50).orEmpty().count()
val messageCount =
messageRepository.getAll(auth, page = 1, limit = 50).orEmpty().groupBy {
listOf(it.creator?.id ?: 0, it.recipient?.id ?: 0).sorted()
.joinToString()
}.count()
mentionCount + replyCount
} else {
0
}
val unreadCount = coordinator.updateUnreadCount()
mvi.emitEffect(InboxRepliesMviModel.Effect.UpdateUnreadItems(unreadCount))
}

View File

@ -0,0 +1,29 @@
package com.github.diegoberaldin.raccoonforlemmy.feature.inbox.usecase
import com.github.diegoberaldin.raccoonforlemmy.domain.identity.repository.IdentityRepository
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.PrivateMessageRepository
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.UserRepository
class DefaultGetUnreadItemsUseCase(
private val identityRepository: IdentityRepository,
private val userRepository: UserRepository,
private val messageRepository: PrivateMessageRepository,
) : GetUnreadItemsUseCase {
override suspend fun getUnreadReplies(): Int {
val auth = identityRepository.authToken.value
return userRepository.getReplies(auth, page = 1, limit = 50).orEmpty().count()
}
override suspend fun getUnreadMentions(): Int {
val auth = identityRepository.authToken.value
return userRepository.getMentions(auth, page = 1, limit = 50).orEmpty().count()
}
override suspend fun getUnreadMessages(): Int {
val auth = identityRepository.authToken.value
return messageRepository.getAll(auth, page = 1, limit = 50).orEmpty().groupBy {
listOf(it.creator?.id ?: 0, it.recipient?.id ?: 0).sorted()
.joinToString()
}.count()
}
}

View File

@ -0,0 +1,7 @@
package com.github.diegoberaldin.raccoonforlemmy.feature.inbox.usecase
interface GetUnreadItemsUseCase {
suspend fun getUnreadReplies(): Int
suspend fun getUnreadMentions(): Int
suspend fun getUnreadMessages(): Int
}

View File

@ -2,20 +2,15 @@ package com.github.diegoberaldin.raccoonforlemmy
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.DefaultMviModel
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.MviModel
import com.github.diegoberaldin.raccoonforlemmy.domain.identity.repository.IdentityRepository
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.PrivateMessageRepository
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.UserRepository
import com.github.diegoberaldin.raccoonforlemmy.feature.inbox.InboxCoordinator
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.IO
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
class MainViewModel(
private val mvi: DefaultMviModel<MainScreenMviModel.Intent, MainScreenMviModel.UiState, MainScreenMviModel.Effect>,
private val identityRepository: IdentityRepository,
private val userRepository: UserRepository,
private val messageRepository: PrivateMessageRepository,
private val inboxCoordinator: InboxCoordinator,
) : MainScreenMviModel,
MviModel<MainScreenMviModel.Intent, MainScreenMviModel.UiState, MainScreenMviModel.Effect> by mvi {
@ -23,27 +18,9 @@ class MainViewModel(
mvi.onStarted()
mvi.scope?.launch(Dispatchers.IO) {
launch {
identityRepository.isLogged.onEach { logged ->
val unreadCount = if (logged == true) {
val auth = identityRepository.authToken.value
val mentionCount =
userRepository.getMentions(auth, page = 1, limit = 50).orEmpty().count()
val replyCount =
userRepository.getReplies(auth, page = 1, limit = 50).orEmpty().count()
val messageCount =
messageRepository.getAll(auth, page = 1, limit = 50).orEmpty().groupBy {
listOf(it.creator?.id ?: 0, it.recipient?.id ?: 0).sorted()
.joinToString()
}.count()
mentionCount + replyCount + messageCount
} else {
0
}
mvi.emitEffect(MainScreenMviModel.Effect.UnreadItemsDetected(unreadCount))
}.launchIn(this)
inboxCoordinator.totalUnread.onEach { unreadCount ->
mvi.emitEffect(MainScreenMviModel.Effect.UnreadItemsDetected(unreadCount))
}
}
}

View File

@ -9,9 +9,7 @@ internal val internalSharedModule = module {
factory<MainScreenMviModel> {
MainViewModel(
mvi = DefaultMviModel(MainScreenMviModel.UiState()),
identityRepository = get(),
userRepository = get(),
messageRepository = get(),
inboxCoordinator = get(),
)
}
}