Skip to content

Commit

Permalink
feat: unread archived conversations indicator [WPB-4437] (#2288)
Browse files Browse the repository at this point in the history
  • Loading branch information
Garzas authored Oct 2, 2023
1 parent 952180c commit 7884f76
Show file tree
Hide file tree
Showing 9 changed files with 229 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ package com.wire.android.di.accountScoped

import com.wire.android.di.CurrentAccount
import com.wire.android.di.KaliumCoreLogic
import dagger.Module
import com.wire.kalium.logic.CoreLogic
import com.wire.kalium.logic.data.user.UserId
import com.wire.kalium.logic.feature.conversation.AddMemberToConversationUseCase
Expand All @@ -32,6 +31,7 @@ import com.wire.kalium.logic.feature.conversation.GetOneToOneConversationUseCase
import com.wire.kalium.logic.feature.conversation.GetOrCreateOneToOneConversationUseCase
import com.wire.kalium.logic.feature.conversation.JoinConversationViaCodeUseCase
import com.wire.kalium.logic.feature.conversation.LeaveConversationUseCase
import com.wire.kalium.logic.feature.conversation.ObserveArchivedUnreadConversationsCountUseCase
import com.wire.kalium.logic.feature.conversation.ObserveConversationDetailsUseCase
import com.wire.kalium.logic.feature.conversation.ObserveConversationInteractionAvailabilityUseCase
import com.wire.kalium.logic.feature.conversation.ObserveConversationListDetailsUseCase
Expand All @@ -53,6 +53,7 @@ import com.wire.kalium.logic.feature.conversation.guestroomlink.ObserveGuestRoom
import com.wire.kalium.logic.feature.conversation.guestroomlink.RevokeGuestRoomLinkUseCase
import com.wire.kalium.logic.feature.conversation.messagetimer.UpdateMessageTimerUseCase
import com.wire.kalium.logic.feature.team.DeleteTeamConversationUseCase
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ViewModelComponent
Expand Down Expand Up @@ -228,4 +229,10 @@ class ConversationModule {
@Provides
fun provideUpdateConversationMemberRoleUseCase(conversationScope: ConversationScope): UpdateConversationMemberRoleUseCase =
conversationScope.updateConversationMemberRole

@ViewModelScoped
@Provides
fun provideObserveArchivedUnreadConversationsCountUseCase(
conversationScope: ConversationScope
): ObserveArchivedUnreadConversationsCountUseCase = conversationScope.observeArchivedUnreadConversationsCount
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ object HorizontalSpace {
Spacer(Modifier.width(dimensions().spacing8x))
}

@Composable
fun x12() {
Spacer(Modifier.width(dimensions().spacing12x))
}

@Composable
fun x16() {
Spacer(Modifier.width(dimensions().spacing16x))
Expand Down
7 changes: 7 additions & 0 deletions app/src/main/kotlin/com/wire/android/ui/home/HomeScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ import com.wire.android.ui.home.conversations.details.GroupConversationActionTyp
import com.wire.android.ui.home.conversations.details.GroupConversationDetailsNavBackArgs
import com.wire.android.ui.home.conversationslist.ConversationListState
import com.wire.android.ui.home.conversationslist.ConversationListViewModel
import com.wire.android.ui.home.drawer.HomeDrawer
import com.wire.android.ui.home.drawer.HomeDrawerState
import com.wire.android.ui.home.drawer.HomeDrawerViewModel
import com.wire.android.util.permission.rememberRequestPushNotificationsPermissionFlow
import kotlinx.coroutines.launch

Expand All @@ -94,6 +97,7 @@ import kotlinx.coroutines.launch
fun HomeScreen(
navigator: Navigator,
homeViewModel: HomeViewModel = hiltViewModel(),
homeDrawerViewModel: HomeDrawerViewModel = hiltViewModel(),
conversationListViewModel: ConversationListViewModel = hiltViewModel(), // TODO: move required elements from this one to HomeViewModel?,
groupDetailsScreenResultRecipient: ResultRecipient<ConversationScreenDestination, GroupConversationDetailsNavBackArgs>,
otherUserProfileScreenResultRecipient: ResultRecipient<OtherUserProfileScreenDestination, String>,
Expand Down Expand Up @@ -122,6 +126,7 @@ fun HomeScreen(

HomeContent(
homeState = homeState,
homeDrawerState = homeDrawerViewModel.drawerState,
homeStateHolder = homeScreenState,
conversationListState = conversationListViewModel.conversationListState,
onNewConversationClick = { navigator.navigate(NavigationCommand(NewConversationSearchPeopleScreenDestination)) },
Expand Down Expand Up @@ -175,6 +180,7 @@ fun HomeScreen(
@Composable
fun HomeContent(
homeState: HomeState,
homeDrawerState: HomeDrawerState,
homeStateHolder: HomeStateHolder,
conversationListState: ConversationListState,
onNewConversationClick: () -> Unit,
Expand Down Expand Up @@ -212,6 +218,7 @@ fun HomeContent(
) {
HomeDrawer(
currentRoute = currentNavigationItem.direction.route,
homeDrawerState = homeDrawerState,
navigateToHomeItem = ::openHomeDestination,
onCloseDrawer = ::closeDrawer
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ fun ConnectPendingRequestBadge(modifier: Modifier = Modifier) {
}

@Composable
private fun UnreadMessageEventBadge(modifier: Modifier = Modifier, unreadMessageCount: Int) {
fun UnreadMessageEventBadge(modifier: Modifier = Modifier, unreadMessageCount: Int) {
if (unreadMessageCount > 0) {
NotificationBadgeContainer(
modifier = modifier,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,13 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*
*
*/

package com.wire.android.ui.home
package com.wire.android.ui.home.drawer

import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
Expand All @@ -40,25 +39,27 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.wire.android.navigation.HomeDestination
import com.wire.android.ui.common.Logo
import com.wire.android.ui.common.dimensions
import com.wire.android.ui.common.selectableBackground
import com.wire.android.ui.common.spacers.HorizontalSpace
import com.wire.android.ui.home.conversationslist.common.UnreadMessageEventBadge
import com.wire.android.ui.theme.wireDimensions
import com.wire.android.ui.theme.wireTypography
import com.wire.android.util.ui.PreviewMultipleThemes

@Composable
fun HomeDrawer(
homeDrawerState: HomeDrawerState,
currentRoute: String?,
navigateToHomeItem: (HomeDestination) -> Unit,
onCloseDrawer: () -> Unit,
) {
val context = LocalContext.current

Column(
modifier = Modifier
.padding(
Expand All @@ -83,19 +84,18 @@ fun HomeDrawer(
onCloseDrawer()
}

val topItems = listOf(
HomeDestination.Conversations,
HomeDestination.Archive
DrawerItem(
destination = HomeDestination.Conversations,
selected = currentRoute == HomeDestination.Conversations.direction.route,
onItemClick = remember { { navigateAndCloseDrawer(HomeDestination.Conversations) } }
)

DrawerItem(
destination = HomeDestination.Archive,
unreadCount = homeDrawerState.unreadArchiveConversationsCount,
selected = currentRoute == HomeDestination.Archive.direction.route,
onItemClick = remember { { navigateAndCloseDrawer(HomeDestination.Archive) } }
)
// TODO: Re-enable once we have Archive & Vault
// listOf(HomeDestination.Conversations, HomeDestination.Archive, HomeDestination.Vault)
topItems.forEach { item ->
DrawerItem(
destination = item,
selected = currentRoute == item.direction.route,
onItemClick = remember { { navigateAndCloseDrawer(item) } }
)
}

Spacer(modifier = Modifier.weight(1f))

Expand All @@ -111,7 +111,7 @@ fun HomeDrawer(
}

@Composable
fun DrawerItem(destination: HomeDestination, selected: Boolean, onItemClick: () -> Unit) {
fun DrawerItem(destination: HomeDestination, selected: Boolean, unreadCount: Int = 0, onItemClick: () -> Unit) {
val backgroundColor = if (selected) MaterialTheme.colorScheme.primary else Color.Transparent
val contentColor = if (selected) MaterialTheme.colorScheme.onPrimary else MaterialTheme.colorScheme.onBackground
Row(
Expand All @@ -134,9 +134,39 @@ fun DrawerItem(destination: HomeDestination, selected: Boolean, onItemClick: ()
Text(
style = MaterialTheme.wireTypography.button02,
text = stringResource(id = destination.title),
textAlign = TextAlign.Start,
color = contentColor,
modifier = Modifier
.align(Alignment.CenterVertically)
.weight(1F)
)
UnreadMessageEventBadge(unreadMessageCount = unreadCount)
HorizontalSpace.x12()
}
}

@PreviewMultipleThemes
@Composable
fun PreviewSelectedArchivedItemWithUnreadCount() {
Box(modifier = Modifier.background(color = MaterialTheme.colorScheme.surface)) {
DrawerItem(
destination = HomeDestination.Archive,
selected = true,
unreadCount = 100,
{}
)
}
}

@PreviewMultipleThemes
@Composable
fun PreviewUnSelectedArchivedItemWithUnreadCount() {
Box(modifier = Modifier.background(color = MaterialTheme.colorScheme.surface)) {
DrawerItem(
destination = HomeDestination.Archive,
selected = false,
unreadCount = 100,
{}
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Wire
* Copyright (C) 2023 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*
*
*/

package com.wire.android.ui.home.drawer

data class HomeDrawerState(val unreadArchiveConversationsCount: Int)
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Wire
* Copyright (C) 2023 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*
*
*/

package com.wire.android.ui.home.drawer

import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import com.wire.android.navigation.SavedStateViewModel
import com.wire.kalium.logic.feature.conversation.ObserveArchivedUnreadConversationsCountUseCase
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import javax.inject.Inject

@Suppress("LongParameterList")
@HiltViewModel
class HomeDrawerViewModel @Inject constructor(
override val savedStateHandle: SavedStateHandle,
private val observeArchivedUnreadConversationsCountUseCase: ObserveArchivedUnreadConversationsCountUseCase,
) : SavedStateViewModel(savedStateHandle) {

var drawerState by mutableStateOf(
HomeDrawerState(
unreadArchiveConversationsCount = 0
)
)
private set

init {
observeUnreadArchiveConversationsCount()
}

private fun observeUnreadArchiveConversationsCount() {
viewModelScope.launch {
observeArchivedUnreadConversationsCountUseCase()
.collect { drawerState = drawerState.copy(unreadArchiveConversationsCount = it.toInt()) }
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* Wire
* Copyright (C) 2023 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
package com.wire.android.ui.home.drawer

import androidx.lifecycle.SavedStateHandle
import com.wire.android.config.CoroutineTestExtension
import com.wire.android.config.NavigationTestExtension
import com.wire.kalium.logic.feature.conversation.ObserveArchivedUnreadConversationsCountUseCase
import io.mockk.MockKAnnotations
import io.mockk.coEvery
import io.mockk.impl.annotations.MockK
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.consumeAsFlow
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
import org.amshove.kluent.internal.assertEquals
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith

@ExtendWith(CoroutineTestExtension::class)
@ExtendWith(NavigationTestExtension::class)
@OptIn(ExperimentalCoroutinesApi::class)
@ExtendWith(NavigationTestExtension::class)
class HomeDrawerViewModelTest {

@Test
fun `given archivedUnreadConversationsCount, when starts observing, then returns correct integer value`() = runTest {
// Given
val unreadCount = 10L
val (arrangement, viewModel) = Arrangement()
.arrange()

// When
arrangement.unreadArchivedConversationsCountChannel.send(unreadCount)
advanceUntilIdle()

// Then
assertEquals(unreadCount.toInt(), viewModel.drawerState.unreadArchiveConversationsCount)
}

private class Arrangement {

@MockK
lateinit var savedStateHandle: SavedStateHandle

@MockK
lateinit var observeArchivedUnreadConversationsCount: ObserveArchivedUnreadConversationsCountUseCase

val unreadArchivedConversationsCountChannel = Channel<Long>(capacity = Channel.UNLIMITED)

init {
MockKAnnotations.init(this, relaxUnitFun = true)
coEvery { observeArchivedUnreadConversationsCount() } returns unreadArchivedConversationsCountChannel.consumeAsFlow()
}

fun arrange() = this to HomeDrawerViewModel(
savedStateHandle,
observeArchivedUnreadConversationsCount
)
}
}

0 comments on commit 7884f76

Please sign in to comment.