Add progress indicator in AddFeedDialog and fix account selection bug
This commit is contained in:
parent
aa3a835756
commit
6cb8c2853d
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
},
|
},
|
||||||
|
@ -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() },
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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>
|
@ -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>
|
Loading…
x
Reference in New Issue
Block a user