Add rename group and rename feed feature

This commit is contained in:
Ash 2022-04-07 04:02:13 +08:00
parent 0db39247e3
commit 263548749c
8 changed files with 324 additions and 141 deletions

View File

@ -1,21 +1,21 @@
package me.ash.reader.ui.page.home.drawer.feed
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import android.widget.Toast
import androidx.compose.foundation.layout.*
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.CreateNewFolder
import androidx.compose.material.icons.outlined.Edit
import androidx.compose.material.icons.rounded.RssFeed
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
@ -33,41 +33,46 @@ fun FeedOptionDrawer(
feedOptionViewModel: FeedOptionViewModel = hiltViewModel(),
content: @Composable () -> Unit = {},
) {
val context = LocalContext.current
val scope = rememberCoroutineScope()
val viewState = feedOptionViewModel.viewState.collectAsStateValue()
val feed = viewState.feed
val toastString = stringResource(R.string.rename_toast, viewState.newName)
BottomDrawer(
drawerState = viewState.drawerState,
sheetContent = {
Column {
Icon(
modifier = modifier
.roundClick { }
.fillMaxWidth()
.align(Alignment.CenterHorizontally),
imageVector = Icons.Rounded.RssFeed,
contentDescription = feed?.name
?: stringResource(R.string.unknown),
tint = MaterialTheme.colorScheme.onSurface
)
Spacer(modifier = modifier.height(16.dp))
Text(
modifier = Modifier
.roundClick {}
.fillMaxWidth(),
text = feed?.name ?: stringResource(R.string.unknown),
style = MaterialTheme.typography.headlineSmall,
textAlign = TextAlign.Center,
color = MaterialTheme.colorScheme.onSurface,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
Spacer(modifier = modifier.height(16.dp))
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Icon(
modifier = Modifier.roundClick { },
imageVector = Icons.Rounded.RssFeed,
contentDescription = feed?.name ?: stringResource(R.string.unknown),
tint = MaterialTheme.colorScheme.secondary,
)
Spacer(modifier = Modifier.height(16.dp))
Text(
modifier = Modifier.roundClick {
feedOptionViewModel.dispatch(FeedOptionViewAction.ShowRenameDialog)
},
text = feed?.name ?: stringResource(R.string.unknown),
style = MaterialTheme.typography.headlineSmall,
color = MaterialTheme.colorScheme.onSurface,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
Spacer(modifier = Modifier.height(16.dp))
ResultView(
link = feed?.url ?: stringResource(R.string.unknown),
groups = viewState.groups,
selectedAllowNotificationPreset = viewState.feed?.isNotification ?: false,
selectedParseFullContentPreset = viewState.feed?.isFullContent ?: false,
isMoveToGroup = true,
showUnsubscribe = true,
selectedGroupId = viewState.feed?.groupId ?: "",
allowNotificationPresetOnClick = {
@ -110,4 +115,23 @@ fun FeedOptionDrawer(
feedOptionViewModel.dispatch(FeedOptionViewAction.AddNewGroup)
}
)
TextFieldDialog(
visible = viewState.renameDialogVisible,
title = stringResource(R.string.rename),
icon = Icons.Outlined.Edit,
value = viewState.newName,
placeholder = stringResource(R.string.name),
onValueChange = {
feedOptionViewModel.dispatch(FeedOptionViewAction.InputNewName(it))
},
onDismissRequest = {
feedOptionViewModel.dispatch(FeedOptionViewAction.HideRenameDialog)
},
onConfirm = {
feedOptionViewModel.dispatch(FeedOptionViewAction.Rename)
feedOptionViewModel.dispatch(FeedOptionViewAction.Hide(scope))
Toast.makeText(context, toastString, Toast.LENGTH_SHORT).show()
}
)
}

View File

@ -57,6 +57,10 @@ class FeedOptionViewModel @Inject constructor(
is FeedOptionViewAction.AddNewGroup -> addNewGroup()
is FeedOptionViewAction.ShowNewGroupDialog -> changeNewGroupDialogVisible(true)
is FeedOptionViewAction.HideNewGroupDialog -> changeNewGroupDialogVisible(false)
is FeedOptionViewAction.InputNewName -> inputNewName(action.content)
is FeedOptionViewAction.Rename -> rename()
is FeedOptionViewAction.ShowRenameDialog -> changeRenameDialogVisible(true)
is FeedOptionViewAction.HideRenameDialog -> changeRenameDialogVisible(false)
}
}
@ -174,6 +178,40 @@ class FeedOptionViewModel @Inject constructor(
)
}
}
private fun rename() {
_viewState.value.feed?.let {
viewModelScope.launch {
rssRepository.get().updateFeed(
it.copy(
name = _viewState.value.newName
)
)
_viewState.update {
it.copy(
renameDialogVisible = false,
)
}
}
}
}
private fun changeRenameDialogVisible(visible: Boolean) {
_viewState.update {
it.copy(
renameDialogVisible = visible,
newName = if (visible) _viewState.value.feed?.name ?: "" else "",
)
}
}
private fun inputNewName(content: String) {
_viewState.update {
it.copy(
newName = content
)
}
}
}
@OptIn(ExperimentalMaterialApi::class)
@ -185,6 +223,8 @@ data class FeedOptionViewState(
val newGroupDialogVisible: Boolean = false,
val groups: List<Group> = emptyList(),
val deleteDialogVisible: Boolean = false,
val newName: String = "",
val renameDialogVisible: Boolean = false,
)
sealed class FeedOptionViewAction {
@ -218,4 +258,11 @@ sealed class FeedOptionViewAction {
object ShowNewGroupDialog : FeedOptionViewAction()
object HideNewGroupDialog : FeedOptionViewAction()
object AddNewGroup : FeedOptionViewAction()
object ShowRenameDialog : FeedOptionViewAction()
object HideRenameDialog : FeedOptionViewAction()
object Rename : FeedOptionViewAction()
data class InputNewName(
val content: String
) : FeedOptionViewAction()
}

View File

@ -1,5 +1,7 @@
package me.ash.reader.ui.page.home.drawer.group
import android.content.Context
import android.widget.Toast
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyRow
@ -9,12 +11,14 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Article
import androidx.compose.material.icons.outlined.Edit
import androidx.compose.material.icons.outlined.Folder
import androidx.compose.material.icons.outlined.Notifications
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
@ -26,9 +30,11 @@ import androidx.hilt.navigation.compose.hiltViewModel
import com.google.accompanist.flowlayout.FlowRow
import com.google.accompanist.flowlayout.MainAxisAlignment
import me.ash.reader.R
import me.ash.reader.data.entity.Group
import me.ash.reader.ui.component.BottomDrawer
import me.ash.reader.ui.component.SelectionChip
import me.ash.reader.ui.component.Subtitle
import me.ash.reader.ui.component.TextFieldDialog
import me.ash.reader.ui.ext.collectAsStateValue
import me.ash.reader.ui.ext.currentAccountId
import me.ash.reader.ui.ext.getDefaultGroupId
@ -38,40 +44,44 @@ import me.ash.reader.ui.ext.roundClick
@Composable
fun GroupOptionDrawer(
modifier: Modifier = Modifier,
GroupOptionViewModel: GroupOptionViewModel = hiltViewModel(),
groupOptionViewModel: GroupOptionViewModel = hiltViewModel(),
content: @Composable () -> Unit = {},
) {
val context = LocalContext.current
val viewState = GroupOptionViewModel.viewState.collectAsStateValue()
val scope = rememberCoroutineScope()
val viewState = groupOptionViewModel.viewState.collectAsStateValue()
val group = viewState.group
val toastString = stringResource(R.string.rename_toast, viewState.newName)
BottomDrawer(
drawerState = viewState.drawerState,
sheetContent = {
Column {
Icon(
modifier = modifier
.fillMaxWidth()
.align(Alignment.CenterHorizontally),
imageVector = Icons.Outlined.Folder,
contentDescription = group?.name ?: stringResource(R.string.unknown),
tint = MaterialTheme.colorScheme.onSurface
)
Spacer(modifier = modifier.height(16.dp))
Text(
modifier = Modifier
.roundClick {}
.fillMaxWidth(),
text = group?.name ?: stringResource(R.string.unknown),
style = MaterialTheme.typography.headlineSmall,
textAlign = TextAlign.Center,
color = MaterialTheme.colorScheme.onSurface,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
Spacer(modifier = modifier.height(16.dp))
Column(
modifier = modifier.verticalScroll(rememberScrollState())
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Icon(
imageVector = Icons.Outlined.Folder,
contentDescription = group?.name ?: stringResource(R.string.unknown),
tint = MaterialTheme.colorScheme.secondary,
)
Spacer(modifier = Modifier.height(16.dp))
Text(
modifier = Modifier.roundClick {
groupOptionViewModel.dispatch(GroupOptionViewAction.ShowRenameDialog)
},
text = group?.name ?: stringResource(R.string.unknown),
style = MaterialTheme.typography.headlineSmall,
color = MaterialTheme.colorScheme.onSurface,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
Spacer(modifier = Modifier.height(16.dp))
Column(
modifier = Modifier.verticalScroll(rememberScrollState())
) {
Row(
modifier = Modifier.fillMaxWidth(),
@ -84,98 +94,22 @@ fun GroupOptionDrawer(
textAlign = TextAlign.Center,
)
}
Spacer(modifier = Modifier.height(26.dp))
Spacer(modifier = Modifier.height(26.dp))
Subtitle(text = stringResource(R.string.preset))
Spacer(modifier = Modifier.height(10.dp))
FlowRow(
mainAxisAlignment = MainAxisAlignment.Start,
crossAxisSpacing = 10.dp,
mainAxisSpacing = 10.dp,
) {
SelectionChip(
modifier = Modifier.animateContentSize(),
content = stringResource(R.string.allow_notification),
selected = false,
selectedIcon = {
Icon(
imageVector = Icons.Outlined.Notifications,
contentDescription = stringResource(R.string.allow_notification),
modifier = Modifier
.padding(start = 8.dp)
.size(20.dp),
)
},
) {
GroupOptionViewModel.dispatch(GroupOptionViewAction.ShowAllAllowNotificationDialog)
}
SelectionChip(
modifier = Modifier.animateContentSize(),
content = stringResource(R.string.parse_full_content),
selected = false,
selectedIcon = {
Icon(
imageVector = Icons.Outlined.Article,
contentDescription = stringResource(R.string.parse_full_content),
modifier = Modifier
.padding(start = 8.dp)
.size(20.dp),
)
},
) {
GroupOptionViewModel.dispatch(GroupOptionViewAction.ShowAllParseFullContentDialog)
}
if (group?.id != context.currentAccountId.getDefaultGroupId()) {
SelectionChip(
modifier = Modifier.animateContentSize(),
content = stringResource(R.string.delete_group),
selected = false,
) {
GroupOptionViewModel.dispatch(GroupOptionViewAction.ShowDeleteDialog)
}
}
}
Spacer(modifier = Modifier.height(26.dp))
Subtitle(text = stringResource(R.string.move_to_group))
Spacer(modifier = Modifier.height(10.dp))
Preset(groupOptionViewModel, group, context)
if (viewState.groups.size > 6) {
LazyRow {
items(viewState.groups) {
if (it.id != group?.id) {
SelectionChip(
modifier = Modifier.animateContentSize(),
content = it.name,
selected = false,
) {
GroupOptionViewModel.dispatch(
GroupOptionViewAction.ShowAllMoveToGroupDialog(it)
)
}
}
Spacer(modifier = Modifier.width(10.dp))
}
}
} else {
FlowRow(
mainAxisAlignment = MainAxisAlignment.Start,
crossAxisSpacing = 10.dp,
mainAxisSpacing = 10.dp,
) {
viewState.groups.forEach {
if (it.id != group?.id) {
SelectionChip(
modifier = Modifier.animateContentSize(),
content = it.name,
selected = false,
) {
GroupOptionViewModel.dispatch(
GroupOptionViewAction.ShowAllMoveToGroupDialog(it)
)
}
}
}
if (viewState.groups.size != 1) {
Spacer(modifier = Modifier.height(26.dp))
Subtitle(text = stringResource(R.string.move_to_group))
Spacer(modifier = Modifier.height(10.dp))
if (viewState.groups.size > 6) {
LazyRowGroups(viewState, group, groupOptionViewModel)
} else {
FlowRowGroups(viewState, group, groupOptionViewModel)
}
}
@ -191,4 +125,128 @@ fun GroupOptionDrawer(
AllAllowNotificationDialog(groupName = group?.name ?: "")
AllParseFullContentDialog(groupName = group?.name ?: "")
AllMoveToGroupDialog(groupName = group?.name ?: "")
TextFieldDialog(
visible = viewState.renameDialogVisible,
title = stringResource(R.string.rename),
icon = Icons.Outlined.Edit,
value = viewState.newName,
placeholder = stringResource(R.string.name),
onValueChange = {
groupOptionViewModel.dispatch(GroupOptionViewAction.InputNewName(it))
},
onDismissRequest = {
groupOptionViewModel.dispatch(GroupOptionViewAction.HideRenameDialog)
},
onConfirm = {
groupOptionViewModel.dispatch(GroupOptionViewAction.Rename)
groupOptionViewModel.dispatch(GroupOptionViewAction.Hide(scope))
Toast.makeText(context, toastString, Toast.LENGTH_SHORT).show()
}
)
}
@Composable
private fun Preset(
groupOptionViewModel: GroupOptionViewModel,
group: Group?,
context: Context
) {
FlowRow(
mainAxisAlignment = MainAxisAlignment.Start,
crossAxisSpacing = 10.dp,
mainAxisSpacing = 10.dp,
) {
SelectionChip(
modifier = Modifier.animateContentSize(),
content = stringResource(R.string.allow_notification),
selected = false,
selectedIcon = {
Icon(
imageVector = Icons.Outlined.Notifications,
contentDescription = stringResource(R.string.allow_notification),
modifier = Modifier
.padding(start = 8.dp)
.size(20.dp),
)
},
) {
groupOptionViewModel.dispatch(GroupOptionViewAction.ShowAllAllowNotificationDialog)
}
SelectionChip(
modifier = Modifier.animateContentSize(),
content = stringResource(R.string.parse_full_content),
selected = false,
selectedIcon = {
Icon(
imageVector = Icons.Outlined.Article,
contentDescription = stringResource(R.string.parse_full_content),
modifier = Modifier
.padding(start = 8.dp)
.size(20.dp),
)
},
) {
groupOptionViewModel.dispatch(GroupOptionViewAction.ShowAllParseFullContentDialog)
}
if (group?.id != context.currentAccountId.getDefaultGroupId()) {
SelectionChip(
modifier = Modifier.animateContentSize(),
content = stringResource(R.string.delete_group),
selected = false,
) {
groupOptionViewModel.dispatch(GroupOptionViewAction.ShowDeleteDialog)
}
}
}
}
@Composable
private fun FlowRowGroups(
viewState: GroupOptionViewState,
group: Group?,
groupOptionViewModel: GroupOptionViewModel
) {
FlowRow(
mainAxisAlignment = MainAxisAlignment.Start,
crossAxisSpacing = 10.dp,
mainAxisSpacing = 10.dp,
) {
viewState.groups.forEach {
if (it.id != group?.id) {
SelectionChip(
modifier = Modifier.animateContentSize(),
content = it.name,
selected = false,
) {
groupOptionViewModel.dispatch(
GroupOptionViewAction.ShowAllMoveToGroupDialog(it)
)
}
}
}
}
}
@Composable
private fun LazyRowGroups(
viewState: GroupOptionViewState,
group: Group?,
groupOptionViewModel: GroupOptionViewModel
) {
LazyRow {
items(viewState.groups) {
if (it.id != group?.id) {
SelectionChip(
modifier = Modifier.animateContentSize(),
content = it.name,
selected = false,
) {
groupOptionViewModel.dispatch(
GroupOptionViewAction.ShowAllMoveToGroupDialog(it)
)
}
}
Spacer(modifier = Modifier.width(10.dp))
}
}
}

View File

@ -70,6 +70,11 @@ class GroupOptionViewModel @Inject constructor(
changeAllMoveToGroupDialogVisible(visible = false)
is GroupOptionViewAction.AllMoveToGroup ->
allMoveToGroup(action.callback)
is GroupOptionViewAction.InputNewName -> inputNewName(action.content)
is GroupOptionViewAction.Rename -> rename()
is GroupOptionViewAction.ShowRenameDialog -> changeRenameDialogVisible(true)
is GroupOptionViewAction.HideRenameDialog -> changeRenameDialogVisible(false)
}
}
@ -173,6 +178,40 @@ class GroupOptionViewModel @Inject constructor(
)
}
}
private fun rename() {
_viewState.value.group?.let {
viewModelScope.launch {
rssRepository.get().updateGroup(
it.copy(
name = _viewState.value.newName
)
)
_viewState.update {
it.copy(
renameDialogVisible = false,
)
}
}
}
}
private fun changeRenameDialogVisible(visible: Boolean) {
_viewState.update {
it.copy(
renameDialogVisible = visible,
newName = if (visible) _viewState.value.group?.name ?: "" else "",
)
}
}
private fun inputNewName(content: String) {
_viewState.update {
it.copy(
newName = content
)
}
}
}
@OptIn(ExperimentalMaterialApi::class)
@ -185,6 +224,8 @@ data class GroupOptionViewState(
val allParseFullContentDialogVisible: Boolean = false,
val allMoveToGroupDialogVisible: Boolean = false,
val deleteDialogVisible: Boolean = false,
val newName: String = "",
val renameDialogVisible: Boolean = false,
)
sealed class GroupOptionViewAction {
@ -229,4 +270,11 @@ sealed class GroupOptionViewAction {
) : GroupOptionViewAction()
object HideAllMoveToGroupDialog : GroupOptionViewAction()
object ShowRenameDialog : GroupOptionViewAction()
object HideRenameDialog : GroupOptionViewAction()
object Rename : GroupOptionViewAction()
data class InputNewName(
val content: String
) : GroupOptionViewAction()
}

View File

@ -43,6 +43,7 @@ fun ResultView(
groups: List<Group> = emptyList(),
selectedAllowNotificationPreset: Boolean = false,
selectedParseFullContentPreset: Boolean = false,
isMoveToGroup: Boolean = false,
showUnsubscribe: Boolean = false,
selectedGroupId: String = "",
allowNotificationPresetOnClick: () -> Unit = {},
@ -72,6 +73,7 @@ fun ResultView(
Spacer(modifier = Modifier.height(26.dp))
AddToGroup(
isMoveToGroup = isMoveToGroup,
groups = groups,
selectedGroupId = selectedGroupId,
onGroupClick = onGroupClick,
@ -169,12 +171,13 @@ private fun Preset(
@Composable
private fun AddToGroup(
isMoveToGroup: Boolean = false,
groups: List<Group>,
selectedGroupId: String,
onGroupClick: (groupId: String) -> Unit = {},
onAddNewGroup: () -> Unit = {},
) {
Subtitle(text = stringResource(R.string.add_to_group))
Subtitle(text = stringResource(if (isMoveToGroup) R.string.move_to_group else R.string.add_to_group))
Spacer(modifier = Modifier.height(10.dp))
if (groups.size > 6) {

View File

@ -72,7 +72,6 @@ fun SettingsPage(
}
item {
SelectableSettingGroupItem(
selected = true,
title = stringResource(R.string.color_and_style),
desc = stringResource(R.string.color_and_style_desc),
icon = Icons.Outlined.Palette,

View File

@ -42,6 +42,8 @@
<string name="move_to_group">移动到组</string>
<string name="all_move_to_group_tip">将 \"%1$s\" 分组中的所有订阅源移动至 \"%2$s\" 分组。</string>
<string name="all_move_to_group_toast">已全部移动至 \"%1$s\" 分组</string>
<string name="rename">重命名</string>
<string name="rename_toast">已重命名为 \"%1$s\"</string>
<string name="create_new_group">新建分组</string>
<string name="name">名称</string>
<string name="open_with">打开 %1$s</string>

View File

@ -42,6 +42,8 @@
<string name="move_to_group">Move to Group</string>
<string name="all_move_to_group_tip">Move all feeds in the \"%1$s\" group to the \"%2$s\" group.</string>
<string name="all_move_to_group_toast">Moved all to \"%1$s\" group</string>
<string name="rename">Rename</string>
<string name="rename_toast">Renamed to \"%1$s\"</string>
<string name="create_new_group">Create New Group</string>
<string name="name">Name</string>
<string name="open_with">Open %1$s</string>