Display remote account synchronization error in TimelineTab

This commit is contained in:
Shinokuni 2024-05-10 16:52:38 +02:00
parent d19b8d90d4
commit d79c5b4637
8 changed files with 52 additions and 39 deletions

View File

@ -23,6 +23,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.ImeAction
@ -178,7 +179,7 @@ class AccountCredentialsScreen(
ShortSpacer() ShortSpacer()
Text( Text(
text = ErrorMessage.get(exception = state.loginException!!), text = ErrorMessage.get(state.loginException!!, LocalContext.current),
style = MaterialTheme.typography.labelMedium, style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.error color = MaterialTheme.colorScheme.error
) )

View File

@ -7,6 +7,7 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
@ -44,7 +45,7 @@ fun ErrorListDialog(
modifier = Modifier.verticalScroll(scrollableState) modifier = Modifier.verticalScroll(scrollableState)
) { ) {
for (error in errorResult.entries) { for (error in errorResult.entries) {
Text(text = "${error.key.name}: ${ErrorMessage.get(error.value)}") Text(text = "${error.key.name}: ${ErrorMessage.get(error.value, LocalContext.current)}")
ShortSpacer() ShortSpacer()
} }

View File

@ -2,7 +2,6 @@ package com.readrops.app.compose.timelime
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.util.Log
import androidx.compose.runtime.Stable import androidx.compose.runtime.Stable
import androidx.paging.Pager import androidx.paging.Pager
import androidx.paging.PagingConfig import androidx.paging.PagingConfig
@ -32,6 +31,7 @@ import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.flatMapConcat import kotlinx.coroutines.flow.flatMapConcat
import kotlinx.coroutines.flow.transformLatest
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -68,6 +68,11 @@ class TimelineScreenModel(
database.newItemDao().selectAll(query) database.newItemDao().selectAll(query)
}, },
).flow ).flow
.transformLatest { value ->
if (!timelineState.value.isRefreshing) {
emit(value)
}
}
.cachedIn(screenModelScope), .cachedIn(screenModelScope),
isAccountLocal = account.isLocal isAccountLocal = account.isLocal
) )
@ -102,11 +107,11 @@ class TimelineScreenModel(
} else { } else {
_timelineState.update { it.copy(isRefreshing = true) } _timelineState.update { it.copy(isRefreshing = true) }
repository?.synchronize()
try { try {
repository?.synchronize()
} catch (e: Exception) { } catch (e: Exception) {
// handle sync exceptions _timelineState.update { it.copy(syncError = e, isRefreshing = false) }
Log.d("TimelineScreenModel", "refreshTimeline: ${e.message}") return@launch
} }
_timelineState.update { _timelineState.update {
@ -160,7 +165,7 @@ class TimelineScreenModel(
it.copy( it.copy(
isRefreshing = false, isRefreshing = false,
endSynchronizing = true, endSynchronizing = true,
synchronizationErrors = if (results!!.second.isNotEmpty()) results.second else null localSyncErrors = if (results!!.second.isNotEmpty()) results.second else null
) )
} }
} }
@ -287,7 +292,7 @@ class TimelineScreenModel(
fun closeDialog(dialog: DialogState? = null) { fun closeDialog(dialog: DialogState? = null) {
if (dialog is DialogState.ErrorList) { if (dialog is DialogState.ErrorList) {
_timelineState.update { it.copy(synchronizationErrors = null) } _timelineState.update { it.copy(localSyncErrors = null) }
} }
_timelineState.update { it.copy(dialog = null) } _timelineState.update { it.copy(dialog = null) }
@ -329,7 +334,8 @@ data class TimelineState(
val feedCount: Int = 0, val feedCount: Int = 0,
val feedMax: Int = 0, val feedMax: Int = 0,
val endSynchronizing: Boolean = false, val endSynchronizing: Boolean = false,
val synchronizationErrors: ErrorResult? = null, val localSyncErrors: ErrorResult? = null,
val syncError: Exception? = null,
val filters: QueryFilters = QueryFilters(), val filters: QueryFilters = QueryFilters(),
val filterFeedName: String = "", val filterFeedName: String = "",
val filterFolderName: String = "", val filterFolderName: String = "",

View File

@ -52,6 +52,7 @@ import cafe.adriel.voyager.navigator.tab.TabOptions
import com.readrops.app.compose.R import com.readrops.app.compose.R
import com.readrops.app.compose.item.ItemScreen import com.readrops.app.compose.item.ItemScreen
import com.readrops.app.compose.timelime.drawer.TimelineDrawer import com.readrops.app.compose.timelime.drawer.TimelineDrawer
import com.readrops.app.compose.util.ErrorMessage
import com.readrops.app.compose.util.components.CenteredProgressIndicator import com.readrops.app.compose.util.components.CenteredProgressIndicator
import com.readrops.app.compose.util.components.Placeholder import com.readrops.app.compose.util.components.Placeholder
import com.readrops.app.compose.util.components.RefreshScreen import com.readrops.app.compose.util.components.RefreshScreen
@ -127,25 +128,31 @@ object TimelineTab : Tab {
} }
} }
LaunchedEffect(state.synchronizationErrors) { LaunchedEffect(state.localSyncErrors) {
if (state.synchronizationErrors != null) { if (state.localSyncErrors != null) {
val action = snackbarHostState.showSnackbar( val action = snackbarHostState.showSnackbar(
message = context.resources.getQuantityString( message = context.resources.getQuantityString(
R.plurals.error_occurred, R.plurals.error_occurred,
state.synchronizationErrors!!.size state.localSyncErrors!!.size
), ),
actionLabel = context.getString(R.string.details), actionLabel = context.getString(R.string.details),
duration = SnackbarDuration.Short duration = SnackbarDuration.Short
) )
if (action == SnackbarResult.ActionPerformed) { if (action == SnackbarResult.ActionPerformed) {
viewModel.openDialog(DialogState.ErrorList(state.synchronizationErrors!!)) viewModel.openDialog(DialogState.ErrorList(state.localSyncErrors!!))
} else { } else {
// remove errors from state // remove errors from state
viewModel.closeDialog(DialogState.ErrorList(state.synchronizationErrors!!)) viewModel.closeDialog(DialogState.ErrorList(state.localSyncErrors!!))
} }
} }
} }
LaunchedEffect(state.syncError) {
if (state.syncError != null) {
snackbarHostState.showSnackbar(ErrorMessage.get(state.syncError!!, context))
}
}
when (val dialog = state.dialog) { when (val dialog = state.dialog) {
is DialogState.ConfirmDialog -> { is DialogState.ConfirmDialog -> {

View File

@ -1,7 +1,6 @@
package com.readrops.app.compose.util package com.readrops.app.compose.util
import androidx.compose.runtime.Composable import android.content.Context
import androidx.compose.ui.res.stringResource
import com.readrops.api.utils.exceptions.HttpException import com.readrops.api.utils.exceptions.HttpException
import com.readrops.api.utils.exceptions.ParseException import com.readrops.api.utils.exceptions.ParseException
import com.readrops.api.utils.exceptions.UnknownFormatException import com.readrops.api.utils.exceptions.UnknownFormatException
@ -11,33 +10,31 @@ import java.net.UnknownHostException
object ErrorMessage { object ErrorMessage {
@Composable fun get(exception: Exception, context: Context) = when (exception) {
fun get(exception: Exception) = when (exception) { is HttpException -> getHttpMessage(exception, context)
is HttpException -> getHttpMessage(exception) is UnknownHostException -> context.resources.getString(R.string.unreachable_url)
is UnknownHostException -> stringResource(R.string.unreachable_url) is NoSuchFileException -> context.resources.getString(R.string.unable_open_file)
is NoSuchFileException -> stringResource(R.string.unable_open_file) is IOException -> context.resources.getString(R.string.network_failure, exception.message.orEmpty())
is IOException -> stringResource(R.string.network_failure, exception.message.orEmpty()) is ParseException, is UnknownFormatException -> context.resources.getString(R.string.processing_feed_error)
is ParseException, is UnknownFormatException -> stringResource(R.string.processing_feed_error)
else -> "${exception.javaClass.simpleName}: ${exception.message}" else -> "${exception.javaClass.simpleName}: ${exception.message}"
} }
@Composable private fun getHttpMessage(exception: HttpException, context: Context): String {
private fun getHttpMessage(exception: HttpException): String {
return when (exception.code) { return when (exception.code) {
in 400..499 -> { in 400..499 -> {
when (exception.code) { when (exception.code) {
400 -> stringResource(id = R.string.http_error_400) 400 -> context.resources.getString(R.string.http_error_400)
401 -> stringResource(id = R.string.http_error_401) 401 -> context.resources.getString(R.string.http_error_401)
403 -> stringResource(id = R.string.http_error_403) 403 -> context.resources.getString(R.string.http_error_403)
404 -> stringResource(id = R.string.http_error_404) 404 -> context.resources.getString(R.string.http_error_404)
else -> stringResource(id = R.string.http_error_4XX, exception.code) else -> context.resources.getString(R.string.http_error_4XX, exception.code)
} }
} }
in 500..599 -> { in 500..599 -> {
stringResource(id = R.string.http_error_5XX, exception.code) context.resources.getString(R.string.http_error_5XX, exception.code)
} }
else -> stringResource(id = R.string.http_error, exception.code) else -> context.resources.getString(R.string.http_error, exception.code)
} }
} }
} }

View File

@ -2,6 +2,7 @@ package com.readrops.app.compose.util.components
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import com.readrops.app.compose.R import com.readrops.app.compose.R
@ -17,7 +18,7 @@ fun ErrorDialog(
icon = painterResource(id = R.drawable.ic_error), icon = painterResource(id = R.drawable.ic_error),
onDismiss = onDismiss onDismiss = onDismiss
) { ) {
Text(text = ErrorMessage.get(exception = exception)) Text(text = ErrorMessage.get(exception, LocalContext.current))
} }
} }

View File

@ -161,7 +161,7 @@
<string name="http_error_401">Erreur HTTP 401, veuillez vérifier vos identifiants</string> <string name="http_error_401">Erreur HTTP 401, veuillez vérifier vos identifiants</string>
<string name="http_error_403">Erreur HTTP 403, accès interdit</string> <string name="http_error_403">Erreur HTTP 403, accès interdit</string>
<string name="http_error_404">Erreur HTTP 404, l\'URL n\'existe pas</string> <string name="http_error_404">Erreur HTTP 404, l\'URL n\'existe pas</string>
<string name="http_error_4XX">Erreur HTTP %1$s, veuillez vérifier vos champs</string> <string name="http_error_4XX">Erreur HTTP %1$d, veuillez vérifier vos champs</string>
<string name="http_error_5XX">Erreur HTTP %1$s, erreur serveur</string> <string name="http_error_5XX">Erreur HTTP %1$d, erreur serveur</string>
<string name="http_error">Erreur HTTP %1$s</string> <string name="http_error">Erreur HTTP %1$d</string>
</resources> </resources>

View File

@ -167,7 +167,7 @@
<string name="http_error_401">HTTP error 401, please check your credentials</string> <string name="http_error_401">HTTP error 401, please check your credentials</string>
<string name="http_error_403">HTTP error 403, access forbidden</string> <string name="http_error_403">HTTP error 403, access forbidden</string>
<string name="http_error_404">HTTP error 404, URL not found</string> <string name="http_error_404">HTTP error 404, URL not found</string>
<string name="http_error_4XX">HTTP error %1$s, please check your fields</string> <string name="http_error_4XX">HTTP error %1$d, please check your fields</string>
<string name="http_error_5XX">HTTP error %1$s, server error</string> <string name="http_error_5XX">HTTP error %1$d, server error</string>
<string name="http_error">HTTP error %1$s</string> <string name="http_error">HTTP error %1$d</string>
</resources> </resources>