observe invite changes and render a ! badge on the profile bottom item
This commit is contained in:
parent
7320690162
commit
40461f692f
|
@ -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,
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue