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
import android.content.Context
import android.content.SharedPreferences
import android.util.Patterns
import cafe.adriel.voyager.core.model.screenModelScope
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.app.R
import com.readrops.app.base.TabScreenModel
import com.readrops.app.repositories.BaseRepository
import com.readrops.app.repositories.GetFoldersWithFeeds
import com.readrops.app.util.components.TextFieldError
import com.readrops.app.util.components.dialog.TextFieldDialogState
@ -26,6 +30,7 @@ import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import org.koin.core.component.KoinComponent
import org.koin.core.component.get
import org.koin.core.parameter.parametersOf
import java.net.UnknownHostException
@OptIn(ExperimentalCoroutinesApi::class)
@ -134,7 +139,8 @@ class FeedScreenModel(
it.copy(
url = "",
error = null,
exception = null
exception = null,
isLoading = false
)
}
}
@ -240,6 +246,8 @@ class FeedScreenModel(
}
else -> screenModelScope.launch(dispatcher) {
_addFeedDialogState.update { it.copy(exception = null, isLoading = true) }
try {
if (localRSSDataSource.isUrlRSSResource(url)) {
insertFeeds(listOf(Feed(url = url)))
@ -248,7 +256,7 @@ class FeedScreenModel(
if (rssUrls.isEmpty()) {
_addFeedDialogState.update {
it.copy(error = TextFieldError.NoRSSFeed)
it.copy(error = TextFieldError.NoRSSFeed, isLoading = false)
}
} else {
insertFeeds(rssUrls.map { Feed(url = it.url) })
@ -256,8 +264,19 @@ class FeedScreenModel(
}
} catch (e: Exception) {
when (e) {
is UnknownHostException -> _addFeedDialogState.update { it.copy(error = TextFieldError.UnreachableUrl) }
else -> _addFeedDialogState.update { it.copy(error = TextFieldError.NoRSSFeed) }
is UnknownHostException -> _addFeedDialogState.update {
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>) {
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,
onUpdate = { /* no need of this here */ }
)!!
onUpdate = { /* TODO */ }
)
if (errors.isEmpty()) {
closeDialog(DialogState.AddFeed)
} 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) {
_updateFeedDialogState.update { it.copy(exception = e, isLoading = false) }
_updateFeedDialogState.update {
it.copy(
exception = e,
isLoading = false
)
}
return@launch
}
}

View File

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

View File

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

View File

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

View File

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

View File

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