Add progress indicator in AddFeedDialog and fix account selection bug

This commit is contained in:
Shinokuni 2024-07-11 17:23:54 +02:00
parent aa3a835756
commit 6cb8c2853d
6 changed files with 75 additions and 31 deletions

View File

@ -1,12 +1,16 @@
package com.readrops.app.feeds package com.readrops.app.feeds
import android.content.Context import android.content.Context
import android.content.SharedPreferences
import android.util.Patterns import android.util.Patterns
import cafe.adriel.voyager.core.model.screenModelScope import cafe.adriel.voyager.core.model.screenModelScope
import com.readrops.api.localfeed.LocalRSSDataSource import com.readrops.api.localfeed.LocalRSSDataSource
import com.readrops.api.services.Credentials
import com.readrops.api.utils.AuthInterceptor
import com.readrops.api.utils.HtmlParser import com.readrops.api.utils.HtmlParser
import com.readrops.app.R import com.readrops.app.R
import com.readrops.app.base.TabScreenModel import com.readrops.app.base.TabScreenModel
import com.readrops.app.repositories.BaseRepository
import com.readrops.app.repositories.GetFoldersWithFeeds import com.readrops.app.repositories.GetFoldersWithFeeds
import com.readrops.app.util.components.TextFieldError import com.readrops.app.util.components.TextFieldError
import com.readrops.app.util.components.dialog.TextFieldDialogState import com.readrops.app.util.components.dialog.TextFieldDialogState
@ -26,6 +30,7 @@ import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
import org.koin.core.component.get import org.koin.core.component.get
import org.koin.core.parameter.parametersOf
import java.net.UnknownHostException import java.net.UnknownHostException
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
@ -134,7 +139,8 @@ class FeedScreenModel(
it.copy( it.copy(
url = "", url = "",
error = null, error = null,
exception = null exception = null,
isLoading = false
) )
} }
} }
@ -240,6 +246,8 @@ class FeedScreenModel(
} }
else -> screenModelScope.launch(dispatcher) { else -> screenModelScope.launch(dispatcher) {
_addFeedDialogState.update { it.copy(exception = null, isLoading = true) }
try { try {
if (localRSSDataSource.isUrlRSSResource(url)) { if (localRSSDataSource.isUrlRSSResource(url)) {
insertFeeds(listOf(Feed(url = url))) insertFeeds(listOf(Feed(url = url)))
@ -248,7 +256,7 @@ class FeedScreenModel(
if (rssUrls.isEmpty()) { if (rssUrls.isEmpty()) {
_addFeedDialogState.update { _addFeedDialogState.update {
it.copy(error = TextFieldError.NoRSSFeed) it.copy(error = TextFieldError.NoRSSFeed, isLoading = false)
} }
} else { } else {
insertFeeds(rssUrls.map { Feed(url = it.url) }) insertFeeds(rssUrls.map { Feed(url = it.url) })
@ -256,8 +264,19 @@ class FeedScreenModel(
} }
} catch (e: Exception) { } catch (e: Exception) {
when (e) { when (e) {
is UnknownHostException -> _addFeedDialogState.update { it.copy(error = TextFieldError.UnreachableUrl) } is UnknownHostException -> _addFeedDialogState.update {
else -> _addFeedDialogState.update { it.copy(error = TextFieldError.NoRSSFeed) } it.copy(
error = TextFieldError.UnreachableUrl,
isLoading = false
)
}
else -> _addFeedDialogState.update {
it.copy(
error = TextFieldError.NoRSSFeed,
isLoading = false
)
}
} }
} }
} }
@ -265,15 +284,34 @@ class FeedScreenModel(
} }
private suspend fun insertFeeds(feeds: List<Feed>) { private suspend fun insertFeeds(feeds: List<Feed>) {
val errors = repository?.insertNewFeeds( val selectedAccount = _addFeedDialogState.value.selectedAccount
if (!selectedAccount.isLocal) {
get<SharedPreferences>().apply {
selectedAccount.login = getString(selectedAccount.loginKey, null)
selectedAccount.password = getString(selectedAccount.passwordKey, null)
}
get<AuthInterceptor>().apply {
credentials = Credentials.toCredentials(selectedAccount)
}
}
val repository = get<BaseRepository> { parametersOf(selectedAccount) }
val errors = repository.insertNewFeeds(
newFeeds = feeds, newFeeds = feeds,
onUpdate = { /* no need of this here */ } onUpdate = { /* TODO */ }
)!! )
if (errors.isEmpty()) { if (errors.isEmpty()) {
closeDialog(DialogState.AddFeed) closeDialog(DialogState.AddFeed)
} else { } else {
_addFeedDialogState.update { it.copy(exception = errors.values.first()) } _addFeedDialogState.update {
it.copy(
exception = errors.values.first(),
isLoading = false
)
}
} }
} }
@ -356,7 +394,12 @@ class FeedScreenModel(
) )
) )
} catch (e: Exception) { } catch (e: Exception) {
_updateFeedDialogState.update { it.copy(exception = e, isLoading = false) } _updateFeedDialogState.update {
it.copy(
exception = e,
isLoading = false
)
}
return@launch return@launch
} }
} }

View File

@ -14,8 +14,8 @@ data class FeedState(
) )
sealed interface DialogState { sealed interface DialogState {
object AddFeed : DialogState data object AddFeed : DialogState
object AddFolder : DialogState data object AddFolder : DialogState
class DeleteFeed(val feed: Feed) : DialogState class DeleteFeed(val feed: Feed) : DialogState
class DeleteFolder(val folder: Folder) : DialogState class DeleteFolder(val folder: Folder) : DialogState
class UpdateFeed(val feed: Feed, val folder: Folder?) : DialogState class UpdateFeed(val feed: Feed, val folder: Folder?) : DialogState
@ -24,7 +24,7 @@ sealed interface DialogState {
} }
sealed class FolderAndFeedsState { sealed class FolderAndFeedsState {
object InitialState : FolderAndFeedsState() data object InitialState : FolderAndFeedsState()
data class ErrorState(val exception: Exception) : FolderAndFeedsState() data class ErrorState(val exception: Exception) : FolderAndFeedsState()
data class LoadedState(val values: Map<Folder?, List<Feed>>) : FolderAndFeedsState() data class LoadedState(val values: Map<Folder?, List<Feed>>) : FolderAndFeedsState()
} }
@ -34,7 +34,8 @@ data class AddFeedDialogState(
val selectedAccount: Account = Account(accountName = ""), val selectedAccount: Account = Account(accountName = ""),
val accounts: List<Account> = listOf(), val accounts: List<Account> = listOf(),
val error: TextFieldError? = null, val error: TextFieldError? = null,
val exception: Exception? = null val exception: Exception? = null,
val isLoading: Boolean = false
) { ) {
val isError: Boolean get() = error != null val isError: Boolean get() = error != null
} }

View File

@ -236,7 +236,7 @@ object FeedTab : Tab {
when (val dialog = state.dialog) { when (val dialog = state.dialog) {
is DialogState.AddFeed -> { is DialogState.AddFeed -> {
AddFeedDialog( AddFeedDialog(
viewModel = screenModel, screenModel = screenModel,
onDismiss = { onDismiss = {
screenModel.closeDialog(DialogState.AddFeed) screenModel.closeDialog(DialogState.AddFeed)
}, },

View File

@ -13,7 +13,6 @@ import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@ -30,6 +29,7 @@ import com.readrops.app.R
import com.readrops.app.account.selection.adaptiveIconPainterResource import com.readrops.app.account.selection.adaptiveIconPainterResource
import com.readrops.app.feeds.FeedScreenModel import com.readrops.app.feeds.FeedScreenModel
import com.readrops.app.util.ErrorMessage import com.readrops.app.util.ErrorMessage
import com.readrops.app.util.components.LoadingTextButton
import com.readrops.app.util.components.dialog.BaseDialog import com.readrops.app.util.components.dialog.BaseDialog
import com.readrops.app.util.theme.LargeSpacer import com.readrops.app.util.theme.LargeSpacer
import com.readrops.app.util.theme.MediumSpacer import com.readrops.app.util.theme.MediumSpacer
@ -38,29 +38,27 @@ import com.readrops.app.util.theme.ShortSpacer
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun AddFeedDialog( fun AddFeedDialog(
viewModel: FeedScreenModel, screenModel: FeedScreenModel,
onDismiss: () -> Unit, onDismiss: () -> Unit,
) { ) {
val state by viewModel.addFeedDialogState.collectAsStateWithLifecycle() val state by screenModel.addFeedDialogState.collectAsStateWithLifecycle()
var isExpanded by remember { mutableStateOf(false) } var isExpanded by remember { mutableStateOf(false) }
BaseDialog( BaseDialog(
title = stringResource(R.string.add_feed_item), title = stringResource(R.string.add_feed_item),
icon = painterResource(id = R.drawable.ic_rss_feed_grey), icon = painterResource(id = R.drawable.ic_rss_feed_grey),
onDismiss = onDismiss onDismiss = { if (!state.isLoading) onDismiss() }
) { ) {
OutlinedTextField( OutlinedTextField(
value = state.url, value = state.url,
label = { label = { Text(text = stringResource(id = R.string.url)) },
Text(text = "URL") onValueChange = { screenModel.setAddFeedDialogURL(it) },
},
onValueChange = { viewModel.setAddFeedDialogURL(it) },
singleLine = true, singleLine = true,
trailingIcon = { trailingIcon = {
if (state.url.isNotEmpty()) { if (state.url.isNotEmpty()) {
IconButton( IconButton(
onClick = { viewModel.setAddFeedDialogURL("") } onClick = { screenModel.setAddFeedDialogURL("") }
) { ) {
Icon( Icon(
imageVector = Icons.Default.Clear, imageVector = Icons.Default.Clear,
@ -77,7 +75,7 @@ fun AddFeedDialog(
ExposedDropdownMenuBox( ExposedDropdownMenuBox(
expanded = isExpanded, expanded = isExpanded,
onExpandedChange = { isExpanded = isExpanded.not() } onExpandedChange = { isExpanded = !isExpanded }
) { ) {
ExposedDropdownMenu( ExposedDropdownMenu(
expanded = isExpanded, expanded = isExpanded,
@ -88,12 +86,12 @@ fun AddFeedDialog(
text = { Text(text = account.accountName!!) }, text = { Text(text = account.accountName!!) },
onClick = { onClick = {
isExpanded = false isExpanded = false
viewModel.setAddFeedDialogSelectedAccount(account) screenModel.setAddFeedDialogSelectedAccount(account)
}, },
leadingIcon = { leadingIcon = {
Image( Image(
painter = adaptiveIconPainterResource( painter = adaptiveIconPainterResource(
id = state.selectedAccount.accountType!!.iconRes id = account.accountType!!.iconRes
), ),
contentDescription = null, contentDescription = null,
modifier = Modifier.size(24.dp) modifier = Modifier.size(24.dp)
@ -135,10 +133,10 @@ fun AddFeedDialog(
LargeSpacer() LargeSpacer()
TextButton( LoadingTextButton(
onClick = { viewModel.addFeedDialogValidate() }, text = stringResource(id = R.string.validate),
) { isLoading = state.isLoading,
Text(text = stringResource(R.string.validate)) onClick = { screenModel.addFeedDialogValidate() },
} )
} }
} }

View File

@ -174,4 +174,5 @@
<string name="update">Mettre à jour</string> <string name="update">Mettre à jour</string>
<string name="rename_account">Renommer le compte</string> <string name="rename_account">Renommer le compte</string>
<string name="name">Nom</string> <string name="name">Nom</string>
<string name="url">URL</string>
</resources> </resources>

View File

@ -183,4 +183,5 @@
<string name="update">Update</string> <string name="update">Update</string>
<string name="rename_account">Rename account</string> <string name="rename_account">Rename account</string>
<string name="name">Name</string> <string name="name">Name</string>
<string name="url">URL</string>
</resources> </resources>