enhancement: retry button for network failure (#786)
This commit is contained in:
parent
0f7ff3e5a0
commit
d58d2ba4b4
@ -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.ProfileMainMviModel
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.feature.profile.main.ProfileMainViewModel
|
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.login.di.loginModule
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.unit.manageaccounts.di.manageAccountsModule
|
import com.github.diegoberaldin.raccoonforlemmy.unit.manageaccounts.di.manageAccountsModule
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.unit.myaccount.di.myAccountModule
|
import com.github.diegoberaldin.raccoonforlemmy.unit.myaccount.di.myAccountModule
|
||||||
@ -19,4 +21,9 @@ val profileTabModule = module {
|
|||||||
logout = get(),
|
logout = get(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
factory<ProfileNotLoggedMviModel> {
|
||||||
|
ProfileNotLoggedViewModel(
|
||||||
|
identityRepository = get(),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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<ProfileNotLoggedMviModel.Intent, ProfileNotLoggedMviModel.State, ProfileNotLoggedMviModel.Effect> {
|
||||||
|
sealed interface Intent {
|
||||||
|
data object Retry : Intent
|
||||||
|
}
|
||||||
|
|
||||||
|
data class State(val authError: Boolean = false)
|
||||||
|
|
||||||
|
sealed interface Effect
|
||||||
|
}
|
@ -9,9 +9,12 @@ import androidx.compose.foundation.layout.padding
|
|||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import cafe.adriel.voyager.koin.getScreenModel
|
||||||
import cafe.adriel.voyager.navigator.tab.Tab
|
import cafe.adriel.voyager.navigator.tab.Tab
|
||||||
import cafe.adriel.voyager.navigator.tab.TabOptions
|
import cafe.adriel.voyager.navigator.tab.TabOptions
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.Spacing
|
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.Spacing
|
||||||
@ -28,16 +31,33 @@ internal object ProfileNotLoggedScreen : Tab {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
|
val model = getScreenModel<ProfileNotLoggedMviModel>()
|
||||||
val navigationCoordinator = remember { getNavigationCoordinator() }
|
val navigationCoordinator = remember { getNavigationCoordinator() }
|
||||||
|
val uiState by model.uiState.collectAsState()
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.fillMaxSize().padding(horizontal = Spacing.m),
|
modifier = Modifier.fillMaxSize().padding(horizontal = Spacing.m),
|
||||||
verticalArrangement = Arrangement.spacedBy(Spacing.xs),
|
verticalArrangement = Arrangement.spacedBy(Spacing.xs),
|
||||||
) {
|
) {
|
||||||
Text(
|
val message = if (uiState.authError) {
|
||||||
text = LocalXmlStrings.current.profileNotLoggedMessage,
|
LocalXmlStrings.current.messageAuthIssue
|
||||||
)
|
} else {
|
||||||
|
LocalXmlStrings.current.profileNotLoggedMessage
|
||||||
|
}
|
||||||
|
Text(text = message)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(Spacing.l))
|
Spacer(modifier = Modifier.height(Spacing.l))
|
||||||
|
|
||||||
|
if (uiState.authError) {
|
||||||
|
Button(
|
||||||
|
modifier = Modifier.align(Alignment.CenterHorizontally),
|
||||||
|
onClick = {
|
||||||
|
model.reduce(ProfileNotLoggedMviModel.Intent.Retry)
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Text(LocalXmlStrings.current.buttonRetry)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
Button(
|
Button(
|
||||||
modifier = Modifier.align(Alignment.CenterHorizontally),
|
modifier = Modifier.align(Alignment.CenterHorizontally),
|
||||||
onClick = {
|
onClick = {
|
||||||
@ -48,4 +68,5 @@ internal object ProfileNotLoggedScreen : Tab {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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<ProfileNotLoggedMviModel.Intent, ProfileNotLoggedMviModel.State, ProfileNotLoggedMviModel.Effect>(
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -16,7 +16,7 @@ interface PostListMviModel :
|
|||||||
ScreenModel {
|
ScreenModel {
|
||||||
|
|
||||||
sealed interface Intent {
|
sealed interface Intent {
|
||||||
data object Refresh : Intent
|
data class Refresh(val hardReset: Boolean = false) : Intent
|
||||||
data object LoadNextPage : Intent
|
data object LoadNextPage : Intent
|
||||||
data class ChangeListing(val value: ListingType) : Intent
|
data class ChangeListing(val value: ListingType) : Intent
|
||||||
data class UpVotePost(val id: Long, val feedback: Boolean = false) : Intent
|
data class UpVotePost(val id: Long, val feedback: Boolean = false) : Intent
|
||||||
|
@ -8,6 +8,7 @@ import androidx.compose.foundation.background
|
|||||||
import androidx.compose.foundation.gestures.animateScrollBy
|
import androidx.compose.foundation.gestures.animateScrollBy
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
@ -294,7 +295,7 @@ class PostListScreen : Screen {
|
|||||||
val pullRefreshState = rememberPullRefreshState(
|
val pullRefreshState = rememberPullRefreshState(
|
||||||
refreshing = uiState.refreshing,
|
refreshing = uiState.refreshing,
|
||||||
onRefresh = rememberCallback(model) {
|
onRefresh = rememberCallback(model) {
|
||||||
model.reduce(PostListMviModel.Intent.Refresh)
|
model.reduce(PostListMviModel.Intent.Refresh())
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
Box(
|
Box(
|
||||||
@ -637,7 +638,10 @@ class PostListScreen : Screen {
|
|||||||
PostListMviModel.Intent.Copy(texts.first())
|
PostListMviModel.Intent.Copy(texts.first())
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
val screen = CopyPostBottomSheet(post.title, post.text)
|
val screen = CopyPostBottomSheet(
|
||||||
|
post.title,
|
||||||
|
post.text,
|
||||||
|
)
|
||||||
navigationCoordinator.showBottomSheet(screen)
|
navigationCoordinator.showBottomSheet(screen)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -715,6 +719,29 @@ class PostListScreen : Screen {
|
|||||||
contentColor = MaterialTheme.colorScheme.onBackground,
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,6 +66,7 @@ class PostListViewModel(
|
|||||||
}.launchIn(this)
|
}.launchIn(this)
|
||||||
|
|
||||||
identityRepository.isLogged.onEach { logged ->
|
identityRepository.isLogged.onEach { logged ->
|
||||||
|
refreshUser()
|
||||||
updateState {
|
updateState {
|
||||||
it.copy(isLogged = logged ?: false)
|
it.copy(isLogged = logged ?: false)
|
||||||
}
|
}
|
||||||
@ -150,10 +151,6 @@ class PostListViewModel(
|
|||||||
emitEffect(PostListMviModel.Effect.ZombieModeTick(index))
|
emitEffect(PostListMviModel.Effect.ZombieModeTick(index))
|
||||||
}
|
}
|
||||||
}.launchIn(this)
|
}.launchIn(this)
|
||||||
|
|
||||||
val auth = identityRepository.authToken.value.orEmpty()
|
|
||||||
val user = siteRepository.getCurrentUser(auth)
|
|
||||||
updateState { it.copy(currentUserId = user?.id ?: 0) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onFirstLoad()
|
onFirstLoad()
|
||||||
@ -168,11 +165,18 @@ class PostListViewModel(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
screenModelScope.launch {
|
screenModelScope.launch {
|
||||||
|
refreshUser()
|
||||||
refresh(initial = true)
|
refresh(initial = true)
|
||||||
emitEffect(PostListMviModel.Effect.BackToTop)
|
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() {
|
private suspend fun updateAvailableSortTypes() {
|
||||||
val sortTypes = getSortTypesUseCase.getTypesForPosts()
|
val sortTypes = getSortTypesUseCase.getTypesForPosts()
|
||||||
updateState { it.copy(availableSortTypes = sortTypes) }
|
updateState { it.copy(availableSortTypes = sortTypes) }
|
||||||
@ -184,7 +188,10 @@ class PostListViewModel(
|
|||||||
loadNextPage()
|
loadNextPage()
|
||||||
}
|
}
|
||||||
|
|
||||||
PostListMviModel.Intent.Refresh -> screenModelScope.launch {
|
is PostListMviModel.Intent.Refresh -> screenModelScope.launch {
|
||||||
|
if (intent.hardReset) {
|
||||||
|
refreshUser()
|
||||||
|
}
|
||||||
refresh()
|
refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -292,7 +299,6 @@ class PostListViewModel(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
updateState { it.copy(loading = true) }
|
updateState { it.copy(loading = true) }
|
||||||
val refreshing = currentState.refreshing
|
|
||||||
val posts = postPaginationManager.loadNextPage().let {
|
val posts = postPaginationManager.loadNextPage().let {
|
||||||
if (!hideReadPosts) {
|
if (!hideReadPosts) {
|
||||||
it
|
it
|
||||||
|
Loading…
x
Reference in New Issue
Block a user