diff --git a/app/src/main/kotlin/app/dapk/st/graph/AppModule.kt b/app/src/main/kotlin/app/dapk/st/graph/AppModule.kt index ff26c53..d8874d9 100644 --- a/app/src/main/kotlin/app/dapk/st/graph/AppModule.kt +++ b/app/src/main/kotlin/app/dapk/st/graph/AppModule.kt @@ -181,7 +181,7 @@ internal class FeatureModules internal constructor( clock ) } - val homeModule by unsafeLazy { HomeModule(storeModule.value, matrixModules.profile, buildMeta) } + val homeModule by unsafeLazy { HomeModule(storeModule.value, matrixModules.profile, matrixModules.sync, buildMeta) } val settingsModule by unsafeLazy { SettingsModule( storeModule.value, diff --git a/features/home/build.gradle b/features/home/build.gradle index 5507d8c..3265a1f 100644 --- a/features/home/build.gradle +++ b/features/home/build.gradle @@ -3,6 +3,7 @@ applyAndroidComposeLibraryModule(project) dependencies { implementation project(":matrix:services:profile") implementation project(":matrix:services:crypto") + implementation project(":matrix:services:sync") implementation project(":features:directory") implementation project(":features:login") implementation project(":features:settings") diff --git a/features/home/src/main/kotlin/app/dapk/st/home/HomeModule.kt b/features/home/src/main/kotlin/app/dapk/st/home/HomeModule.kt index 1d33dd8..b49c5cc 100644 --- a/features/home/src/main/kotlin/app/dapk/st/home/HomeModule.kt +++ b/features/home/src/main/kotlin/app/dapk/st/home/HomeModule.kt @@ -6,11 +6,13 @@ import app.dapk.st.directory.DirectoryViewModel import app.dapk.st.domain.StoreModule import app.dapk.st.login.LoginViewModel import app.dapk.st.matrix.room.ProfileService +import app.dapk.st.matrix.sync.SyncService import app.dapk.st.profile.ProfileViewModel class HomeModule( private val storeModule: StoreModule, private val profileService: ProfileService, + private val syncService: SyncService, private val buildMeta: BuildMeta, ) : ProvidableModule { @@ -26,6 +28,7 @@ class HomeModule( storeModule.applicationStore(), buildMeta, ), + syncService, ) } diff --git a/features/home/src/main/kotlin/app/dapk/st/home/HomeScreen.kt b/features/home/src/main/kotlin/app/dapk/st/home/HomeScreen.kt index 57f852a..4590076 100644 --- a/features/home/src/main/kotlin/app/dapk/st/home/HomeScreen.kt +++ b/features/home/src/main/kotlin/app/dapk/st/home/HomeScreen.kt @@ -3,13 +3,12 @@ package app.dapk.st.home import androidx.compose.foundation.layout.* import androidx.compose.material3.* import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp +import app.dapk.st.core.LifecycleEffect import app.dapk.st.core.components.CenteredLoading import app.dapk.st.design.components.CircleishAvatar -import app.dapk.st.design.components.SmallTalkTheme import app.dapk.st.directory.DirectoryScreen import app.dapk.st.home.HomeScreenState.* import app.dapk.st.home.HomeScreenState.Page.Directory @@ -20,41 +19,42 @@ import app.dapk.st.profile.ProfileScreen @OptIn(ExperimentalMaterial3Api::class) @Composable fun HomeScreen(homeViewModel: HomeViewModel) { - Surface(Modifier.fillMaxSize()) { - LaunchedEffect(true) { - homeViewModel.start() - } + LifecycleEffect( + onStart = { homeViewModel.start() }, + onStop = { homeViewModel.stop() } + ) - when (val state = homeViewModel.state) { - Loading -> CenteredLoading() - is SignedIn -> { - Scaffold( - bottomBar = { - BottomBar(state, homeViewModel) - }, - content = { innerPadding -> - Box(modifier = Modifier.padding(innerPadding)) { - when (state.page) { - Directory -> DirectoryScreen(homeViewModel.directory()) - Profile -> { - ProfileScreen(homeViewModel.profile()) { - homeViewModel.changePage(Directory) - } - } + when (val state = homeViewModel.state) { + Loading -> CenteredLoading() + is SignedIn -> { + Scaffold( + bottomBar = { + BottomBar(state, homeViewModel) + }, + content = { innerPadding -> + Box(modifier = Modifier.padding(innerPadding)) { + when (state.page) { + Directory -> DirectoryScreen(homeViewModel.directory()) + Profile -> { + ProfileScreen(homeViewModel.profile()) { + homeViewModel.changePage(Directory) } } } - ) - } - SignedOut -> { - LoginScreen(homeViewModel.login()) { - homeViewModel.loggedIn() } } + ) + } + + SignedOut -> { + LoginScreen(homeViewModel.login()) { + homeViewModel.loggedIn() } } + } } +@OptIn(ExperimentalMaterial3Api::class) @Composable private fun BottomBar(state: SignedIn, homeViewModel: HomeViewModel) { Column { @@ -72,11 +72,17 @@ private fun BottomBar(state: SignedIn, homeViewModel: HomeViewModel) { } }, ) - Profile -> NavigationBarItem( + Profile -> NavigationBarItem( icon = { - Box { - CircleishAvatar(state.me.avatarUrl?.value, state.me.displayName ?: state.me.userId.value, size = 25.dp) + BadgedBox(badge = { + if (state.invites > 0) { + Badge(containerColor = MaterialTheme.colorScheme.primary) { Text("!", color = MaterialTheme.colorScheme.onPrimary) } + } + }) { + Box { + CircleishAvatar(state.me.avatarUrl?.value, state.me.displayName ?: state.me.userId.value, size = 25.dp) + } } }, selected = state.page == page, diff --git a/features/home/src/main/kotlin/app/dapk/st/home/HomeState.kt b/features/home/src/main/kotlin/app/dapk/st/home/HomeState.kt index ca25e6f..7bf0114 100644 --- a/features/home/src/main/kotlin/app/dapk/st/home/HomeState.kt +++ b/features/home/src/main/kotlin/app/dapk/st/home/HomeState.kt @@ -10,7 +10,7 @@ sealed interface HomeScreenState { object Loading : HomeScreenState object SignedOut : HomeScreenState - data class SignedIn(val page: Page, val me: ProfileService.Me) : HomeScreenState + data class SignedIn(val page: Page, val me: ProfileService.Me, val invites: Int) : HomeScreenState enum class Page(val icon: ImageVector) { Directory(Icons.Filled.Menu), diff --git a/features/home/src/main/kotlin/app/dapk/st/home/HomeViewModel.kt b/features/home/src/main/kotlin/app/dapk/st/home/HomeViewModel.kt index 877314a..334660b 100644 --- a/features/home/src/main/kotlin/app/dapk/st/home/HomeViewModel.kt +++ b/features/home/src/main/kotlin/app/dapk/st/home/HomeViewModel.kt @@ -8,8 +8,15 @@ import app.dapk.st.login.LoginViewModel import app.dapk.st.matrix.common.CredentialsStore import app.dapk.st.matrix.common.isSignedIn import app.dapk.st.matrix.room.ProfileService +import app.dapk.st.matrix.sync.SyncService import app.dapk.st.profile.ProfileViewModel import app.dapk.st.viewmodel.DapkViewModel +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.cancel +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch class HomeViewModel( @@ -20,10 +27,13 @@ class HomeViewModel( private val profileService: ProfileService, private val cacheCleaner: StoreCleaner, private val betaVersionUpgradeUseCase: BetaVersionUpgradeUseCase, + private val syncService: SyncService, ) : DapkViewModel( initialState = Loading ) { + private var listenForInvitesJob: Job? = null + fun directory() = directoryViewModel fun login() = loginViewModel fun profile() = profileViewModel @@ -31,21 +41,47 @@ class HomeViewModel( fun start() { viewModelScope.launch { state = if (credentialsProvider.isSignedIn()) { - val me = profileService.me(forceRefresh = false) - SignedIn(Page.Directory, me) + initialHomeContent() } else { SignedOut } } + + viewModelScope.launch { + if (credentialsProvider.isSignedIn()) { + listenForInviteChanges() + } + } + + } + + private suspend fun initialHomeContent(): SignedIn { + val me = profileService.me(forceRefresh = false) + val initialInvites = syncService.invites().first().size + return SignedIn(Page.Directory, me, invites = initialInvites) } fun loggedIn() { viewModelScope.launch { - val me = profileService.me(forceRefresh = false) - state = SignedIn(Page.Directory, me) + state = initialHomeContent() + listenForInviteChanges() } } + private fun CoroutineScope.listenForInviteChanges() { + listenForInvitesJob?.cancel() + listenForInvitesJob = syncService.invites() + .onEach { invites -> + when (val currentState = state) { + is SignedIn -> updateState { currentState.copy(invites = invites.size) } + Loading, + SignedOut -> { + // do nothing + } + } + }.launchIn(this) + } + fun hasVersionChanged() = betaVersionUpgradeUseCase.hasVersionChanged() fun clearCache() { @@ -66,4 +102,8 @@ class HomeViewModel( SignedOut -> current } } + + fun stop() { + viewModelScope.cancel() + } } diff --git a/features/home/src/main/kotlin/app/dapk/st/home/MainActivity.kt b/features/home/src/main/kotlin/app/dapk/st/home/MainActivity.kt index cd9872b..609f52d 100644 --- a/features/home/src/main/kotlin/app/dapk/st/home/MainActivity.kt +++ b/features/home/src/main/kotlin/app/dapk/st/home/MainActivity.kt @@ -1,11 +1,13 @@ package app.dapk.st.home import android.os.Bundle -import androidx.activity.compose.setContent +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier import androidx.lifecycle.lifecycleScope import app.dapk.st.core.DapkActivity import app.dapk.st.core.module @@ -35,7 +37,9 @@ class MainActivity : DapkActivity() { if (homeViewModel.hasVersionChanged()) { BetaUpgradeDialog() } else { - HomeScreen(homeViewModel) + Surface(Modifier.fillMaxSize()) { + HomeScreen(homeViewModel) + } } } }