mirror of https://github.com/readrops/Readrops.git
Show opml import errors in AccountTab
This commit is contained in:
parent
e0874f2297
commit
0a1574df0d
|
@ -2,10 +2,14 @@ package com.readrops.app.compose.account
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import androidx.core.net.toFile
|
||||||
import cafe.adriel.voyager.core.model.screenModelScope
|
import cafe.adriel.voyager.core.model.screenModelScope
|
||||||
import com.readrops.api.opml.OPMLParser
|
import com.readrops.api.opml.OPMLParser
|
||||||
import com.readrops.app.compose.base.TabScreenModel
|
import com.readrops.app.compose.base.TabScreenModel
|
||||||
|
import com.readrops.app.compose.repositories.ErrorResult
|
||||||
import com.readrops.db.Database
|
import com.readrops.db.Database
|
||||||
|
import com.readrops.db.entities.Feed
|
||||||
|
import com.readrops.db.entities.Folder
|
||||||
import com.readrops.db.entities.account.Account
|
import com.readrops.db.entities.account.Account
|
||||||
import com.readrops.db.entities.account.AccountType
|
import com.readrops.db.entities.account.AccountType
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
@ -38,7 +42,15 @@ class AccountScreenModel(
|
||||||
|
|
||||||
fun openDialog(dialog: DialogState) = _accountState.update { it.copy(dialog = dialog) }
|
fun openDialog(dialog: DialogState) = _accountState.update { it.copy(dialog = dialog) }
|
||||||
|
|
||||||
fun closeDialog() = _accountState.update { it.copy(dialog = null) }
|
fun closeDialog(dialog: DialogState? = null) {
|
||||||
|
if (dialog is DialogState.ErrorList) {
|
||||||
|
_accountState.update { it.copy(synchronizationErrors = null) }
|
||||||
|
} else if (dialog is DialogState.Error) {
|
||||||
|
_accountState.update { it.copy(opmlImportError = null) }
|
||||||
|
}
|
||||||
|
|
||||||
|
_accountState.update { it.copy(dialog = null) }
|
||||||
|
}
|
||||||
|
|
||||||
fun deleteAccount() {
|
fun deleteAccount() {
|
||||||
screenModelScope.launch(Dispatchers.IO) {
|
screenModelScope.launch(Dispatchers.IO) {
|
||||||
|
@ -51,8 +63,20 @@ class AccountScreenModel(
|
||||||
|
|
||||||
fun parseOPMLFile(uri: Uri, context: Context) {
|
fun parseOPMLFile(uri: Uri, context: Context) {
|
||||||
screenModelScope.launch(Dispatchers.IO) {
|
screenModelScope.launch(Dispatchers.IO) {
|
||||||
val stream = context.contentResolver.openInputStream(uri)!!
|
val foldersAndFeeds: Map<Folder?, List<Feed>>
|
||||||
val foldersAndFeeds = OPMLParser.read(stream)
|
|
||||||
|
try {
|
||||||
|
val stream = context.contentResolver.openInputStream(uri)
|
||||||
|
if (stream == null) {
|
||||||
|
_accountState.update { it.copy(opmlImportError = NoSuchFileException(uri.toFile())) }
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
|
||||||
|
foldersAndFeeds = OPMLParser.read(stream)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
_accountState.update { it.copy(opmlImportError = e) }
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
|
||||||
openDialog(
|
openDialog(
|
||||||
DialogState.OPMLImport(
|
DialogState.OPMLImport(
|
||||||
|
@ -62,7 +86,7 @@ class AccountScreenModel(
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
repository?.insertOPMLFoldersAndFeeds(
|
val errors = repository?.insertOPMLFoldersAndFeeds(
|
||||||
foldersAndFeeds = foldersAndFeeds,
|
foldersAndFeeds = foldersAndFeeds,
|
||||||
onUpdate = { feed ->
|
onUpdate = { feed ->
|
||||||
_accountState.update {
|
_accountState.update {
|
||||||
|
@ -79,6 +103,10 @@ class AccountScreenModel(
|
||||||
)
|
)
|
||||||
|
|
||||||
closeDialog()
|
closeDialog()
|
||||||
|
|
||||||
|
_accountState.update {
|
||||||
|
it.copy(synchronizationErrors = if (errors!!.isNotEmpty()) errors else null)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -86,6 +114,8 @@ class AccountScreenModel(
|
||||||
data class AccountState(
|
data class AccountState(
|
||||||
val account: Account = Account(accountName = "account", accountType = AccountType.LOCAL),
|
val account: Account = Account(accountName = "account", accountType = AccountType.LOCAL),
|
||||||
val dialog: DialogState? = null,
|
val dialog: DialogState? = null,
|
||||||
|
val synchronizationErrors: ErrorResult? = null,
|
||||||
|
val opmlImportError: Exception? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
sealed interface DialogState {
|
sealed interface DialogState {
|
||||||
|
@ -93,4 +123,7 @@ sealed interface DialogState {
|
||||||
object NewAccount : DialogState
|
object NewAccount : DialogState
|
||||||
data class OPMLImport(val currentFeed: String, val feedCount: Int, val feedMax: Int) :
|
data class OPMLImport(val currentFeed: String, val feedCount: Int, val feedMax: Int) :
|
||||||
DialogState
|
DialogState
|
||||||
|
|
||||||
|
data class ErrorList(val errorResult: ErrorResult) : DialogState
|
||||||
|
data class Error(val exception: Exception) : DialogState
|
||||||
}
|
}
|
|
@ -19,10 +19,16 @@ import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.SnackbarDuration
|
||||||
|
import androidx.compose.material3.SnackbarHost
|
||||||
|
import androidx.compose.material3.SnackbarHostState
|
||||||
|
import androidx.compose.material3.SnackbarResult
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
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 androidx.compose.ui.graphics.vector.rememberVectorPainter
|
import androidx.compose.ui.graphics.vector.rememberVectorPainter
|
||||||
|
@ -41,6 +47,8 @@ import com.readrops.app.compose.R
|
||||||
import com.readrops.app.compose.account.credentials.AccountCredentialsScreen
|
import com.readrops.app.compose.account.credentials.AccountCredentialsScreen
|
||||||
import com.readrops.app.compose.account.selection.AccountSelectionDialog
|
import com.readrops.app.compose.account.selection.AccountSelectionDialog
|
||||||
import com.readrops.app.compose.account.selection.AccountSelectionScreen
|
import com.readrops.app.compose.account.selection.AccountSelectionScreen
|
||||||
|
import com.readrops.app.compose.timelime.ErrorListDialog
|
||||||
|
import com.readrops.app.compose.util.components.ErrorDialog
|
||||||
import com.readrops.app.compose.util.components.SelectableIconText
|
import com.readrops.app.compose.util.components.SelectableIconText
|
||||||
import com.readrops.app.compose.util.components.TwoChoicesDialog
|
import com.readrops.app.compose.util.components.TwoChoicesDialog
|
||||||
import com.readrops.app.compose.util.theme.LargeSpacer
|
import com.readrops.app.compose.util.theme.LargeSpacer
|
||||||
|
@ -66,6 +74,8 @@ object AccountTab : Tab {
|
||||||
val closeHome by viewModel.closeHome.collectAsStateWithLifecycle()
|
val closeHome by viewModel.closeHome.collectAsStateWithLifecycle()
|
||||||
val state by viewModel.accountState.collectAsStateWithLifecycle()
|
val state by viewModel.accountState.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
|
|
||||||
if (closeHome) {
|
if (closeHome) {
|
||||||
navigator.replaceAll(AccountSelectionScreen())
|
navigator.replaceAll(AccountSelectionScreen())
|
||||||
}
|
}
|
||||||
|
@ -75,6 +85,44 @@ object AccountTab : Tab {
|
||||||
uri?.let { viewModel.parseOPMLFile(uri, context) }
|
uri?.let { viewModel.parseOPMLFile(uri, context) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(state.opmlImportError) {
|
||||||
|
if (state.opmlImportError != null) {
|
||||||
|
val action = snackbarHostState.showSnackbar(
|
||||||
|
message = context.resources.getQuantityString(
|
||||||
|
R.plurals.error_occurred,
|
||||||
|
1
|
||||||
|
),
|
||||||
|
actionLabel = context.getString(R.string.details),
|
||||||
|
duration = SnackbarDuration.Short
|
||||||
|
)
|
||||||
|
|
||||||
|
if (action == SnackbarResult.ActionPerformed) {
|
||||||
|
viewModel.openDialog(DialogState.Error(state.opmlImportError!!))
|
||||||
|
} else {
|
||||||
|
viewModel.closeDialog(DialogState.Error(state.opmlImportError!!))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(state.synchronizationErrors) {
|
||||||
|
if (state.synchronizationErrors != null) {
|
||||||
|
val action = snackbarHostState.showSnackbar(
|
||||||
|
message = context.resources.getQuantityString(
|
||||||
|
R.plurals.error_occurred,
|
||||||
|
state.synchronizationErrors!!.size
|
||||||
|
),
|
||||||
|
actionLabel = context.getString(R.string.details),
|
||||||
|
duration = SnackbarDuration.Short
|
||||||
|
)
|
||||||
|
|
||||||
|
if (action == SnackbarResult.ActionPerformed) {
|
||||||
|
viewModel.openDialog(DialogState.ErrorList(state.synchronizationErrors!!))
|
||||||
|
} else {
|
||||||
|
viewModel.closeDialog(DialogState.ErrorList(state.synchronizationErrors!!))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
when (val dialog = state.dialog) {
|
when (val dialog = state.dialog) {
|
||||||
is DialogState.DeleteAccount -> {
|
is DialogState.DeleteAccount -> {
|
||||||
TwoChoicesDialog(
|
TwoChoicesDialog(
|
||||||
|
@ -109,6 +157,20 @@ object AccountTab : Tab {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is DialogState.ErrorList -> {
|
||||||
|
ErrorListDialog(
|
||||||
|
errorResult = dialog.errorResult,
|
||||||
|
onDismiss = { viewModel.closeDialog(dialog) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
is DialogState.Error -> {
|
||||||
|
ErrorDialog(
|
||||||
|
exception = dialog.exception,
|
||||||
|
onDismiss = { viewModel.closeDialog(dialog) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -137,7 +199,8 @@ object AccountTab : Tab {
|
||||||
contentDescription = null
|
contentDescription = null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
snackbarHost = { SnackbarHost(snackbarHostState) }
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
|
@ -37,7 +37,7 @@ abstract class ARepository(
|
||||||
*/
|
*/
|
||||||
abstract suspend fun synchronize(): SyncResult
|
abstract suspend fun synchronize(): SyncResult
|
||||||
|
|
||||||
abstract suspend fun insertNewFeeds(newFeeds: List<Feed>, onUpdate: (Feed) -> Unit)
|
abstract suspend fun insertNewFeeds(newFeeds: List<Feed>, onUpdate: (Feed) -> Unit): ErrorResult
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class BaseRepository(
|
abstract class BaseRepository(
|
||||||
|
@ -87,7 +87,9 @@ abstract class BaseRepository(
|
||||||
suspend fun insertOPMLFoldersAndFeeds(
|
suspend fun insertOPMLFoldersAndFeeds(
|
||||||
foldersAndFeeds: Map<Folder?, List<Feed>>,
|
foldersAndFeeds: Map<Folder?, List<Feed>>,
|
||||||
onUpdate: (Feed) -> Unit
|
onUpdate: (Feed) -> Unit
|
||||||
) {
|
): ErrorResult {
|
||||||
|
val errors = mutableMapOf<Feed, Exception>()
|
||||||
|
|
||||||
for ((folder, feeds) in foldersAndFeeds) {
|
for ((folder, feeds) in foldersAndFeeds) {
|
||||||
if (folder != null) {
|
if (folder != null) {
|
||||||
folder.accountId = account.id
|
folder.accountId = account.id
|
||||||
|
@ -103,10 +105,12 @@ abstract class BaseRepository(
|
||||||
|
|
||||||
feeds.forEach { it.folderId = folder?.id }
|
feeds.forEach { it.folderId = folder?.id }
|
||||||
|
|
||||||
insertNewFeeds(
|
errors += insertNewFeeds(
|
||||||
newFeeds = feeds,
|
newFeeds = feeds,
|
||||||
onUpdate = onUpdate
|
onUpdate = onUpdate
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return errors
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
package com.readrops.app.compose.repositories
|
package com.readrops.app.compose.repositories
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import com.readrops.api.localfeed.LocalRSSDataSource
|
import com.readrops.api.localfeed.LocalRSSDataSource
|
||||||
import com.readrops.api.services.SyncResult
|
import com.readrops.api.services.SyncResult
|
||||||
import com.readrops.api.utils.ApiUtils
|
import com.readrops.api.utils.ApiUtils
|
||||||
|
@ -69,20 +68,26 @@ class LocalRSSRepository(
|
||||||
throw NotImplementedError("This method can't be called here")
|
throw NotImplementedError("This method can't be called here")
|
||||||
|
|
||||||
|
|
||||||
override suspend fun insertNewFeeds(newFeeds: List<Feed>, onUpdate: (Feed) -> Unit) = withContext(Dispatchers.IO) {
|
override suspend fun insertNewFeeds(
|
||||||
for (newFeed in newFeeds) {
|
newFeeds: List<Feed>,
|
||||||
onUpdate(newFeed)
|
onUpdate: (Feed) -> Unit
|
||||||
|
): ErrorResult = withContext(Dispatchers.IO) {
|
||||||
|
val errors = mutableMapOf<Feed, Exception>()
|
||||||
|
|
||||||
try {
|
for (newFeed in newFeeds) {
|
||||||
val result = dataSource.queryRSSResource(newFeed.url!!, null)!!
|
onUpdate(newFeed)
|
||||||
insertFeed(result.first.also { it.folderId = newFeed.folderId })
|
|
||||||
} catch (e: Exception) {
|
try {
|
||||||
Log.d("LocalRSSRepository", e.message.orEmpty())
|
val result = dataSource.queryRSSResource(newFeed.url!!, null)!!
|
||||||
//throw e
|
insertFeed(result.first.also { it.folderId = newFeed.folderId })
|
||||||
}
|
} catch (e: Exception) {
|
||||||
|
errors[newFeed] = e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return@withContext errors
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun insertNewItems(items: List<Item>, feed: Feed) {
|
private suspend fun insertNewItems(items: List<Item>, feed: Feed) {
|
||||||
items.sortedWith(Item::compareTo) // TODO Check if ordering is useful in this situation
|
items.sortedWith(Item::compareTo) // TODO Check if ordering is useful in this situation
|
||||||
val itemsToInsert = mutableListOf<Item>()
|
val itemsToInsert = mutableListOf<Item>()
|
||||||
|
|
|
@ -11,16 +11,12 @@ 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
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.readrops.api.utils.exceptions.HttpException
|
|
||||||
import com.readrops.api.utils.exceptions.ParseException
|
|
||||||
import com.readrops.api.utils.exceptions.UnknownFormatException
|
|
||||||
import com.readrops.app.compose.R
|
import com.readrops.app.compose.R
|
||||||
import com.readrops.app.compose.repositories.ErrorResult
|
import com.readrops.app.compose.repositories.ErrorResult
|
||||||
import com.readrops.app.compose.util.components.BaseDialog
|
import com.readrops.app.compose.util.components.BaseDialog
|
||||||
|
import com.readrops.app.compose.util.components.errorText
|
||||||
import com.readrops.app.compose.util.theme.MediumSpacer
|
import com.readrops.app.compose.util.theme.MediumSpacer
|
||||||
import com.readrops.app.compose.util.theme.ShortSpacer
|
import com.readrops.app.compose.util.theme.ShortSpacer
|
||||||
import java.io.IOException
|
|
||||||
import java.net.UnknownHostException
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ErrorListDialog(
|
fun ErrorListDialog(
|
||||||
|
@ -54,14 +50,4 @@ fun ErrorListDialog(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// TODO check compatibility with other accounts errors
|
|
||||||
@Composable
|
|
||||||
fun errorText(exception: Exception) = when (exception) {
|
|
||||||
is HttpException -> stringResource(id = R.string.unreachable_feed_http_error, exception.code.toString())
|
|
||||||
is UnknownHostException -> stringResource(R.string.unreachable_feed)
|
|
||||||
is IOException -> stringResource(R.string.network_failure, exception.message.orEmpty())
|
|
||||||
is ParseException, is UnknownFormatException -> stringResource(R.string.processing_feed_error)
|
|
||||||
else -> "${exception.javaClass.simpleName}: ${exception.message}"
|
|
||||||
}
|
}
|
|
@ -32,7 +32,6 @@ import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
|
@ -61,7 +60,6 @@ import com.readrops.app.compose.util.theme.spacing
|
||||||
import com.readrops.db.filters.ListSortType
|
import com.readrops.db.filters.ListSortType
|
||||||
import com.readrops.db.filters.MainFilter
|
import com.readrops.db.filters.MainFilter
|
||||||
import com.readrops.db.filters.SubFilter
|
import com.readrops.db.filters.SubFilter
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
|
|
||||||
object TimelineTab : Tab {
|
object TimelineTab : Tab {
|
||||||
|
@ -78,7 +76,6 @@ object TimelineTab : Tab {
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val coroutineScope = rememberCoroutineScope()
|
|
||||||
|
|
||||||
val viewModel = getScreenModel<TimelineScreenModel>()
|
val viewModel = getScreenModel<TimelineScreenModel>()
|
||||||
val state by viewModel.timelineState.collectAsStateWithLifecycle()
|
val state by viewModel.timelineState.collectAsStateWithLifecycle()
|
||||||
|
@ -132,19 +129,20 @@ object TimelineTab : Tab {
|
||||||
|
|
||||||
LaunchedEffect(state.synchronizationErrors) {
|
LaunchedEffect(state.synchronizationErrors) {
|
||||||
if (state.synchronizationErrors != null) {
|
if (state.synchronizationErrors != null) {
|
||||||
coroutineScope.launch {
|
val action = snackbarHostState.showSnackbar(
|
||||||
val action = snackbarHostState.showSnackbar(
|
message = context.resources.getQuantityString(
|
||||||
message = context.resources.getQuantityString(R.plurals.error_occurred, state.synchronizationErrors!!.size),
|
R.plurals.error_occurred,
|
||||||
actionLabel = context.getString(R.string.details),
|
state.synchronizationErrors!!.size
|
||||||
duration = SnackbarDuration.Short
|
),
|
||||||
)
|
actionLabel = context.getString(R.string.details),
|
||||||
|
duration = SnackbarDuration.Short
|
||||||
|
)
|
||||||
|
|
||||||
if (action == SnackbarResult.ActionPerformed) {
|
if (action == SnackbarResult.ActionPerformed) {
|
||||||
viewModel.openDialog(DialogState.ErrorList(state.synchronizationErrors!!))
|
viewModel.openDialog(DialogState.ErrorList(state.synchronizationErrors!!))
|
||||||
} else {
|
} else {
|
||||||
// remove errors from state
|
// remove errors from state
|
||||||
viewModel.closeDialog(DialogState.ErrorList(state.synchronizationErrors!!))
|
viewModel.closeDialog(DialogState.ErrorList(state.synchronizationErrors!!))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -186,7 +184,7 @@ object TimelineTab : Tab {
|
||||||
is DialogState.ErrorList -> {
|
is DialogState.ErrorList -> {
|
||||||
ErrorListDialog(
|
ErrorListDialog(
|
||||||
errorResult = dialog.errorResult,
|
errorResult = dialog.errorResult,
|
||||||
onDismiss = { viewModel.closeDialog(state.dialog) }
|
onDismiss = { viewModel.closeDialog(dialog) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
package com.readrops.app.compose.util.components
|
||||||
|
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import com.readrops.api.utils.exceptions.HttpException
|
||||||
|
import com.readrops.api.utils.exceptions.ParseException
|
||||||
|
import com.readrops.api.utils.exceptions.UnknownFormatException
|
||||||
|
import com.readrops.app.compose.R
|
||||||
|
import java.io.IOException
|
||||||
|
import java.net.UnknownHostException
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ErrorDialog(
|
||||||
|
exception: Exception,
|
||||||
|
onDismiss: () -> Unit
|
||||||
|
) {
|
||||||
|
BaseDialog(
|
||||||
|
title = stringResource(id = R.string.error_occured),
|
||||||
|
icon = painterResource(id = R.drawable.ic_error),
|
||||||
|
onDismiss = onDismiss
|
||||||
|
) {
|
||||||
|
Text(text = errorText(exception = exception))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO check compatibility with other accounts errors
|
||||||
|
@Composable
|
||||||
|
fun errorText(exception: Exception) = when (exception) {
|
||||||
|
is HttpException -> stringResource(id = R.string.unreachable_feed_http_error, exception.code.toString())
|
||||||
|
is UnknownHostException -> stringResource(R.string.unreachable_feed)
|
||||||
|
is NoSuchFileException -> stringResource(R.string.unable_open_file)
|
||||||
|
is IOException -> stringResource(R.string.network_failure, exception.message.orEmpty())
|
||||||
|
is ParseException, is UnknownFormatException -> stringResource(R.string.processing_feed_error)
|
||||||
|
else -> "${exception.javaClass.simpleName}: ${exception.message}"
|
||||||
|
}
|
|
@ -156,4 +156,5 @@
|
||||||
<string name="network_failure">Erreur réseau: %1$s</string>
|
<string name="network_failure">Erreur réseau: %1$s</string>
|
||||||
<string name="processing_feed_error">Erreur de traitement du flux</string>
|
<string name="processing_feed_error">Erreur de traitement du flux</string>
|
||||||
<string name="unreachable_feed">Flux non attaignable</string>
|
<string name="unreachable_feed">Flux non attaignable</string>
|
||||||
|
<string name="unable_open_file">Impossible d\'ouvrir le fichier</string>
|
||||||
</resources>
|
</resources>
|
|
@ -162,4 +162,5 @@
|
||||||
<string name="network_failure">Network failure: %1$s</string>
|
<string name="network_failure">Network failure: %1$s</string>
|
||||||
<string name="processing_feed_error">Processing feed error</string>
|
<string name="processing_feed_error">Processing feed error</string>
|
||||||
<string name="unreachable_feed">Unreachable feed</string>
|
<string name="unreachable_feed">Unreachable feed</string>
|
||||||
|
<string name="unable_open_file">Unable to open file</string>
|
||||||
</resources>
|
</resources>
|
Loading…
Reference in New Issue