Add clear articles feature for feeds and groups (#49)

* Add clear articles in feeds and groups feature

* Fix the display of ClearArticles button
This commit is contained in:
Ashinch 2022-05-06 05:57:49 +08:00 committed by GitHub
parent 4d2d857676
commit 5e569d1303
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 291 additions and 22 deletions

View File

@ -145,16 +145,23 @@ abstract class AbstractRssRepository constructor(
}
suspend fun deleteGroup(group: Group) {
articleDao.deleteByGroupId(context.currentAccountId, group.id)
deleteArticles(group = group)
feedDao.deleteByGroupId(context.currentAccountId, group.id)
groupDao.delete(group)
}
suspend fun deleteFeed(feed: Feed) {
articleDao.deleteByFeedId(context.currentAccountId, feed.id)
deleteArticles(feed = feed)
feedDao.delete(feed)
}
suspend fun deleteArticles(group: Group? = null, feed: Feed? = null) {
when {
group != null -> articleDao.deleteByGroupId(context.currentAccountId, group.id)
feed != null -> articleDao.deleteByFeedId(context.currentAccountId, feed.id)
}
}
suspend fun groupParseFullContent(group: Group, isFullContent: Boolean) {
feedDao.updateIsFullContentByGroupId(context.currentAccountId, group.id, isFullContent)
}

View File

@ -253,8 +253,8 @@ fun FeedsPage(
// }
}
item {
Spacer(modifier = Modifier.height(64.dp))
Spacer(modifier = Modifier.height(64.dp))
Spacer(modifier = Modifier.height(128.dp))
Spacer(modifier = Modifier.windowInsetsBottomHeight(WindowInsets.navigationBars))
}
}
},

View File

@ -0,0 +1,76 @@
package me.ash.reader.ui.page.home.feeds.option.feed
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.DeleteForever
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import com.google.accompanist.pager.ExperimentalPagerApi
import me.ash.reader.R
import me.ash.reader.ui.component.Dialog
import me.ash.reader.ui.ext.collectAsStateValue
import me.ash.reader.ui.ext.showToast
@OptIn(ExperimentalPagerApi::class)
@Composable
fun ClearFeedDialog(
modifier: Modifier = Modifier,
feedName: String,
viewModel: FeedOptionViewModel = hiltViewModel(),
) {
val context = LocalContext.current
val viewState = viewModel.viewState.collectAsStateValue()
val scope = rememberCoroutineScope()
val toastString = stringResource(R.string.clear_articles_in_feed_toast, feedName)
Dialog(
visible = viewState.clearDialogVisible,
onDismissRequest = {
viewModel.dispatch(FeedOptionViewAction.HideClearDialog)
},
icon = {
Icon(
imageVector = Icons.Outlined.DeleteForever,
contentDescription = stringResource(R.string.clear_articles),
)
},
title = {
Text(text = stringResource(R.string.clear_articles))
},
text = {
Text(text = stringResource(R.string.clear_articles_feed_tips, feedName))
},
confirmButton = {
TextButton(
onClick = {
viewModel.dispatch(FeedOptionViewAction.Clear {
viewModel.dispatch(FeedOptionViewAction.HideClearDialog)
viewModel.dispatch(FeedOptionViewAction.Hide(scope))
context.showToast(toastString)
})
}
) {
Text(
text = stringResource(R.string.clear),
)
}
},
dismissButton = {
TextButton(
onClick = {
viewModel.dispatch(FeedOptionViewAction.HideClearDialog)
}
) {
Text(
text = stringResource(R.string.cancel),
)
}
},
)
}

View File

@ -44,7 +44,7 @@ fun DeleteFeedDialog(
Text(text = stringResource(R.string.unsubscribe))
},
text = {
Text(text = stringResource(R.string.unsubscribe_tip, feedName))
Text(text = stringResource(R.string.unsubscribe_tips, feedName))
},
confirmButton = {
TextButton(

View File

@ -89,6 +89,9 @@ fun FeedOptionDrawer(
parseFullContentPresetOnClick = {
feedOptionViewModel.dispatch(FeedOptionViewAction.ChangeParseFullContentPreset)
},
clearArticlesOnClick = {
feedOptionViewModel.dispatch(FeedOptionViewAction.ShowClearDialog)
},
unsubscribeOnClick = {
feedOptionViewModel.dispatch(FeedOptionViewAction.ShowDeleteDialog)
},
@ -110,6 +113,8 @@ fun FeedOptionDrawer(
DeleteFeedDialog(feedName = feed?.name ?: "")
ClearFeedDialog(feedName = feed?.name ?: "")
TextFieldDialog(
visible = viewState.newGroupDialogVisible,
title = stringResource(R.string.create_new_group),

View File

@ -54,6 +54,9 @@ class FeedOptionViewModel @Inject constructor(
is FeedOptionViewAction.ShowDeleteDialog -> showDeleteDialog()
is FeedOptionViewAction.HideDeleteDialog -> hideDeleteDialog()
is FeedOptionViewAction.Delete -> delete(action.callback)
is FeedOptionViewAction.ShowClearDialog -> showClearDialog()
is FeedOptionViewAction.HideClearDialog -> hideClearDialog()
is FeedOptionViewAction.Clear -> clear(action.callback)
is FeedOptionViewAction.AddNewGroup -> addNewGroup()
is FeedOptionViewAction.ShowNewGroupDialog -> changeNewGroupDialogVisible(true)
is FeedOptionViewAction.HideNewGroupDialog -> changeNewGroupDialogVisible(false)
@ -183,6 +186,33 @@ class FeedOptionViewModel @Inject constructor(
}
}
private fun showClearDialog() {
_viewState.update {
it.copy(
clearDialogVisible = true,
)
}
}
private fun hideClearDialog() {
_viewState.update {
it.copy(
clearDialogVisible = false,
)
}
}
private fun clear(callback: () -> Unit = {}) {
_viewState.value.feed?.let {
viewModelScope.launch(Dispatchers.IO) {
rssRepository.get().deleteArticles(feed = it)
withContext(Dispatchers.Main) {
callback()
}
}
}
}
private fun rename() {
_viewState.value.feed?.let {
viewModelScope.launch {
@ -261,6 +291,7 @@ data class FeedOptionViewState(
val newGroupDialogVisible: Boolean = false,
val groups: List<Group> = emptyList(),
val deleteDialogVisible: Boolean = false,
val clearDialogVisible: Boolean = false,
val newName: String = "",
val renameDialogVisible: Boolean = false,
val newUrl: String = "",
@ -295,6 +326,13 @@ sealed class FeedOptionViewAction {
object ShowDeleteDialog : FeedOptionViewAction()
object HideDeleteDialog : FeedOptionViewAction()
data class Clear(
val callback: () -> Unit = {}
) : FeedOptionViewAction()
object ShowClearDialog : FeedOptionViewAction()
object HideClearDialog : FeedOptionViewAction()
object ShowNewGroupDialog : FeedOptionViewAction()
object HideNewGroupDialog : FeedOptionViewAction()
object AddNewGroup : FeedOptionViewAction()

View File

@ -45,7 +45,7 @@ fun AllAllowNotificationDialog(
Text(text = stringResource(R.string.allow_notification))
},
text = {
Text(text = stringResource(R.string.all_allow_notification_tip, groupName))
Text(text = stringResource(R.string.all_allow_notification_tips, groupName))
},
confirmButton = {
TextButton(

View File

@ -47,7 +47,7 @@ fun AllMoveToGroupDialog(
text = {
Text(
text = stringResource(
R.string.all_move_to_group_tip,
R.string.all_move_to_group_tips,
groupName,
viewState.targetGroup?.name ?: "",
)

View File

@ -45,7 +45,7 @@ fun AllParseFullContentDialog(
Text(text = stringResource(R.string.parse_full_content))
},
text = {
Text(text = stringResource(R.string.all_parse_full_content_tip, groupName))
Text(text = stringResource(R.string.all_parse_full_content_tips, groupName))
},
confirmButton = {
TextButton(

View File

@ -0,0 +1,76 @@
package me.ash.reader.ui.page.home.feeds.option.group
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.DeleteForever
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import com.google.accompanist.pager.ExperimentalPagerApi
import me.ash.reader.R
import me.ash.reader.ui.component.Dialog
import me.ash.reader.ui.ext.collectAsStateValue
import me.ash.reader.ui.ext.showToast
@OptIn(ExperimentalPagerApi::class)
@Composable
fun ClearGroupDialog(
modifier: Modifier = Modifier,
groupName: String,
viewModel: GroupOptionViewModel = hiltViewModel(),
) {
val context = LocalContext.current
val viewState = viewModel.viewState.collectAsStateValue()
val scope = rememberCoroutineScope()
val toastString = stringResource(R.string.clear_articles_in_group_toast, groupName)
Dialog(
visible = viewState.clearDialogVisible,
onDismissRequest = {
viewModel.dispatch(GroupOptionViewAction.HideClearDialog)
},
icon = {
Icon(
imageVector = Icons.Outlined.DeleteForever,
contentDescription = stringResource(R.string.clear_articles),
)
},
title = {
Text(text = stringResource(R.string.clear_articles))
},
text = {
Text(text = stringResource(R.string.clear_articles_group_tips, groupName))
},
confirmButton = {
TextButton(
onClick = {
viewModel.dispatch(GroupOptionViewAction.Clear {
viewModel.dispatch(GroupOptionViewAction.HideClearDialog)
viewModel.dispatch(GroupOptionViewAction.Hide(scope))
context.showToast(toastString)
})
}
) {
Text(
text = stringResource(R.string.clear),
)
}
},
dismissButton = {
TextButton(
onClick = {
viewModel.dispatch(GroupOptionViewAction.HideClearDialog)
}
) {
Text(
text = stringResource(R.string.cancel),
)
}
},
)
}

View File

@ -44,7 +44,7 @@ fun DeleteGroupDialog(
Text(text = stringResource(R.string.delete_group))
},
text = {
Text(text = stringResource(R.string.delete_group_tip, groupName))
Text(text = stringResource(R.string.delete_group_tips, groupName))
},
confirmButton = {
TextButton(

View File

@ -93,7 +93,7 @@ fun GroupOptionDrawer(
horizontalArrangement = Arrangement.Center
) {
Text(
text = stringResource(R.string.group_option_tip),
text = stringResource(R.string.group_option_tips),
color = MaterialTheme.colorScheme.outline.copy(alpha = 0.7f),
style = MaterialTheme.typography.bodyMedium,
textAlign = TextAlign.Center,
@ -126,6 +126,7 @@ fun GroupOptionDrawer(
content()
}
ClearGroupDialog(groupName = group?.name ?: "")
DeleteGroupDialog(groupName = group?.name ?: "")
AllAllowNotificationDialog(groupName = group?.name ?: "")
AllParseFullContentDialog(groupName = group?.name ?: "")
@ -194,6 +195,13 @@ private fun Preset(
) {
groupOptionViewModel.dispatch(GroupOptionViewAction.ShowAllParseFullContentDialog)
}
SelectionChip(
modifier = Modifier.animateContentSize(),
content = stringResource(R.string.clear_articles),
selected = false,
) {
groupOptionViewModel.dispatch(GroupOptionViewAction.ShowClearDialog)
}
if (group?.id != context.currentAccountId.getDefaultGroupId()) {
SelectionChip(
modifier = Modifier.animateContentSize(),

View File

@ -50,6 +50,10 @@ class GroupOptionViewModel @Inject constructor(
is GroupOptionViewAction.HideDeleteDialog -> changeDeleteDialogVisible(false)
is GroupOptionViewAction.Delete -> delete(action.callback)
is GroupOptionViewAction.ShowClearDialog -> showClearDialog()
is GroupOptionViewAction.HideClearDialog -> hideClearDialog()
is GroupOptionViewAction.Clear -> clear(action.callback)
is GroupOptionViewAction.ShowAllAllowNotificationDialog ->
changeAllAllowNotificationDialogVisible(true)
is GroupOptionViewAction.HideAllAllowNotificationDialog ->
@ -157,6 +161,33 @@ class GroupOptionViewModel @Inject constructor(
}
}
private fun showClearDialog() {
_viewState.update {
it.copy(
clearDialogVisible = true,
)
}
}
private fun hideClearDialog() {
_viewState.update {
it.copy(
clearDialogVisible = false,
)
}
}
private fun clear(callback: () -> Unit = {}) {
_viewState.value.group?.let {
viewModelScope.launch(Dispatchers.IO) {
rssRepository.get().deleteArticles(group = it)
withContext(Dispatchers.Main) {
callback()
}
}
}
}
private fun allMoveToGroup(callback: () -> Unit) {
_viewState.value.group?.let { group ->
_viewState.value.targetGroup?.let { targetGroup ->
@ -224,6 +255,7 @@ data class GroupOptionViewState(
val allParseFullContentDialogVisible: Boolean = false,
val allMoveToGroupDialogVisible: Boolean = false,
val deleteDialogVisible: Boolean = false,
val clearDialogVisible: Boolean = false,
val newName: String = "",
val renameDialogVisible: Boolean = false,
)
@ -245,6 +277,13 @@ sealed class GroupOptionViewAction {
object ShowDeleteDialog : GroupOptionViewAction()
object HideDeleteDialog : GroupOptionViewAction()
data class Clear(
val callback: () -> Unit = {}
) : GroupOptionViewAction()
object ShowClearDialog : GroupOptionViewAction()
object HideClearDialog : GroupOptionViewAction()
data class AllParseFullContent(
val isFullContent: Boolean,
val callback: () -> Unit = {}

View File

@ -47,6 +47,7 @@ fun ResultView(
selectedGroupId: String = "",
allowNotificationPresetOnClick: () -> Unit = {},
parseFullContentPresetOnClick: () -> Unit = {},
clearArticlesOnClick: () -> Unit = {},
unsubscribeOnClick: () -> Unit = {},
onGroupClick: (groupId: String) -> Unit = {},
onAddNewGroup: () -> Unit = {},
@ -68,6 +69,7 @@ fun ResultView(
showUnsubscribe = showUnsubscribe,
allowNotificationPresetOnClick = allowNotificationPresetOnClick,
parseFullContentPresetOnClick = parseFullContentPresetOnClick,
clearArticlesOnClick = clearArticlesOnClick,
unsubscribeOnClick = unsubscribeOnClick,
)
Spacer(modifier = Modifier.height(26.dp))
@ -114,6 +116,7 @@ private fun Preset(
showUnsubscribe: Boolean = false,
allowNotificationPresetOnClick: () -> Unit = {},
parseFullContentPresetOnClick: () -> Unit = {},
clearArticlesOnClick: () -> Unit = {},
unsubscribeOnClick: () -> Unit = {},
) {
Subtitle(text = stringResource(R.string.preset))
@ -159,6 +162,13 @@ private fun Preset(
parseFullContentPresetOnClick()
}
if (showUnsubscribe) {
SelectionChip(
modifier = Modifier.animateContentSize(),
content = stringResource(R.string.clear_articles),
selected = false,
) {
clearArticlesOnClick()
}
SelectionChip(
modifier = Modifier.animateContentSize(),
content = stringResource(R.string.unsubscribe),

View File

@ -31,16 +31,21 @@
<string name="preset">预设</string>
<string name="selected">已选择</string>
<string name="allow_notification">允许通知</string>
<string name="all_allow_notification_tip">允许 \"%1$s\" 分组中的所有订阅源发出通知。</string>
<string name="all_allow_notification_tips">允许 \"%1$s\" 分组中的所有订阅源发出通知。</string>
<string name="all_allow_notification_toast">已全部允许 \"%1$s\" 分组中的通知</string>
<string name="all_deny_notification_toast">已全部拒绝 \"%1$s\" 分组中的通知</string>
<string name="parse_full_content">全文解析</string>
<string name="all_parse_full_content_tip">对 \"%1$s\" 分组中的所有文章进行全文解析。</string>
<string name="all_parse_full_content_tips">对 \"%1$s\" 分组中的所有文章进行全文解析。</string>
<string name="all_parse_full_content_toast">全文解析 \"%1$s\" 分组中的文章</string>
<string name="all_deny_parse_full_content_toast">不再全文解析 \"%1$s\" 分组中的文章</string>
<string name="clear_articles">清空文章</string>
<string name="clear_articles_in_feed_toast">已清空 \"%1$s\" 订阅源中所有已归档的文章。</string>
<string name="clear_articles_in_group_toast">已清空 \"%1$s\" 分组中所有已归档的文章。</string>
<string name="clear_articles_feed_tips">清空 \"%1$s\" 订阅源中所有已归档的文章。</string>
<string name="clear_articles_group_tips">清空 \"%1$s\" 分组中所有已归档的文章。</string>
<string name="add_to_group">添加到组</string>
<string name="move_to_group">移动到组</string>
<string name="all_move_to_group_tip">将 \"%1$s\" 分组中的所有订阅源移动至 \"%2$s\" 分组。</string>
<string name="all_move_to_group_tips">将 \"%1$s\" 分组中的所有订阅源移动至 \"%2$s\" 分组。</string>
<string name="all_move_to_group_toast">已全部移动至 \"%1$s\" 分组</string>
<string name="rename">重命名</string>
<string name="change_url">更改链接</string>
@ -52,10 +57,10 @@
<string name="delete">删除</string>
<string name="delete_toast">\"%1$s\" 已被删除</string>
<string name="unsubscribe">取消订阅</string>
<string name="unsubscribe_tip">不再订阅 \"%1$s\",同时删除其中所有已归档的文章。</string>
<string name="unsubscribe_tips">不再订阅 \"%1$s\",同时删除其中所有已归档的文章。</string>
<string name="delete_group">删除分组</string>
<string name="delete_group_tip">删除 \"%1$s\" 分组,同时删除其中所有订阅源和已归档的文章。</string>
<string name="group_option_tip">以下选项将应用到该分组中的所有订阅源。</string>
<string name="delete_group_tips">删除 \"%1$s\" 分组,同时删除其中所有订阅源和已归档的文章。</string>
<string name="group_option_tips">以下选项将应用到该分组中的所有订阅源。</string>
<string name="today">今天</string>
<string name="yesterday">昨天</string>
<string name="date_at_time">%1$s %2$s</string>

View File

@ -31,16 +31,21 @@
<string name="preset">Preset</string>
<string name="selected">Selected</string>
<string name="allow_notification">Allow Notification</string>
<string name="all_allow_notification_tip">Allow all feeds in the \"%1$s\" group to send notifications.</string>
<string name="all_allow_notification_tips">Allow all feeds in the \"%1$s\" group to send notifications.</string>
<string name="all_allow_notification_toast">All notifications in the \"%1$s\" group are allowed</string>
<string name="all_deny_notification_toast">All notifications in the \"%1$s\" group are denied</string>
<string name="parse_full_content">Parse Full Content</string>
<string name="all_parse_full_content_tip">Full content parsing of all articles in the \"%1$s\" group.</string>
<string name="all_parse_full_content_tips">Full content parsing of all articles in the \"%1$s\" group.</string>
<string name="all_parse_full_content_toast">Full content parsing of all articles in the \"%1$s\" group</string>
<string name="all_deny_parse_full_content_toast">No more full content parsing of all articles in the \"%1$s\" group</string>
<string name="clear_articles">Clear Articles</string>
<string name="clear_articles_in_feed_toast">All archived of articles in the \"%1$s\" Feed has been cleaned</string>
<string name="clear_articles_in_group_toast">All archived of articles in the \"%1$s\" group has been cleaned</string>
<string name="clear_articles_feed_tips">Clear all archived of articles in \"%1$s\" feed.</string>
<string name="clear_articles_group_tips">Clear all archived of articles in \"%1$s\" group.</string>
<string name="add_to_group">Add to Group</string>
<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_tips">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="change_url">Change URL</string>
@ -53,10 +58,10 @@
<string name="delete">Delete</string>
<string name="delete_toast">\"%1$s\" has been deleted</string>
<string name="unsubscribe">Unsubscribe</string>
<string name="unsubscribe_tip">Unsubscribe \"%1$s\" and delete all archived articles in it.</string>
<string name="unsubscribe_tips">Unsubscribe \"%1$s\" and delete all archived articles in it.</string>
<string name="delete_group">Delete Group</string>
<string name="delete_group_tip">Delete the \"%1$s\" group, and delete all feeds and archived articles in it.</string>
<string name="group_option_tip">The following options will be applied to all feeds in this group.</string>
<string name="delete_group_tips">Delete the \"%1$s\" group, and delete all feeds and archived articles in it.</string>
<string name="group_option_tips">The following options will be applied to all feeds in this group.</string>
<string name="today">Today</string>
<string name="yesterday">Yesterday</string>
<string name="date_at_time">%1$s At %2$s</string>