Add update and delete folder dialogs
This commit is contained in:
parent
a3f78094f1
commit
6f01333065
@ -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
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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("") }
|
||||
) {
|
@ -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) {
|
||||
|
@ -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,
|
||||
|
@ -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>>
|
||||
|
@ -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(
|
||||
|
Loading…
x
Reference in New Issue
Block a user