Display remote account synchronization error in TimelineTab
This commit is contained in:
parent
d19b8d90d4
commit
d79c5b4637
@ -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
|
||||||
)
|
)
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
|
@ -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 = "",
|
||||||
|
@ -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,26 +128,32 @@ 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 -> {
|
||||||
TwoChoicesDialog(
|
TwoChoicesDialog(
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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>
|
@ -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>
|
Loading…
x
Reference in New Issue
Block a user