observe invite changes and render a ! badge on the profile bottom item

This commit is contained in:
Adam Brown 2022-09-10 19:29:45 +01:00 committed by Adam Brown
parent 7320690162
commit 40461f692f
7 changed files with 92 additions and 38 deletions

View File

@ -181,7 +181,7 @@ internal class FeatureModules internal constructor(
clock 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 { val settingsModule by unsafeLazy {
SettingsModule( SettingsModule(
storeModule.value, storeModule.value,

View File

@ -3,6 +3,7 @@ applyAndroidComposeLibraryModule(project)
dependencies { dependencies {
implementation project(":matrix:services:profile") implementation project(":matrix:services:profile")
implementation project(":matrix:services:crypto") implementation project(":matrix:services:crypto")
implementation project(":matrix:services:sync")
implementation project(":features:directory") implementation project(":features:directory")
implementation project(":features:login") implementation project(":features:login")
implementation project(":features:settings") implementation project(":features:settings")

View File

@ -6,11 +6,13 @@ import app.dapk.st.directory.DirectoryViewModel
import app.dapk.st.domain.StoreModule import app.dapk.st.domain.StoreModule
import app.dapk.st.login.LoginViewModel import app.dapk.st.login.LoginViewModel
import app.dapk.st.matrix.room.ProfileService import app.dapk.st.matrix.room.ProfileService
import app.dapk.st.matrix.sync.SyncService
import app.dapk.st.profile.ProfileViewModel import app.dapk.st.profile.ProfileViewModel
class HomeModule( class HomeModule(
private val storeModule: StoreModule, private val storeModule: StoreModule,
private val profileService: ProfileService, private val profileService: ProfileService,
private val syncService: SyncService,
private val buildMeta: BuildMeta, private val buildMeta: BuildMeta,
) : ProvidableModule { ) : ProvidableModule {
@ -26,6 +28,7 @@ class HomeModule(
storeModule.applicationStore(), storeModule.applicationStore(),
buildMeta, buildMeta,
), ),
syncService,
) )
} }

View File

@ -3,13 +3,12 @@ package app.dapk.st.home
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import app.dapk.st.core.LifecycleEffect
import app.dapk.st.core.components.CenteredLoading import app.dapk.st.core.components.CenteredLoading
import app.dapk.st.design.components.CircleishAvatar import app.dapk.st.design.components.CircleishAvatar
import app.dapk.st.design.components.SmallTalkTheme
import app.dapk.st.directory.DirectoryScreen import app.dapk.st.directory.DirectoryScreen
import app.dapk.st.home.HomeScreenState.* import app.dapk.st.home.HomeScreenState.*
import app.dapk.st.home.HomeScreenState.Page.Directory import app.dapk.st.home.HomeScreenState.Page.Directory
@ -20,41 +19,42 @@ import app.dapk.st.profile.ProfileScreen
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun HomeScreen(homeViewModel: HomeViewModel) { fun HomeScreen(homeViewModel: HomeViewModel) {
Surface(Modifier.fillMaxSize()) { LifecycleEffect(
LaunchedEffect(true) { onStart = { homeViewModel.start() },
homeViewModel.start() onStop = { homeViewModel.stop() }
} )
when (val state = homeViewModel.state) { when (val state = homeViewModel.state) {
Loading -> CenteredLoading() Loading -> CenteredLoading()
is SignedIn -> { is SignedIn -> {
Scaffold( Scaffold(
bottomBar = { bottomBar = {
BottomBar(state, homeViewModel) BottomBar(state, homeViewModel)
}, },
content = { innerPadding -> content = { innerPadding ->
Box(modifier = Modifier.padding(innerPadding)) { Box(modifier = Modifier.padding(innerPadding)) {
when (state.page) { when (state.page) {
Directory -> DirectoryScreen(homeViewModel.directory()) Directory -> DirectoryScreen(homeViewModel.directory())
Profile -> { Profile -> {
ProfileScreen(homeViewModel.profile()) { ProfileScreen(homeViewModel.profile()) {
homeViewModel.changePage(Directory) homeViewModel.changePage(Directory)
}
}
} }
} }
} }
)
}
SignedOut -> {
LoginScreen(homeViewModel.login()) {
homeViewModel.loggedIn()
} }
} }
)
}
SignedOut -> {
LoginScreen(homeViewModel.login()) {
homeViewModel.loggedIn()
} }
} }
}
} }
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
private fun BottomBar(state: SignedIn, homeViewModel: HomeViewModel) { private fun BottomBar(state: SignedIn, homeViewModel: HomeViewModel) {
Column { Column {
@ -72,11 +72,17 @@ private fun BottomBar(state: SignedIn, homeViewModel: HomeViewModel) {
} }
}, },
) )
Profile -> NavigationBarItem(
Profile -> NavigationBarItem(
icon = { icon = {
Box { BadgedBox(badge = {
CircleishAvatar(state.me.avatarUrl?.value, state.me.displayName ?: state.me.userId.value, size = 25.dp) 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, selected = state.page == page,

View File

@ -10,7 +10,7 @@ sealed interface HomeScreenState {
object Loading : HomeScreenState object Loading : HomeScreenState
object SignedOut : 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) { enum class Page(val icon: ImageVector) {
Directory(Icons.Filled.Menu), Directory(Icons.Filled.Menu),

View File

@ -8,8 +8,15 @@ import app.dapk.st.login.LoginViewModel
import app.dapk.st.matrix.common.CredentialsStore import app.dapk.st.matrix.common.CredentialsStore
import app.dapk.st.matrix.common.isSignedIn import app.dapk.st.matrix.common.isSignedIn
import app.dapk.st.matrix.room.ProfileService import app.dapk.st.matrix.room.ProfileService
import app.dapk.st.matrix.sync.SyncService
import app.dapk.st.profile.ProfileViewModel import app.dapk.st.profile.ProfileViewModel
import app.dapk.st.viewmodel.DapkViewModel 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 import kotlinx.coroutines.launch
class HomeViewModel( class HomeViewModel(
@ -20,10 +27,13 @@ class HomeViewModel(
private val profileService: ProfileService, private val profileService: ProfileService,
private val cacheCleaner: StoreCleaner, private val cacheCleaner: StoreCleaner,
private val betaVersionUpgradeUseCase: BetaVersionUpgradeUseCase, private val betaVersionUpgradeUseCase: BetaVersionUpgradeUseCase,
private val syncService: SyncService,
) : DapkViewModel<HomeScreenState, HomeEvent>( ) : DapkViewModel<HomeScreenState, HomeEvent>(
initialState = Loading initialState = Loading
) { ) {
private var listenForInvitesJob: Job? = null
fun directory() = directoryViewModel fun directory() = directoryViewModel
fun login() = loginViewModel fun login() = loginViewModel
fun profile() = profileViewModel fun profile() = profileViewModel
@ -31,21 +41,47 @@ class HomeViewModel(
fun start() { fun start() {
viewModelScope.launch { viewModelScope.launch {
state = if (credentialsProvider.isSignedIn()) { state = if (credentialsProvider.isSignedIn()) {
val me = profileService.me(forceRefresh = false) initialHomeContent()
SignedIn(Page.Directory, me)
} else { } else {
SignedOut 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() { fun loggedIn() {
viewModelScope.launch { viewModelScope.launch {
val me = profileService.me(forceRefresh = false) state = initialHomeContent()
state = SignedIn(Page.Directory, me) 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 hasVersionChanged() = betaVersionUpgradeUseCase.hasVersionChanged()
fun clearCache() { fun clearCache() {
@ -66,4 +102,8 @@ class HomeViewModel(
SignedOut -> current SignedOut -> current
} }
} }
fun stop() {
viewModelScope.cancel()
}
} }

View File

@ -1,11 +1,13 @@
package app.dapk.st.home package app.dapk.st.home
import android.os.Bundle import android.os.Bundle
import androidx.activity.compose.setContent import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import app.dapk.st.core.DapkActivity import app.dapk.st.core.DapkActivity
import app.dapk.st.core.module import app.dapk.st.core.module
@ -35,7 +37,9 @@ class MainActivity : DapkActivity() {
if (homeViewModel.hasVersionChanged()) { if (homeViewModel.hasVersionChanged()) {
BetaUpgradeDialog() BetaUpgradeDialog()
} else { } else {
HomeScreen(homeViewModel) Surface(Modifier.fillMaxSize()) {
HomeScreen(homeViewModel)
}
} }
} }
} }