diff --git a/feature/profile/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/profile/di/ProfileModule.kt b/feature/profile/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/profile/di/ProfileModule.kt index 06728564f..6df33c355 100644 --- a/feature/profile/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/profile/di/ProfileModule.kt +++ b/feature/profile/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/profile/di/ProfileModule.kt @@ -2,6 +2,8 @@ package com.github.diegoberaldin.raccoonforlemmy.feature.profile.di import com.github.diegoberaldin.raccoonforlemmy.feature.profile.main.ProfileMainMviModel import com.github.diegoberaldin.raccoonforlemmy.feature.profile.main.ProfileMainViewModel +import com.github.diegoberaldin.raccoonforlemmy.feature.profile.notlogged.ProfileNotLoggedMviModel +import com.github.diegoberaldin.raccoonforlemmy.feature.profile.notlogged.ProfileNotLoggedViewModel import com.github.diegoberaldin.raccoonforlemmy.unit.login.di.loginModule import com.github.diegoberaldin.raccoonforlemmy.unit.manageaccounts.di.manageAccountsModule import com.github.diegoberaldin.raccoonforlemmy.unit.myaccount.di.myAccountModule @@ -19,4 +21,9 @@ val profileTabModule = module { logout = get(), ) } + factory { + ProfileNotLoggedViewModel( + identityRepository = get(), + ) + } } diff --git a/feature/profile/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/profile/notlogged/ProfileNotLoggedMviModel.kt b/feature/profile/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/profile/notlogged/ProfileNotLoggedMviModel.kt new file mode 100644 index 000000000..fa805bed1 --- /dev/null +++ b/feature/profile/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/profile/notlogged/ProfileNotLoggedMviModel.kt @@ -0,0 +1,16 @@ +package com.github.diegoberaldin.raccoonforlemmy.feature.profile.notlogged + +import cafe.adriel.voyager.core.model.ScreenModel +import com.github.diegoberaldin.raccoonforlemmy.core.architecture.MviModel + +interface ProfileNotLoggedMviModel : + ScreenModel, + MviModel { + sealed interface Intent { + data object Retry : Intent + } + + data class State(val authError: Boolean = false) + + sealed interface Effect +} \ No newline at end of file diff --git a/feature/profile/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/profile/notlogged/ProfileNotLoggedScreen.kt b/feature/profile/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/profile/notlogged/ProfileNotLoggedScreen.kt index e06cc6a7d..b9e01cc95 100644 --- a/feature/profile/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/profile/notlogged/ProfileNotLoggedScreen.kt +++ b/feature/profile/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/profile/notlogged/ProfileNotLoggedScreen.kt @@ -9,9 +9,12 @@ import androidx.compose.foundation.layout.padding import androidx.compose.material3.Button import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import cafe.adriel.voyager.koin.getScreenModel import cafe.adriel.voyager.navigator.tab.Tab import cafe.adriel.voyager.navigator.tab.TabOptions import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.Spacing @@ -28,23 +31,41 @@ internal object ProfileNotLoggedScreen : Tab { @Composable override fun Content() { + val model = getScreenModel() val navigationCoordinator = remember { getNavigationCoordinator() } + val uiState by model.uiState.collectAsState() Column( modifier = Modifier.fillMaxSize().padding(horizontal = Spacing.m), verticalArrangement = Arrangement.spacedBy(Spacing.xs), ) { - Text( - text = LocalXmlStrings.current.profileNotLoggedMessage, - ) + val message = if (uiState.authError) { + LocalXmlStrings.current.messageAuthIssue + } else { + LocalXmlStrings.current.profileNotLoggedMessage + } + Text(text = message) + Spacer(modifier = Modifier.height(Spacing.l)) - Button( - modifier = Modifier.align(Alignment.CenterHorizontally), - onClick = { - navigationCoordinator.pushScreen(LoginBottomSheet()) - }, - ) { - Text(LocalXmlStrings.current.profileButtonLogin) + + if (uiState.authError) { + Button( + modifier = Modifier.align(Alignment.CenterHorizontally), + onClick = { + model.reduce(ProfileNotLoggedMviModel.Intent.Retry) + }, + ) { + Text(LocalXmlStrings.current.buttonRetry) + } + } else { + Button( + modifier = Modifier.align(Alignment.CenterHorizontally), + onClick = { + navigationCoordinator.pushScreen(LoginBottomSheet()) + }, + ) { + Text(LocalXmlStrings.current.profileButtonLogin) + } } } } diff --git a/feature/profile/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/profile/notlogged/ProfileNotLoggedViewModel.kt b/feature/profile/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/profile/notlogged/ProfileNotLoggedViewModel.kt new file mode 100644 index 000000000..a374b2284 --- /dev/null +++ b/feature/profile/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/profile/notlogged/ProfileNotLoggedViewModel.kt @@ -0,0 +1,33 @@ +package com.github.diegoberaldin.raccoonforlemmy.feature.profile.notlogged + +import cafe.adriel.voyager.core.model.screenModelScope +import com.github.diegoberaldin.raccoonforlemmy.core.architecture.DefaultMviModel +import com.github.diegoberaldin.raccoonforlemmy.domain.identity.repository.IdentityRepository +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch + +class ProfileNotLoggedViewModel( + private val identityRepository: IdentityRepository, +) : ProfileNotLoggedMviModel, + DefaultMviModel( + initialState = ProfileNotLoggedMviModel.State() + ) { + + init { + screenModelScope.launch { + identityRepository.isLogged.onEach { logged -> + val auth = identityRepository.authToken.value + updateState { it.copy(authError = !auth.isNullOrEmpty() && logged == false) } + }.launchIn(this) + } + } + + override fun reduce(intent: ProfileNotLoggedMviModel.Intent) { + when (intent) { + ProfileNotLoggedMviModel.Intent.Retry -> screenModelScope.launch { + identityRepository.refreshLoggedState() + } + } + } +} \ No newline at end of file diff --git a/unit/postlist/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/unit/postlist/PostListMviModel.kt b/unit/postlist/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/unit/postlist/PostListMviModel.kt index ea29f8fc3..c1c4beb14 100644 --- a/unit/postlist/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/unit/postlist/PostListMviModel.kt +++ b/unit/postlist/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/unit/postlist/PostListMviModel.kt @@ -16,7 +16,7 @@ interface PostListMviModel : ScreenModel { sealed interface Intent { - data object Refresh : Intent + data class Refresh(val hardReset: Boolean = false) : Intent data object LoadNextPage : Intent data class ChangeListing(val value: ListingType) : Intent data class UpVotePost(val id: Long, val feedback: Boolean = false) : Intent diff --git a/unit/postlist/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/unit/postlist/PostListScreen.kt b/unit/postlist/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/unit/postlist/PostListScreen.kt index 145a506cd..9cbb4fc2b 100644 --- a/unit/postlist/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/unit/postlist/PostListScreen.kt +++ b/unit/postlist/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/unit/postlist/PostListScreen.kt @@ -8,6 +8,7 @@ import androidx.compose.foundation.background import androidx.compose.foundation.gestures.animateScrollBy import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets @@ -294,7 +295,7 @@ class PostListScreen : Screen { val pullRefreshState = rememberPullRefreshState( refreshing = uiState.refreshing, onRefresh = rememberCallback(model) { - model.reduce(PostListMviModel.Intent.Refresh) + model.reduce(PostListMviModel.Intent.Refresh()) }, ) Box( @@ -637,7 +638,10 @@ class PostListScreen : Screen { PostListMviModel.Intent.Copy(texts.first()) ) } else { - val screen = CopyPostBottomSheet(post.title, post.text) + val screen = CopyPostBottomSheet( + post.title, + post.text, + ) navigationCoordinator.showBottomSheet(screen) } } @@ -715,6 +719,29 @@ class PostListScreen : Screen { contentColor = MaterialTheme.colorScheme.onBackground, ) } + } else { + Column( + modifier = Modifier.padding(vertical = Spacing.m), + verticalArrangement = Arrangement.spacedBy(Spacing.s), + ) { + Text( + modifier = Modifier + .fillMaxWidth() + .padding(top = Spacing.xs), + textAlign = TextAlign.Center, + text = LocalXmlStrings.current.messageGenericError, + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onBackground, + ) + Button( + modifier = Modifier.align(Alignment.CenterHorizontally), + onClick = rememberCallback { + model.reduce(PostListMviModel.Intent.Refresh(hardReset = true)) + }, + ) { + Text(LocalXmlStrings.current.buttonRetry) + } + } } } diff --git a/unit/postlist/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/unit/postlist/PostListViewModel.kt b/unit/postlist/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/unit/postlist/PostListViewModel.kt index 20b8c0078..a16fd5887 100644 --- a/unit/postlist/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/unit/postlist/PostListViewModel.kt +++ b/unit/postlist/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/unit/postlist/PostListViewModel.kt @@ -66,6 +66,7 @@ class PostListViewModel( }.launchIn(this) identityRepository.isLogged.onEach { logged -> + refreshUser() updateState { it.copy(isLogged = logged ?: false) } @@ -150,10 +151,6 @@ class PostListViewModel( emitEffect(PostListMviModel.Effect.ZombieModeTick(index)) } }.launchIn(this) - - val auth = identityRepository.authToken.value.orEmpty() - val user = siteRepository.getCurrentUser(auth) - updateState { it.copy(currentUserId = user?.id ?: 0) } } onFirstLoad() @@ -168,11 +165,18 @@ class PostListViewModel( ) } screenModelScope.launch { + refreshUser() refresh(initial = true) emitEffect(PostListMviModel.Effect.BackToTop) } } + private suspend fun refreshUser() { + val auth = identityRepository.authToken.value.orEmpty() + val user = siteRepository.getCurrentUser(auth) + updateState { it.copy(currentUserId = user?.id ?: 0) } + } + private suspend fun updateAvailableSortTypes() { val sortTypes = getSortTypesUseCase.getTypesForPosts() updateState { it.copy(availableSortTypes = sortTypes) } @@ -184,7 +188,10 @@ class PostListViewModel( loadNextPage() } - PostListMviModel.Intent.Refresh -> screenModelScope.launch { + is PostListMviModel.Intent.Refresh -> screenModelScope.launch { + if (intent.hardReset) { + refreshUser() + } refresh() } @@ -292,7 +299,6 @@ class PostListViewModel( return } updateState { it.copy(loading = true) } - val refreshing = currentState.refreshing val posts = postPaginationManager.loadNextPage().let { if (!hideReadPosts) { it