Add GroupOptionView
This commit is contained in:
parent
590470137a
commit
1ba149368b
|
@ -69,6 +69,7 @@ class App : Application(), Configuration.Provider {
|
|||
super.onCreate()
|
||||
applicationScope.launch(dispatcherDefault) {
|
||||
accountInit()
|
||||
dataStoreInit()
|
||||
workerInit()
|
||||
}
|
||||
}
|
||||
|
@ -81,6 +82,9 @@ class App : Application(), Configuration.Provider {
|
|||
}
|
||||
}
|
||||
|
||||
private fun dataStoreInit() {
|
||||
}
|
||||
|
||||
private fun workerInit() {
|
||||
rssRepository.get().doSync()
|
||||
}
|
||||
|
|
|
@ -56,6 +56,20 @@ interface ArticleDao {
|
|||
)
|
||||
suspend fun deleteByFeedId(accountId: Int, feedId: String)
|
||||
|
||||
@Query(
|
||||
"""
|
||||
DELETE FROM article
|
||||
WHERE id IN (
|
||||
SELECT a.id FROM article AS a, feed AS b, `group` AS c
|
||||
WHERE a.accountId = :accountId
|
||||
AND a.feedId = b.id
|
||||
AND b.groupId = c.id
|
||||
AND c.id = :groupId
|
||||
)
|
||||
"""
|
||||
)
|
||||
suspend fun deleteByGroupId(accountId: Int, groupId: String)
|
||||
|
||||
@Transaction
|
||||
@Query(
|
||||
"""
|
||||
|
|
|
@ -5,6 +5,41 @@ import me.ash.reader.data.entity.Feed
|
|||
|
||||
@Dao
|
||||
interface FeedDao {
|
||||
@Query(
|
||||
"""
|
||||
UPDATE feed SET isFullContent = :isFullContent
|
||||
WHERE accountId = :accountId
|
||||
AND groupId = :groupId
|
||||
"""
|
||||
)
|
||||
suspend fun updateIsFullContentByGroupId(
|
||||
accountId: Int,
|
||||
groupId: String,
|
||||
isFullContent: Boolean
|
||||
)
|
||||
|
||||
@Query(
|
||||
"""
|
||||
UPDATE feed SET isNotification = :isNotification
|
||||
WHERE accountId = :accountId
|
||||
AND groupId = :groupId
|
||||
"""
|
||||
)
|
||||
suspend fun updateIsNotificationByGroupId(
|
||||
accountId: Int,
|
||||
groupId: String,
|
||||
isNotification: Boolean
|
||||
)
|
||||
|
||||
@Query(
|
||||
"""
|
||||
DELETE FROM feed
|
||||
WHERE groupId = :groupId
|
||||
AND accountId = :accountId
|
||||
"""
|
||||
)
|
||||
suspend fun deleteByGroupId(accountId: Int, groupId: String)
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM feed
|
||||
|
|
|
@ -13,7 +13,7 @@ interface GroupDao {
|
|||
WHERE id = :id
|
||||
"""
|
||||
)
|
||||
fun queryById(id: String): Group?
|
||||
suspend fun queryById(id: String): Group?
|
||||
|
||||
@Transaction
|
||||
@Query(
|
||||
|
@ -31,7 +31,7 @@ interface GroupDao {
|
|||
WHERE accountId = :accountId
|
||||
"""
|
||||
)
|
||||
fun queryAllGroupWithFeed(accountId: Int): List<GroupWithFeed>
|
||||
suspend fun queryAllGroupWithFeed(accountId: Int): List<GroupWithFeed>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
|
|
|
@ -112,6 +112,10 @@ abstract class AbstractRssRepository constructor(
|
|||
return feedDao.queryById(id)
|
||||
}
|
||||
|
||||
suspend fun findGroupById(id: String): Group? {
|
||||
return groupDao.queryById(id)
|
||||
}
|
||||
|
||||
suspend fun findArticleById(id: String): ArticleWithFeed? {
|
||||
return articleDao.queryById(id)
|
||||
}
|
||||
|
@ -133,13 +137,23 @@ abstract class AbstractRssRepository constructor(
|
|||
}
|
||||
|
||||
suspend fun deleteGroup(group: Group) {
|
||||
groupDao.update(group)
|
||||
articleDao.deleteByGroupId(context.currentAccountId, group.id)
|
||||
feedDao.deleteByGroupId(context.currentAccountId, group.id)
|
||||
groupDao.delete(group)
|
||||
}
|
||||
|
||||
suspend fun deleteFeed(feed: Feed) {
|
||||
articleDao.deleteByFeedId(context.currentAccountId, feed.id)
|
||||
feedDao.delete(feed)
|
||||
}
|
||||
|
||||
suspend fun groupParseFullContent(group: Group, isFullContent: Boolean) {
|
||||
feedDao.updateIsFullContentByGroupId(context.currentAccountId, group.id, isFullContent)
|
||||
}
|
||||
|
||||
suspend fun groupAllowNotification(group: Group, isNotification: Boolean) {
|
||||
feedDao.updateIsNotificationByGroupId(context.currentAccountId, group.id, isNotification)
|
||||
}
|
||||
}
|
||||
|
||||
@HiltWorker
|
||||
|
|
|
@ -8,7 +8,7 @@ import me.ash.reader.data.dao.GroupDao
|
|||
import me.ash.reader.data.entity.Account
|
||||
import me.ash.reader.data.entity.Group
|
||||
import me.ash.reader.ui.ext.currentAccountId
|
||||
import me.ash.reader.ui.ext.spacerDollar
|
||||
import me.ash.reader.ui.ext.getDefaultGroupId
|
||||
import javax.inject.Inject
|
||||
|
||||
class AccountRepository @Inject constructor(
|
||||
|
@ -38,7 +38,7 @@ class AccountRepository @Inject constructor(
|
|||
if (groupDao.queryAll(it.id!!).isEmpty()) {
|
||||
groupDao.insert(
|
||||
Group(
|
||||
id = it.id!!.spacerDollar(readYouString + defaultString),
|
||||
id = it.id!!.getDefaultGroupId(),
|
||||
name = defaultString,
|
||||
accountId = it.id!!,
|
||||
)
|
||||
|
|
|
@ -31,6 +31,7 @@ import me.ash.reader.data.module.DispatcherIO
|
|||
import me.ash.reader.data.repository.SyncWorker.Companion.setIsSyncing
|
||||
import me.ash.reader.data.source.RssNetworkDataSource
|
||||
import me.ash.reader.ui.ext.currentAccountId
|
||||
import me.ash.reader.ui.ext.spacerDollar
|
||||
import me.ash.reader.ui.page.common.ExtraName
|
||||
import me.ash.reader.ui.page.common.NotificationGroupName
|
||||
import java.util.*
|
||||
|
@ -81,14 +82,16 @@ class LocalRssRepository @Inject constructor(
|
|||
}
|
||||
|
||||
override suspend fun addGroup(name: String): String {
|
||||
return UUID.randomUUID().toString().also {
|
||||
groupDao.insert(
|
||||
Group(
|
||||
id = it,
|
||||
name = name,
|
||||
accountId = context.currentAccountId
|
||||
context.currentAccountId.let { accountId ->
|
||||
return accountId.spacerDollar(UUID.randomUUID().toString()).also {
|
||||
groupDao.insert(
|
||||
Group(
|
||||
id = it,
|
||||
name = name,
|
||||
accountId = accountId
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,14 +7,13 @@ import be.ceau.opml.entity.Head
|
|||
import be.ceau.opml.entity.Opml
|
||||
import be.ceau.opml.entity.Outline
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import me.ash.reader.R
|
||||
import me.ash.reader.data.dao.AccountDao
|
||||
import me.ash.reader.data.dao.FeedDao
|
||||
import me.ash.reader.data.dao.GroupDao
|
||||
import me.ash.reader.data.entity.Feed
|
||||
import me.ash.reader.data.source.OpmlLocalDataSource
|
||||
import me.ash.reader.ui.ext.currentAccountId
|
||||
import me.ash.reader.ui.ext.spacerDollar
|
||||
import me.ash.reader.ui.ext.getDefaultGroupId
|
||||
import java.io.InputStream
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
|
@ -27,7 +26,6 @@ class OpmlRepository @Inject constructor(
|
|||
private val accountDao: AccountDao,
|
||||
private val rssRepository: RssRepository,
|
||||
private val opmlLocalDataSource: OpmlLocalDataSource,
|
||||
private val stringsRepository: StringsRepository,
|
||||
) {
|
||||
@Throws(Exception::class)
|
||||
suspend fun saveToDatabase(inputStream: InputStream) {
|
||||
|
@ -88,8 +86,6 @@ class OpmlRepository @Inject constructor(
|
|||
}
|
||||
|
||||
private fun getDefaultGroupId(): String {
|
||||
val readYouString = stringsRepository.getString(R.string.read_you)
|
||||
val defaultString = stringsRepository.getString(R.string.defaults)
|
||||
return context.currentAccountId.spacerDollar(readYouString + defaultString)
|
||||
return context.currentAccountId.getDefaultGroupId()
|
||||
}
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
package me.ash.reader.ui.ext
|
||||
|
||||
fun Int.spacerDollar(str: Any): String = "$this$$str"
|
||||
|
||||
fun Int.getDefaultGroupId() = this.spacerDollar("read_you_app_default_group")
|
|
@ -17,6 +17,7 @@ import me.ash.reader.ui.page.common.ExtraName
|
|||
import me.ash.reader.ui.page.home.drawer.feed.FeedOptionDrawer
|
||||
import me.ash.reader.ui.page.home.drawer.feed.FeedOptionViewAction
|
||||
import me.ash.reader.ui.page.home.drawer.feed.FeedOptionViewModel
|
||||
import me.ash.reader.ui.page.home.drawer.group.GroupOptionDrawer
|
||||
import me.ash.reader.ui.page.home.feeds.FeedsPage
|
||||
import me.ash.reader.ui.page.home.flow.FlowPage
|
||||
import me.ash.reader.ui.page.home.read.ReadPage
|
||||
|
@ -165,4 +166,5 @@ fun HomePage(
|
|||
}
|
||||
|
||||
FeedOptionDrawer()
|
||||
GroupOptionDrawer()
|
||||
}
|
|
@ -27,7 +27,7 @@ fun DeleteFeedDialog(
|
|||
val context = LocalContext.current
|
||||
val viewState = viewModel.viewState.collectAsStateValue()
|
||||
val scope = rememberCoroutineScope()
|
||||
val deletedTip = stringResource(R.string.has_been_deleted, feedName)
|
||||
val toastString = stringResource(R.string.delete_toast, feedName)
|
||||
|
||||
Dialog(
|
||||
visible = viewState.deleteDialogVisible,
|
||||
|
@ -37,7 +37,7 @@ fun DeleteFeedDialog(
|
|||
icon = {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.DeleteForever,
|
||||
contentDescription = stringResource(R.string.subscribe),
|
||||
contentDescription = stringResource(R.string.unsubscribe),
|
||||
)
|
||||
},
|
||||
title = {
|
||||
|
@ -52,7 +52,7 @@ fun DeleteFeedDialog(
|
|||
viewModel.dispatch(FeedOptionViewAction.Delete {
|
||||
viewModel.dispatch(FeedOptionViewAction.HideDeleteDialog)
|
||||
viewModel.dispatch(FeedOptionViewAction.Hide(scope))
|
||||
Toast.makeText(context, deletedTip, Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(context, toastString, Toast.LENGTH_SHORT).show()
|
||||
})
|
||||
}
|
||||
) {
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
package me.ash.reader.ui.page.home.drawer.group
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Notifications
|
||||
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
|
||||
|
||||
@OptIn(ExperimentalPagerApi::class)
|
||||
@Composable
|
||||
fun AllAllowNotificationDialog(
|
||||
modifier: Modifier = Modifier,
|
||||
groupName: String,
|
||||
viewModel: GroupOptionViewModel = hiltViewModel(),
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val viewState = viewModel.viewState.collectAsStateValue()
|
||||
val scope = rememberCoroutineScope()
|
||||
val allowToastString = stringResource(R.string.all_allow_notification_toast, groupName)
|
||||
val denyToastString = stringResource(R.string.all_deny_notification_toast, groupName)
|
||||
|
||||
Dialog(
|
||||
visible = viewState.allAllowNotificationDialogVisible,
|
||||
onDismissRequest = {
|
||||
viewModel.dispatch(GroupOptionViewAction.HideAllAllowNotificationDialog)
|
||||
},
|
||||
icon = {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Notifications,
|
||||
contentDescription = stringResource(R.string.allow_notification),
|
||||
)
|
||||
},
|
||||
title = {
|
||||
Text(text = stringResource(R.string.allow_notification))
|
||||
},
|
||||
text = {
|
||||
Text(text = stringResource(R.string.all_allow_notification_tip, groupName))
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
viewModel.dispatch(GroupOptionViewAction.AllAllowNotification(true) {
|
||||
viewModel.dispatch(GroupOptionViewAction.HideAllAllowNotificationDialog)
|
||||
viewModel.dispatch(GroupOptionViewAction.Hide(scope))
|
||||
Toast.makeText(context, allowToastString, Toast.LENGTH_SHORT).show()
|
||||
})
|
||||
}
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.allow),
|
||||
)
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
viewModel.dispatch(GroupOptionViewAction.AllAllowNotification(false) {
|
||||
viewModel.dispatch(GroupOptionViewAction.HideAllAllowNotificationDialog)
|
||||
viewModel.dispatch(GroupOptionViewAction.Hide(scope))
|
||||
Toast.makeText(context, denyToastString, Toast.LENGTH_SHORT).show()
|
||||
})
|
||||
}
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.deny),
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
package me.ash.reader.ui.page.home.drawer.group
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Article
|
||||
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
|
||||
|
||||
@OptIn(ExperimentalPagerApi::class)
|
||||
@Composable
|
||||
fun AllParseFullContentDialog(
|
||||
modifier: Modifier = Modifier,
|
||||
groupName: String,
|
||||
viewModel: GroupOptionViewModel = hiltViewModel(),
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val viewState = viewModel.viewState.collectAsStateValue()
|
||||
val scope = rememberCoroutineScope()
|
||||
val allowToastString = stringResource(R.string.all_parse_full_content_toast, groupName)
|
||||
val denyToastString = stringResource(R.string.all_deny_parse_full_content_toast, groupName)
|
||||
|
||||
Dialog(
|
||||
visible = viewState.allParseFullContentDialogVisible,
|
||||
onDismissRequest = {
|
||||
viewModel.dispatch(GroupOptionViewAction.HideAllParseFullContentDialog)
|
||||
},
|
||||
icon = {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Article,
|
||||
contentDescription = stringResource(R.string.parse_full_content),
|
||||
)
|
||||
},
|
||||
title = {
|
||||
Text(text = stringResource(R.string.parse_full_content))
|
||||
},
|
||||
text = {
|
||||
Text(text = stringResource(R.string.all_parse_full_content_tip, groupName))
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
viewModel.dispatch(GroupOptionViewAction.AllParseFullContent(true) {
|
||||
viewModel.dispatch(GroupOptionViewAction.HideAllParseFullContentDialog)
|
||||
viewModel.dispatch(GroupOptionViewAction.Hide(scope))
|
||||
Toast.makeText(context, allowToastString, Toast.LENGTH_SHORT).show()
|
||||
})
|
||||
}
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.allow),
|
||||
)
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
viewModel.dispatch(GroupOptionViewAction.AllParseFullContent(false) {
|
||||
viewModel.dispatch(GroupOptionViewAction.HideAllParseFullContentDialog)
|
||||
viewModel.dispatch(GroupOptionViewAction.Hide(scope))
|
||||
Toast.makeText(context, denyToastString, Toast.LENGTH_SHORT).show()
|
||||
})
|
||||
}
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.deny),
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
package me.ash.reader.ui.page.home.drawer.group
|
||||
|
||||
import android.widget.Toast
|
||||
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
|
||||
|
||||
@OptIn(ExperimentalPagerApi::class)
|
||||
@Composable
|
||||
fun DeleteGroupDialog(
|
||||
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.delete_toast, groupName)
|
||||
|
||||
Dialog(
|
||||
visible = viewState.deleteDialogVisible,
|
||||
onDismissRequest = {
|
||||
viewModel.dispatch(GroupOptionViewAction.HideDeleteDialog)
|
||||
},
|
||||
icon = {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.DeleteForever,
|
||||
contentDescription = stringResource(R.string.delete_group),
|
||||
)
|
||||
},
|
||||
title = {
|
||||
Text(text = stringResource(R.string.delete_group))
|
||||
},
|
||||
text = {
|
||||
Text(text = stringResource(R.string.delete_group_tip, groupName))
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
viewModel.dispatch(GroupOptionViewAction.Delete {
|
||||
viewModel.dispatch(GroupOptionViewAction.HideDeleteDialog)
|
||||
viewModel.dispatch(GroupOptionViewAction.Hide(scope))
|
||||
Toast.makeText(context, toastString, Toast.LENGTH_SHORT).show()
|
||||
})
|
||||
}
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.delete),
|
||||
)
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
viewModel.dispatch(GroupOptionViewAction.HideDeleteDialog)
|
||||
}
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.cancel),
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
|
@ -0,0 +1,155 @@
|
|||
package me.ash.reader.ui.page.home.drawer.group
|
||||
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
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.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.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
|
||||
import com.google.accompanist.flowlayout.FlowRow
|
||||
import com.google.accompanist.flowlayout.MainAxisAlignment
|
||||
import me.ash.reader.R
|
||||
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.ext.collectAsStateValue
|
||||
import me.ash.reader.ui.ext.currentAccountId
|
||||
import me.ash.reader.ui.ext.getDefaultGroupId
|
||||
import me.ash.reader.ui.ext.roundClick
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
fun GroupOptionDrawer(
|
||||
modifier: Modifier = Modifier,
|
||||
GroupOptionViewModel: GroupOptionViewModel = hiltViewModel(),
|
||||
content: @Composable () -> Unit = {},
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val viewState = GroupOptionViewModel.viewState.collectAsStateValue()
|
||||
val group = viewState.group
|
||||
|
||||
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())
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.Center
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.group_option_tip),
|
||||
color = MaterialTheme.colorScheme.outline.copy(alpha = 0.7f),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
}
|
||||
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))
|
||||
|
||||
// AddToGroup(
|
||||
// groups = groups,
|
||||
// selectedGroupId = selectedGroupId,
|
||||
// onGroupClick = onGroupClick,
|
||||
// onAddNewGroup = onAddNewGroup,
|
||||
// )
|
||||
Spacer(modifier = Modifier.height(6.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
) {
|
||||
content()
|
||||
}
|
||||
|
||||
DeleteGroupDialog(groupName = group?.name ?: "")
|
||||
AllAllowNotificationDialog(groupName = group?.name ?: "")
|
||||
AllParseFullContentDialog(groupName = group?.name ?: "")
|
||||
}
|
|
@ -0,0 +1,189 @@
|
|||
package me.ash.reader.ui.page.home.drawer.group
|
||||
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.ModalBottomSheetState
|
||||
import androidx.compose.material.ModalBottomSheetValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.google.accompanist.pager.ExperimentalPagerApi
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import me.ash.reader.data.entity.Group
|
||||
import me.ash.reader.data.repository.RssRepository
|
||||
import javax.inject.Inject
|
||||
|
||||
@OptIn(
|
||||
ExperimentalPagerApi::class,
|
||||
ExperimentalMaterialApi::class
|
||||
)
|
||||
@HiltViewModel
|
||||
class GroupOptionViewModel @Inject constructor(
|
||||
private val rssRepository: RssRepository,
|
||||
) : ViewModel() {
|
||||
private val _viewState = MutableStateFlow(GroupOptionViewState())
|
||||
val viewState: StateFlow<GroupOptionViewState> = _viewState.asStateFlow()
|
||||
|
||||
init {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
rssRepository.get().pullGroups().collect { groups ->
|
||||
_viewState.update {
|
||||
it.copy(
|
||||
groups = groups
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun dispatch(action: GroupOptionViewAction) {
|
||||
when (action) {
|
||||
is GroupOptionViewAction.Show -> show(action.scope, action.groupId)
|
||||
is GroupOptionViewAction.Hide -> hide(action.scope)
|
||||
is GroupOptionViewAction.ShowDeleteDialog -> changeDeleteDialogVisible(true)
|
||||
is GroupOptionViewAction.HideDeleteDialog -> changeDeleteDialogVisible(false)
|
||||
is GroupOptionViewAction.Delete -> delete(action.callback)
|
||||
is GroupOptionViewAction.ShowAllAllowNotificationDialog ->
|
||||
changeAllAllowNotificationDialogVisible(true)
|
||||
is GroupOptionViewAction.HideAllAllowNotificationDialog ->
|
||||
changeAllAllowNotificationDialogVisible(false)
|
||||
is GroupOptionViewAction.AllAllowNotification ->
|
||||
allAllowNotification(action.isNotification, action.callback)
|
||||
is GroupOptionViewAction.ShowAllParseFullContentDialog ->
|
||||
changeAllParseFullContentDialogVisible(true)
|
||||
is GroupOptionViewAction.HideAllParseFullContentDialog ->
|
||||
changeAllParseFullContentDialogVisible(false)
|
||||
is GroupOptionViewAction.AllParseFullContent ->
|
||||
allParseFullContent(action.isFullContent, action.callback)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun fetchGroup(groupId: String) {
|
||||
val group = rssRepository.get().findGroupById(groupId)
|
||||
_viewState.update {
|
||||
it.copy(
|
||||
group = group,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun show(scope: CoroutineScope, groupId: String) {
|
||||
scope.launch {
|
||||
fetchGroup(groupId)
|
||||
_viewState.value.drawerState.show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun hide(scope: CoroutineScope) {
|
||||
scope.launch {
|
||||
_viewState.value.drawerState.hide()
|
||||
}
|
||||
}
|
||||
|
||||
private fun allAllowNotification(isNotification: Boolean, callback: () -> Unit = {}) {
|
||||
_viewState.value.group?.let {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
rssRepository.get().groupAllowNotification(it, isNotification)
|
||||
withContext(Dispatchers.Main) {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun changeAllAllowNotificationDialogVisible(visible: Boolean) {
|
||||
_viewState.update {
|
||||
it.copy(
|
||||
allAllowNotificationDialogVisible = visible,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun allParseFullContent(isFullContent: Boolean, callback: () -> Unit = {}) {
|
||||
_viewState.value.group?.let {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
rssRepository.get().groupParseFullContent(it, isFullContent)
|
||||
withContext(Dispatchers.Main) {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun changeAllParseFullContentDialogVisible(visible: Boolean) {
|
||||
_viewState.update {
|
||||
it.copy(
|
||||
allParseFullContentDialogVisible = visible,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun delete(callback: () -> Unit = {}) {
|
||||
_viewState.value.group?.let {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
rssRepository.get().deleteGroup(it)
|
||||
withContext(Dispatchers.Main) {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun changeDeleteDialogVisible(visible: Boolean) {
|
||||
_viewState.update {
|
||||
it.copy(
|
||||
deleteDialogVisible = visible,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
data class GroupOptionViewState(
|
||||
var drawerState: ModalBottomSheetState = ModalBottomSheetState(ModalBottomSheetValue.Hidden),
|
||||
val group: Group? = null,
|
||||
val groups: List<Group> = emptyList(),
|
||||
val allAllowNotificationDialogVisible: Boolean = false,
|
||||
val allParseFullContentDialogVisible: Boolean = false,
|
||||
val deleteDialogVisible: Boolean = false,
|
||||
)
|
||||
|
||||
sealed class GroupOptionViewAction {
|
||||
data class Show(
|
||||
val scope: CoroutineScope,
|
||||
val groupId: String
|
||||
) : GroupOptionViewAction()
|
||||
|
||||
data class Hide(
|
||||
val scope: CoroutineScope,
|
||||
) : GroupOptionViewAction()
|
||||
|
||||
data class Delete(
|
||||
val callback: () -> Unit = {}
|
||||
) : GroupOptionViewAction()
|
||||
|
||||
object ShowDeleteDialog : GroupOptionViewAction()
|
||||
object HideDeleteDialog : GroupOptionViewAction()
|
||||
|
||||
data class AllParseFullContent(
|
||||
val isFullContent: Boolean,
|
||||
val callback: () -> Unit = {}
|
||||
) : GroupOptionViewAction()
|
||||
|
||||
object ShowAllParseFullContentDialog : GroupOptionViewAction()
|
||||
object HideAllParseFullContentDialog : GroupOptionViewAction()
|
||||
|
||||
data class AllAllowNotification(
|
||||
val isNotification: Boolean,
|
||||
val callback: () -> Unit = {}
|
||||
) : GroupOptionViewAction()
|
||||
|
||||
object ShowAllAllowNotificationDialog : GroupOptionViewAction()
|
||||
object HideAllAllowNotificationDialog : GroupOptionViewAction()
|
||||
}
|
|
@ -186,7 +186,7 @@ fun FeedsPage(
|
|||
// Crossfade(targetState = groupWithFeed) { groupWithFeed ->
|
||||
Column {
|
||||
GroupItem(
|
||||
text = groupWithFeed.group.name,
|
||||
group = groupWithFeed.group,
|
||||
feeds = groupWithFeed.feeds,
|
||||
groupOnClick = {
|
||||
onFilterChange(
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package me.ash.reader.ui.page.home.feeds
|
||||
|
||||
import android.view.HapticFeedbackConstants
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
|
@ -18,22 +19,30 @@ import androidx.compose.runtime.*
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import me.ash.reader.R
|
||||
import me.ash.reader.data.entity.Feed
|
||||
import me.ash.reader.data.entity.Group
|
||||
import me.ash.reader.ui.page.home.drawer.group.GroupOptionViewAction
|
||||
import me.ash.reader.ui.page.home.drawer.group.GroupOptionViewModel
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class, androidx.compose.foundation.ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun GroupItem(
|
||||
modifier: Modifier = Modifier,
|
||||
text: String,
|
||||
group: Group,
|
||||
feeds: List<Feed>,
|
||||
isExpanded: Boolean = true,
|
||||
groupOptionViewModel: GroupOptionViewModel = hiltViewModel(),
|
||||
groupOnClick: () -> Unit = {},
|
||||
feedOnClick: (feed: Feed) -> Unit = {},
|
||||
) {
|
||||
val view = LocalView.current
|
||||
val scope = rememberCoroutineScope()
|
||||
var expanded by remember { mutableStateOf(isExpanded) }
|
||||
|
||||
Column(
|
||||
|
@ -47,6 +56,8 @@ fun GroupItem(
|
|||
groupOnClick()
|
||||
},
|
||||
onLongClick = {
|
||||
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
|
||||
groupOptionViewModel.dispatch(GroupOptionViewAction.Show(scope, group.id))
|
||||
}
|
||||
)
|
||||
.padding(top = 22.dp)
|
||||
|
@ -60,7 +71,7 @@ fun GroupItem(
|
|||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(start = 28.dp),
|
||||
text = text,
|
||||
text = group.name,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = MaterialTheme.colorScheme.onSecondaryContainer,
|
||||
maxLines = 1,
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
<string name="expand_more">展开</string>
|
||||
<string name="confirm">确认</string>
|
||||
<string name="cancel">取消</string>
|
||||
<string name="allow">允许</string>
|
||||
<string name="deny">拒绝</string>
|
||||
<string name="defaults">默认</string>
|
||||
<string name="unknown">未知</string>
|
||||
<string name="back">返回</string>
|
||||
|
@ -29,16 +31,25 @@
|
|||
<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_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_toast">全文解析 \"%1$s\" 分组中的文章</string>
|
||||
<string name="all_deny_parse_full_content_toast">不再全文解析 \"%1$s\" 分组中的文章</string>
|
||||
<string name="add_to_group">添加到组</string>
|
||||
<string name="create_new_group">新建分组</string>
|
||||
<string name="name">名称</string>
|
||||
<string name="open_with">打开 %1$s</string>
|
||||
<string name="options">选项</string>
|
||||
<string name="delete">删除</string>
|
||||
<string name="has_been_deleted">\"%1$s\" 已被删除</string>
|
||||
<string name="delete_toast">\"%1$s\" 已被删除</string>
|
||||
<string name="unsubscribe">取消订阅</string>
|
||||
<string name="unsubscribe_tip">不再订阅 \"%1$s\",同时删除其所有已归档的文章。</string>
|
||||
<string name="unsubscribe_tip">不再订阅 \"%1$s\",同时删除其中所有已归档的文章。</string>
|
||||
<string name="delete_group">删除分组</string>
|
||||
<string name="delete_group_tip">删除 \"%1$s\" 分组,同时删除其中所有订阅源和已归档的文章。</string>
|
||||
<string name="group_option_tip">以下选项将应用到该分组中的所有订阅源。</string>
|
||||
<string name="today">今天</string>
|
||||
<string name="yesterday">昨天</string>
|
||||
<string name="date_at_time">%1$s %2$s</string>
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
<string name="expand_more">Expand More</string>
|
||||
<string name="confirm">Confirm</string>
|
||||
<string name="cancel">Cancel</string>
|
||||
<string name="allow">Allow</string>
|
||||
<string name="deny">Deny</string>
|
||||
<string name="defaults">Default</string>
|
||||
<string name="unknown">Unknown</string>
|
||||
<string name="back">Back</string>
|
||||
|
@ -29,16 +31,25 @@
|
|||
<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_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_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="add_to_group">Add to Group</string>
|
||||
<string name="create_new_group">Create New Group</string>
|
||||
<string name="name">Name</string>
|
||||
<string name="open_with">Open %1$s</string>
|
||||
<string name="options">Options</string>
|
||||
<string name="delete">Delete</string>
|
||||
<string name="has_been_deleted">\"%1$s\" has been deleted</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 its archived articles.</string>
|
||||
<string name="unsubscribe_tip">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="today">Today</string>
|
||||
<string name="yesterday">Yesterday</string>
|
||||
<string name="date_at_time">%1$s At %2$s</string>
|
||||
|
|
Loading…
Reference in New Issue
Block a user