Add update and delete folder dialogs

This commit is contained in:
Shinokuni 2024-02-11 17:51:58 +01:00
parent a3f78094f1
commit 6f01333065
9 changed files with 158 additions and 55 deletions

View File

@ -56,9 +56,11 @@ data class UpdateFeedDialogState(
get() = accountType != null && !accountType.accountConfig!!.isFeedUrlEditable
}
data class AddFolderState(
val name: String = "",
data class AddUpdateFolderState(
val folder: Folder = Folder(),
val nameError: TextFieldError? = null,
) {
val name = folder.name
val isError = nameError != null
}

View File

@ -9,6 +9,7 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FloatingActionButton
@ -21,6 +22,7 @@ import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.platform.LocalUriHandler
@ -31,7 +33,7 @@ import cafe.adriel.voyager.navigator.tab.Tab
import cafe.adriel.voyager.navigator.tab.TabOptions
import com.readrops.app.compose.R
import com.readrops.app.compose.feeds.dialogs.AddFeedDialog
import com.readrops.app.compose.feeds.dialogs.AddFolderDialog
import com.readrops.app.compose.feeds.dialogs.AddUpdateFolderDialog
import com.readrops.app.compose.feeds.dialogs.DeleteFeedDialog
import com.readrops.app.compose.feeds.dialogs.FeedModalBottomSheet
import com.readrops.app.compose.feeds.dialogs.UpdateFeedDialog
@ -110,27 +112,54 @@ object FeedTab : Tab {
}
DialogState.AddFolder -> {
AddFolderDialog(
AddUpdateFolderDialog(
viewModel = viewModel,
onDismiss = {
viewModel.closeDialog()
viewModel.resetAddFolderState()
},
onValidate = {
viewModel.addFolderValidate()
}
)
}
is DialogState.DeleteFolder -> {
TwoChoicesDialog(
title = "Delete folder",
text = "Do you want to delete folder ${dialog.folder.name}?",
icon = rememberVectorPainter(image = Icons.Default.Delete),
confirmText = "Delete",
dismissText = "Cancel",
onDismiss = { viewModel.closeDialog() },
onConfirm = {
viewModel.deleteFolder(dialog.folder)
viewModel.closeDialog()
}
)
}
is DialogState.DeleteFolder -> {}
is DialogState.UpdateFolder -> {}
AddUpdateFolderDialog(
updateFolder = true,
viewModel = viewModel,
onDismiss = {
viewModel.closeDialog()
viewModel.resetAddFolderState()
},
onValidate = {
viewModel.updateFolderValidate()
}
)
}
null -> {}
}
Scaffold(
topBar = {
TopAppBar(
title = {
Text(text = "Feeds")
},
title = { Text(text = "Feeds") },
actions = {
IconButton(
onClick = {}
@ -187,25 +216,33 @@ object FeedTab : Tab {
items(
items = foldersAndFeeds.toList()
) { folderWithFeeds ->
fun onFeedLongClick(feed: Feed) {
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
uriHandler.openUri(feed.siteUrl!!)
}
if (folderWithFeeds.first != null) {
val folder = folderWithFeeds.first!!
FolderExpandableItem(
folder = folderWithFeeds.first!!,
folder = folder,
feeds = folderWithFeeds.second,
onFeedClick = { feed ->
viewModel.openDialog(
DialogState.FeedSheet(
feed,
folderWithFeeds.first
)
DialogState.FeedSheet(feed, folder)
)
},
onFeedLongClick = { feed -> onFeedLongClick(feed) }
onFeedLongClick = { feed -> onFeedLongClick(feed) },
onUpdateFolder = {
viewModel.openDialog(
DialogState.UpdateFolder(folder)
)
},
onDeleteFolder = {
viewModel.openDialog(
DialogState.DeleteFolder(folder)
)
}
)
} else {
val feeds = folderWithFeeds.second

View File

@ -36,7 +36,7 @@ class FeedViewModel(
private val _updateFeedDialogState = MutableStateFlow(UpdateFeedDialogState())
val updateFeedDialogState = _updateFeedDialogState.asStateFlow()
private val _addFolderState = MutableStateFlow(AddFolderState())
private val _addFolderState = MutableStateFlow(AddUpdateFolderState())
val addFolderState = _addFolderState.asStateFlow()
init {
@ -100,6 +100,14 @@ class FeedViewModel(
}
}
if (state is DialogState.UpdateFolder) {
_addFolderState.update {
it.copy(
folder = state.folder
)
}
}
_feedState.update { it.copy(dialog = state) }
}
@ -109,6 +117,12 @@ class FeedViewModel(
}
}
fun deleteFolder(folder: Folder) {
viewModelScope.launch(Dispatchers.IO) {
repository?.deleteFolder(folder)
}
}
// Add feed
fun setAddFeedDialogURL(url: String) {
@ -257,13 +271,43 @@ class FeedViewModel(
fun setFolderName(name: String) = _addFolderState.update {
it.copy(
name = name,
folder = it.folder.copy(name = name),
nameError = null,
)
}
fun addFolderValidate() {
val name = _addFolderState.value.name
val name = _addFolderState.value.name.orEmpty()
if (name.isEmpty()) {
_addFolderState.update { it.copy(nameError = TextFieldError.EmptyField) }
return
}
viewModelScope.launch(Dispatchers.IO) {
repository?.addFolder(_addFolderState.value.folder.apply { accountId = currentAccount!!.id })
closeDialog()
resetAddFolderState()
}
}
fun resetAddFolderState() {
_addFolderState.update {
it.copy(
folder = Folder(),
nameError = null,
)
}
}
// add folder
// update folder
fun updateFolderValidate() {
val name = _addFolderState.value.name.orEmpty()
if (name.isEmpty()) {
_addFolderState.update {
@ -274,22 +318,13 @@ class FeedViewModel(
}
viewModelScope.launch(Dispatchers.IO) {
repository?.addFolder(Folder(name = name, accountId = currentAccount?.id!!))
repository?.updateFolder(_addFolderState.value.folder)
closeDialog()
resetAddFolderState()
}
}
fun resetAddFolderState() {
_addFolderState.update {
it.copy(
name = "",
nameError = null,
)
}
}
// add folder
// update folder
}

View File

@ -2,17 +2,20 @@ package com.readrops.app.compose.feeds
import androidx.compose.animation.animateContentSize
import androidx.compose.animation.core.LinearOutSlowInEasing
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowDropDown
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@ -22,7 +25,6 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextOverflow
import com.readrops.app.compose.R
@ -37,12 +39,11 @@ fun FolderExpandableItem(
feeds: List<Feed>,
onFeedClick: (Feed) -> Unit,
onFeedLongClick: (Feed) -> Unit,
onUpdateFolder: () -> Unit,
onDeleteFolder: () -> Unit
) {
var isExpanded by remember { mutableStateOf(false) }
val rotationState by animateFloatAsState(
targetValue = if (isExpanded) 180f else 0f,
label = "folder item arrow rotation"
)
var isDropDownMenuExpanded by remember { mutableStateOf(false) }
Column(
modifier = Modifier
@ -85,13 +86,36 @@ fun FolderExpandableItem(
)
}
Row {
Icon(
imageVector = Icons.Default.ArrowDropDown,
contentDescription = null,
modifier = Modifier
.rotate(rotationState)
)
Box {
IconButton(
onClick = { isDropDownMenuExpanded = isDropDownMenuExpanded.not() }
) {
Icon(
imageVector = Icons.Default.MoreVert,
contentDescription = null,
)
}
DropdownMenu(
expanded = isDropDownMenuExpanded,
onDismissRequest = { isDropDownMenuExpanded = false }
) {
DropdownMenuItem(
text = { Text(text = "Update") },
onClick = {
isDropDownMenuExpanded = false
onUpdateFolder()
}
)
DropdownMenuItem(
text = { Text(text = "Delete") },
onClick = {
isDropDownMenuExpanded = false
onDeleteFolder()
}
)
}
}
}
}

View File

@ -15,27 +15,29 @@ import com.readrops.app.compose.feeds.FeedViewModel
import com.readrops.app.compose.util.components.BaseDialog
@Composable
fun AddFolderDialog(
fun AddUpdateFolderDialog(
updateFolder: Boolean = false,
viewModel: FeedViewModel,
onDismiss: () -> Unit
onDismiss: () -> Unit,
onValidate: () -> Unit
) {
val state by viewModel.addFolderState.collectAsStateWithLifecycle()
BaseDialog(
title = "Add Folder",
icon = painterResource(id = R.drawable.ic_new_folder),
onDismiss = { onDismiss() },
onValidate = { viewModel.addFolderValidate() }
title = if (updateFolder) "Update Folder" else "Add Folder",
icon = painterResource(id = if (updateFolder) R.drawable.ic_folder_grey else R.drawable.ic_new_folder),
onDismiss = onDismiss,
onValidate = onValidate
) {
OutlinedTextField(
value = state.name,
value = state.name.orEmpty(),
label = {
Text(text = "URL")
},
onValueChange = { viewModel.setFolderName(it) },
singleLine = true,
trailingIcon = {
if (state.name.isNotEmpty()) {
if (!state.name.isNullOrEmpty()) {
IconButton(
onClick = { viewModel.setFolderName("") }
) {

View File

@ -53,6 +53,8 @@ abstract class BaseRepository(
open suspend fun addFolder(folder: Folder) = database.newFolderDao().insert(folder)
open suspend fun updateFolder(folder: Folder) = database.newFolderDao().update(folder)
open suspend fun deleteFolder(folder: Folder) = database.newFolderDao().delete(folder)
open suspend fun setItemReadState(item: Item) {

View File

@ -18,7 +18,7 @@ class GetFoldersWithFeeds(
.selectFeedsWithoutFolder(accountId)
) { folders, feedsWithoutFolder ->
val foldersWithFeeds = folders.groupBy(
keySelector = { Folder(id = it.folderId, name = it.folderName) },
keySelector = { Folder(id = it.folderId, name = it.folderName, accountId = it.accountId) },
valueTransform = {
Feed(
id = it.feedId,

View File

@ -10,8 +10,8 @@ import kotlinx.coroutines.flow.Flow
abstract class NewFolderDao : NewBaseDao<Folder> {
@Query("Select Folder.id As folderId, Folder.name as folderName, Feed.id As feedId, Feed.name AS feedName, " +
"Feed.icon_url As feedIcon, Feed.url as feedUrl, Feed.siteUrl as feedSiteUrl, count(*) As unreadCount From Folder Left Join Feed " +
"On Folder.id = Feed.folder_id Left Join Item On Item.feed_id = Feed.id " +
"Feed.icon_url As feedIcon, Feed.url as feedUrl, Feed.siteUrl as feedSiteUrl, count(*) As unreadCount, Folder.account_id as accountId " +
"From Folder Left Join Feed On Folder.id = Feed.folder_id Left Join Item On Item.feed_id = Feed.id " +
"Where Feed.folder_id is NULL OR Feed.folder_id is NOT NULL And Item.read = 0 " +
"And Feed.account_id = :accountId Group By Feed.id, Folder.id Order By Folder.id")
abstract fun selectFoldersAndFeeds(accountId: Int): Flow<List<FolderWithFeed>>

View File

@ -20,7 +20,8 @@ data class FolderWithFeed(
val feedIcon: String? = null,
val feedUrl: String? = null,
val feedSiteUrl: String? = null,
val unreadCount: Int = 0
val unreadCount: Int = 0,
val accountId: Int = 0
)
data class FeedWithCount(