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
)
}
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,

View File

@ -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")

View File

@ -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,
)
}

View File

@ -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,

View File

@ -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),

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.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<HomeScreenState, HomeEvent>(
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()
}
}

View File

@ -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)
}
}
}
}