From cd938cd380a2aa8997b639d15359f7e8a7f5c394 Mon Sep 17 00:00:00 2001 From: Konstantin Ivanov <54908981+konstantiniiv@users.noreply.github.com> Date: Sat, 24 May 2025 15:21:39 +0200 Subject: [PATCH] DROID-3590 Vault | Space counters, part 1 (#2453) --- .../anytype/di/feature/vault/VaultDI.kt | 2 + .../anytypeio/anytype/ui/vault/VaultScreen.kt | 62 ++++++++++- .../presentation/vault/VaultViewModel.kt | 100 ++++++++++-------- 3 files changed, 119 insertions(+), 45 deletions(-) diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/vault/VaultDI.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/vault/VaultDI.kt index 46cb95e58c..99457479bb 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/feature/vault/VaultDI.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/vault/VaultDI.kt @@ -9,6 +9,7 @@ import com.anytypeio.anytype.domain.account.AwaitAccountStartManager import com.anytypeio.anytype.domain.auth.repo.AuthRepository import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers import com.anytypeio.anytype.domain.block.repo.BlockRepository +import com.anytypeio.anytype.domain.chats.ChatPreviewContainer import com.anytypeio.anytype.domain.config.UserSettingsRepository import com.anytypeio.anytype.domain.debugging.Logger import com.anytypeio.anytype.domain.misc.AppActionManager @@ -81,4 +82,5 @@ interface VaultComponentDependencies : ComponentDependencies { fun logger(): Logger fun awaitAccount(): AwaitAccountStartManager fun profileContainer(): ProfileSubscriptionManager + fun chatPreviewContainer(): ChatPreviewContainer } \ No newline at end of file diff --git a/app/src/main/java/com/anytypeio/anytype/ui/vault/VaultScreen.kt b/app/src/main/java/com/anytypeio/anytype/ui/vault/VaultScreen.kt index f785ba39c1..03fe70173a 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/vault/VaultScreen.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/vault/VaultScreen.kt @@ -72,6 +72,8 @@ import com.anytypeio.anytype.presentation.vault.VaultViewModel.VaultSpaceView import com.anytypeio.anytype.presentation.wallpaper.WallpaperColor import com.bumptech.glide.integration.compose.ExperimentalGlideComposeApi import com.bumptech.glide.integration.compose.GlideImage +import androidx.compose.foundation.layout.Row +import com.anytypeio.anytype.core_ui.views.Caption1Regular @Composable @@ -155,7 +157,9 @@ fun VaultScreen( }, wallpaper = item.wallpaper, onCardClicked = { onSpaceClicked(item) }, - icon = item.icon + icon = item.icon, + unreadMessageCount = item.unreadMessageCount, + unreadMentionCount = item.unreadMentionCount ) } } @@ -271,7 +275,9 @@ fun VaultSpaceCard( subtitle: String, onCardClicked: () -> Unit, icon: SpaceIconView, - wallpaper: Wallpaper + wallpaper: Wallpaper, + unreadMessageCount: Int = 0, + unreadMentionCount: Int = 0 ) { Box( modifier = Modifier @@ -358,6 +364,54 @@ fun VaultSpaceCard( modifier = Modifier.alpha(0.6f) ) } + Row( + modifier = Modifier + .align(Alignment.CenterEnd) + .padding(end = 16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + if (unreadMentionCount > 0) { + Box( + modifier = Modifier + .background( + color = colorResource(R.color.glyph_active), + shape = CircleShape + ) + .size(18.dp), + contentAlignment = Alignment.Center + ) { + Image( + painter = painterResource(R.drawable.ic_chat_widget_mention), + contentDescription = null + ) + } + Spacer(modifier = Modifier.width(8.dp)) + } + + if (unreadMessageCount > 0) { + val shape = if (unreadMentionCount > 9) { + CircleShape + } else { + RoundedCornerShape(100.dp) + } + Box( + modifier = Modifier + .height(18.dp) + .background( + color = colorResource(R.color.glyph_active), + shape = shape + ) + .padding(horizontal = 5.dp), + contentAlignment = Alignment.Center + ) { + Text( + text = unreadMessageCount.toString(), + style = Caption1Regular, + color = colorResource(id = R.color.text_white), + ) + } + } + } } } @@ -476,7 +530,9 @@ fun VaultSpaceCardPreview() { subtitle = "Private space", onCardClicked = {}, wallpaper = Wallpaper.Default, - icon = SpaceIconView.Placeholder() + icon = SpaceIconView.Placeholder(), + unreadMentionCount = 1, + unreadMessageCount = 9 ) } diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/vault/VaultViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/vault/VaultViewModel.kt index 379bf8a35a..5bcfa9997f 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/vault/VaultViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/vault/VaultViewModel.kt @@ -15,6 +15,7 @@ import com.anytypeio.anytype.core_models.multiplayer.SpaceUxType import com.anytypeio.anytype.core_models.primitives.Space import com.anytypeio.anytype.core_models.primitives.SpaceId import com.anytypeio.anytype.domain.base.fold +import com.anytypeio.anytype.domain.chats.ChatPreviewContainer import com.anytypeio.anytype.domain.misc.AppActionManager import com.anytypeio.anytype.domain.misc.DeepLinkResolver import com.anytypeio.anytype.domain.misc.UrlBuilder @@ -47,7 +48,9 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.emitAll +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.flow.stateIn @@ -69,7 +72,8 @@ class VaultViewModel( private val deepLinkToObjectDelegate: DeepLinkToObjectDelegate, private val appActionManager: AppActionManager, private val spaceInviteResolver: SpaceInviteResolver, - private val profileContainer: ProfileSubscriptionManager + private val profileContainer: ProfileSubscriptionManager, + private val chatPreviewContainer: ChatPreviewContainer ) : NavigationViewModel(), DeepLinkToObjectDelegate by deepLinkToObjectDelegate { val spaces = MutableStateFlow>(emptyList()) @@ -90,45 +94,53 @@ class VaultViewModel( Timber.i("VaultViewModel, init") viewModelScope.launch { val wallpapers = getSpaceWallpapers.async(Unit).getOrNull() ?: emptyMap() - spaceViewSubscriptionContainer - .observe() - .take(1) - .onCompletion { - emitAll( - spaceViewSubscriptionContainer - .observe() - .debounce(SPACE_VAULT_DEBOUNCE_DURATION) - ) - } - .combine(observeVaultSettings.flow()) { spaces, settings -> - spaces - .filter { space -> (space.isActive || space.isLoading) } - .distinctBy { it.id } - .map { space -> - VaultSpaceView( - space = space, - icon = space.spaceIcon( - builder = urlBuilder, - spaceGradientProvider = SpaceGradientProvider.Default - ), - wallpaper = wallpapers.getOrDefault( - key = space.targetSpaceId, - defaultValue = Wallpaper.Default - ) - ) - }.sortedBy { space -> - val idx = settings.orderOfSpaces.indexOf( - space.space.id - ) - if (idx == -1) { - Int.MIN_VALUE - } else { - idx - } + combine( + spaceViewSubscriptionContainer + .observe() + .take(1) + .onCompletion { + emitAll( + spaceViewSubscriptionContainer + .observe() + .debounce(SPACE_VAULT_DEBOUNCE_DURATION) + ) + }, + observeVaultSettings.flow(), + chatPreviewContainer.observePreviews() + ) { spaces, settings, chatPreviews -> + spaces + .filter { space -> (space.isActive || space.isLoading) } + .map { space -> + val chatPreview = space.targetSpaceId?.let { spaceId -> + chatPreviews.find { it.space.id == spaceId } } - }.collect { - spaces.value = it - } + + VaultSpaceView( + space = space, + icon = space.spaceIcon( + builder = urlBuilder, + spaceGradientProvider = SpaceGradientProvider.Default + ), + wallpaper = wallpapers.getOrDefault( + key = space.targetSpaceId, + defaultValue = Wallpaper.Default + ), + unreadMessageCount = chatPreview?.state?.unreadMessages?.counter ?: 0, + unreadMentionCount = chatPreview?.state?.unreadMentions?.counter ?: 0 + ) + }.sortedBy { space -> + val idx = settings.orderOfSpaces.indexOf( + space.space.id + ) + if (idx == -1) { + Int.MIN_VALUE + } else { + idx + } + } + }.collect { + spaces.value = it + } } } @@ -353,7 +365,8 @@ class VaultViewModel( private val deepLinkToObjectDelegate: DeepLinkToObjectDelegate, private val appActionManager: AppActionManager, private val spaceInviteResolver: SpaceInviteResolver, - private val profileContainer: ProfileSubscriptionManager + private val profileContainer: ProfileSubscriptionManager, + private val chatPreviewContainer: ChatPreviewContainer ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun create( @@ -372,14 +385,17 @@ class VaultViewModel( deepLinkToObjectDelegate = deepLinkToObjectDelegate, appActionManager = appActionManager, spaceInviteResolver = spaceInviteResolver, - profileContainer = profileContainer + profileContainer = profileContainer, + chatPreviewContainer = chatPreviewContainer ) as T } data class VaultSpaceView( val space: ObjectWrapper.SpaceView, val icon: SpaceIconView, - val wallpaper: Wallpaper = Wallpaper.Default + val wallpaper: Wallpaper = Wallpaper.Default, + val unreadMessageCount: Int = 0, + val unreadMentionCount: Int = 0, ) sealed class Command {