diff --git a/app/src/main/java/me/ash/reader/ui/page/common/HomeEntry.kt b/app/src/main/java/me/ash/reader/ui/page/common/HomeEntry.kt index 547409b..92ca3b6 100644 --- a/app/src/main/java/me/ash/reader/ui/page/common/HomeEntry.kt +++ b/app/src/main/java/me/ash/reader/ui/page/common/HomeEntry.kt @@ -15,11 +15,10 @@ import com.google.accompanist.systemuicontroller.rememberSystemUiController import me.ash.reader.data.model.Filter import me.ash.reader.data.preference.LocalDarkTheme import me.ash.reader.ui.ext.* -import me.ash.reader.ui.page.home.HomeViewAction import me.ash.reader.ui.page.home.HomeViewModel 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 +import me.ash.reader.ui.page.home.reading.ReadingPage import me.ash.reader.ui.page.settings.SettingsPage import me.ash.reader.ui.page.settings.color.ColorAndStylePage import me.ash.reader.ui.page.settings.color.DarkThemePage @@ -37,7 +36,7 @@ fun HomeEntry( homeViewModel: HomeViewModel = hiltViewModel(), ) { val context = LocalContext.current - val filterState = homeViewModel.filterState.collectAsStateValue() + val filterUiState = homeViewModel.filterUiState.collectAsStateValue() val navController = rememberAnimatedNavController() val intent by rememberSaveable { mutableStateOf(context.findActivity()?.intent) } @@ -57,16 +56,14 @@ fun HomeEntry( // Other initial pages } - homeViewModel.dispatch( - HomeViewAction.ChangeFilter( - filterState.copy( - filter = when (context.initialFilter) { - 0 -> Filter.Starred - 1 -> Filter.Unread - 2 -> Filter.All - else -> Filter.All - } - ) + homeViewModel.changeFilter( + filterUiState.copy( + filter = when (context.initialFilter) { + 0 -> Filter.Starred + 1 -> Filter.Unread + 2 -> Filter.All + else -> Filter.All + } ) ) } @@ -114,7 +111,7 @@ fun HomeEntry( ) } animatedComposable(route = "${RouteName.READING}/{articleId}") { - ReadPage(navController = navController) + ReadingPage(navController = navController) } // Settings diff --git a/app/src/main/java/me/ash/reader/ui/page/home/HomeViewModel.kt b/app/src/main/java/me/ash/reader/ui/page/home/HomeViewModel.kt index 3f7f258..a250c56 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/HomeViewModel.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/HomeViewModel.kt @@ -7,8 +7,8 @@ import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.* import me.ash.reader.data.entity.Feed -import me.ash.reader.data.model.Filter import me.ash.reader.data.entity.Group +import me.ash.reader.data.model.Filter import me.ash.reader.data.module.ApplicationScope import me.ash.reader.data.repository.RssRepository import me.ash.reader.data.repository.StringsRepository @@ -24,30 +24,20 @@ class HomeViewModel @Inject constructor( private val applicationScope: CoroutineScope, private val workManager: WorkManager, ) : ViewModel() { + private val _homeUiState = MutableStateFlow(HomeUiState()) + val homeUiState: StateFlow = _homeUiState.asStateFlow() - private val _viewState = MutableStateFlow(HomeViewState()) - val viewState: StateFlow = _viewState.asStateFlow() - - private val _filterState = MutableStateFlow(FilterState()) - val filterState = _filterState.asStateFlow() + private val _filterUiState = MutableStateFlow(FilterState()) + val filterUiState = _filterUiState.asStateFlow() val syncWorkLiveData = workManager.getWorkInfoByIdLiveData(SyncWorker.UUID) - fun dispatch(action: HomeViewAction) { - when (action) { - is HomeViewAction.Sync -> sync() - is HomeViewAction.ChangeFilter -> changeFilter(action.filterState) - is HomeViewAction.FetchArticles -> fetchArticles() - is HomeViewAction.InputSearchContent -> inputSearchContent(action.content) - } - } - - private fun sync() { + fun sync() { rssRepository.get().doSync() } - private fun changeFilter(filterState: FilterState) { - _filterState.update { + fun changeFilter(filterState: FilterState) { + _filterUiState.update { it.copy( group = filterState.group, feed = filterState.feed, @@ -57,24 +47,24 @@ class HomeViewModel @Inject constructor( fetchArticles() } - private fun fetchArticles() { - _viewState.update { + fun fetchArticles() { + _homeUiState.update { it.copy( pagingData = Pager(PagingConfig(pageSize = 50)) { - if (_viewState.value.searchContent.isNotBlank()) { + if (_homeUiState.value.searchContent.isNotBlank()) { rssRepository.get().searchArticles( - content = _viewState.value.searchContent.trim(), - groupId = _filterState.value.group?.id, - feedId = _filterState.value.feed?.id, - isStarred = _filterState.value.filter.isStarred(), - isUnread = _filterState.value.filter.isUnread(), + content = _homeUiState.value.searchContent.trim(), + groupId = _filterUiState.value.group?.id, + feedId = _filterUiState.value.feed?.id, + isStarred = _filterUiState.value.filter.isStarred(), + isUnread = _filterUiState.value.filter.isUnread(), ) } else { rssRepository.get().pullArticles( - groupId = _filterState.value.group?.id, - feedId = _filterState.value.feed?.id, - isStarred = _filterState.value.filter.isStarred(), - isUnread = _filterState.value.filter.isUnread(), + groupId = _filterUiState.value.group?.id, + feedId = _filterUiState.value.feed?.id, + isStarred = _filterUiState.value.filter.isStarred(), + isUnread = _filterUiState.value.filter.isUnread(), ) } }.flow.map { @@ -94,8 +84,8 @@ class HomeViewModel @Inject constructor( } } - private fun inputSearchContent(content: String) { - _viewState.update { + fun inputSearchContent(content: String) { + _homeUiState.update { it.copy( searchContent = content, ) @@ -110,21 +100,7 @@ data class FilterState( val filter: Filter = Filter.All, ) -data class HomeViewState( +data class HomeUiState( val pagingData: Flow> = emptyFlow(), val searchContent: String = "", -) - -sealed class HomeViewAction { - object Sync : HomeViewAction() - - data class ChangeFilter( - val filterState: FilterState - ) : HomeViewAction() - - object FetchArticles : HomeViewAction() - - data class InputSearchContent( - val content: String, - ) : HomeViewAction() -} \ No newline at end of file +) \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedItem.kt b/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedItem.kt index f80a65d..63ed5e2 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedItem.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedItem.kt @@ -21,7 +21,6 @@ import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import me.ash.reader.data.entity.Feed import me.ash.reader.ui.component.FeedIcon -import me.ash.reader.ui.page.home.feeds.drawer.feed.FeedOptionViewAction import me.ash.reader.ui.page.home.feeds.drawer.feed.FeedOptionViewModel import kotlin.math.ln @@ -55,7 +54,7 @@ fun FeedItem( }, onLongClick = { view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP) - feedOptionViewModel.dispatch(FeedOptionViewAction.Show(scope, feed.id)) + feedOptionViewModel.showDrawer(scope, feed.id) } ) .padding(vertical = 14.dp), diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsPage.kt b/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsPage.kt index c33a89d..4feb513 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsPage.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsPage.kt @@ -39,12 +39,10 @@ import me.ash.reader.ui.ext.findActivity import me.ash.reader.ui.ext.getCurrentVersion import me.ash.reader.ui.page.common.RouteName import me.ash.reader.ui.page.home.FilterState -import me.ash.reader.ui.page.home.HomeViewAction import me.ash.reader.ui.page.home.HomeViewModel import me.ash.reader.ui.page.home.feeds.drawer.feed.FeedOptionDrawer import me.ash.reader.ui.page.home.feeds.drawer.group.GroupOptionDrawer import me.ash.reader.ui.page.home.feeds.subscribe.SubscribeDialog -import me.ash.reader.ui.page.home.feeds.subscribe.SubscribeViewAction import me.ash.reader.ui.page.home.feeds.subscribe.SubscribeViewModel @OptIn( @@ -67,8 +65,8 @@ fun FeedsPage( val filterBarPadding = LocalFeedsFilterBarPadding.current val filterBarTonalElevation = LocalFeedsFilterBarTonalElevation.current - val feedsViewState = feedsViewModel.viewState.collectAsStateValue() - val filterState = homeViewModel.filterState.collectAsStateValue() + val feedsUiState = feedsViewModel.feedsUiState.collectAsStateValue() + val filterUiState = homeViewModel.filterUiState.collectAsStateValue() val newVersion = LocalNewVersionNumber.current val skipVersion = LocalSkipVersionNumber.current @@ -92,22 +90,22 @@ fun FeedsPage( val launcher = rememberLauncherForActivityResult( ActivityResultContracts.CreateDocument() ) { result -> - feedsViewModel.dispatch(FeedsViewAction.ExportAsString { string -> + feedsViewModel.exportAsOpml { string -> result?.let { uri -> context.contentResolver.openOutputStream(uri)?.let { outputStream -> outputStream.write(string.toByteArray()) } } - }) + } } LaunchedEffect(Unit) { - feedsViewModel.dispatch(FeedsViewAction.FetchAccount) + feedsViewModel.fetchAccount() } - LaunchedEffect(filterState) { - snapshotFlow { filterState }.collect { - feedsViewModel.dispatch(FeedsViewAction.FetchData(it)) + LaunchedEffect(filterUiState) { + snapshotFlow { filterUiState }.collect { + feedsViewModel.fetchData(it) } } @@ -138,14 +136,14 @@ fun FeedsPage( contentDescription = stringResource(R.string.refresh), tint = MaterialTheme.colorScheme.onSurface, ) { - if (!isSyncing) homeViewModel.dispatch(HomeViewAction.Sync) + if (!isSyncing) homeViewModel.sync() } FeedbackIconButton( imageVector = Icons.Rounded.Add, contentDescription = stringResource(R.string.subscribe), tint = MaterialTheme.colorScheme.onSurface, ) { - subscribeViewModel.dispatch(SubscribeViewAction.Show) + subscribeViewModel.showDrawer() } }, content = { @@ -159,15 +157,15 @@ fun FeedsPage( } ) }, - text = feedsViewState.account?.name ?: stringResource(R.string.read_you), + text = feedsUiState.account?.name ?: stringResource(R.string.read_you), desc = if (isSyncing) stringResource(R.string.syncing) else "", ) } item { Banner( - title = filterState.filter.getName(), - desc = feedsViewState.importantCount.ifEmpty { stringResource(R.string.loading) }, - icon = filterState.filter.iconOutline, + title = filterUiState.filter.getName(), + desc = feedsUiState.importantCount.ifEmpty { stringResource(R.string.loading) }, + icon = filterUiState.filter.iconOutline, action = { Icon( imageVector = Icons.Outlined.KeyboardArrowRight, @@ -178,7 +176,7 @@ fun FeedsPage( filterChange( navController = navController, homeViewModel = homeViewModel, - filterState = filterState.copy( + filterState = filterUiState.copy( group = null, feed = null, ) @@ -193,7 +191,7 @@ fun FeedsPage( ) Spacer(modifier = Modifier.height(8.dp)) } - itemsIndexed(feedsViewState.groupWithFeedList) { index, groupWithFeed -> + itemsIndexed(feedsUiState.groupWithFeedList) { index, groupWithFeed -> // Crossfade(targetState = groupWithFeed) { groupWithFeed -> Column { GroupItem( @@ -205,7 +203,7 @@ fun FeedsPage( filterChange( navController = navController, homeViewModel = homeViewModel, - filterState = filterState.copy( + filterState = filterUiState.copy( group = groupWithFeed.group, feed = null, ) @@ -215,14 +213,14 @@ fun FeedsPage( filterChange( navController = navController, homeViewModel = homeViewModel, - filterState = filterState.copy( + filterState = filterUiState.copy( group = null, feed = feed, ) ) } ) - if (index != feedsViewState.groupWithFeedList.lastIndex) { + if (index != feedsUiState.groupWithFeedList.lastIndex) { Spacer(modifier = Modifier.height(8.dp)) } } @@ -236,7 +234,7 @@ fun FeedsPage( }, bottomBar = { FilterBar( - filter = filterState.filter, + filter = filterUiState.filter, filterBarStyle = filterBarStyle.value, filterBarFilled = filterBarFilled.value, filterBarPadding = filterBarPadding.dp, @@ -245,7 +243,7 @@ fun FeedsPage( filterChange( navController = navController, homeViewModel = homeViewModel, - filterState = filterState.copy(filter = it), + filterState = filterUiState.copy(filter = it), isNavigate = false, ) } @@ -263,7 +261,7 @@ private fun filterChange( filterState: FilterState, isNavigate: Boolean = true, ) { - homeViewModel.dispatch(HomeViewAction.ChangeFilter(filterState)) + homeViewModel.changeFilter(filterState) if (isNavigate) { navController.navigate(RouteName.FLOW) { launchSingleTop = true diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsViewModel.kt b/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsViewModel.kt index 6030473..6fd8243 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsViewModel.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsViewModel.kt @@ -31,21 +31,12 @@ class FeedsViewModel @Inject constructor( @DispatcherIO private val dispatcherIO: CoroutineDispatcher, ) : ViewModel() { - private val _viewState = MutableStateFlow(FeedsViewState()) - val viewState: StateFlow = _viewState.asStateFlow() + private val _feedsUiState = MutableStateFlow(FeedsUiState()) + val feedsUiState: StateFlow = _feedsUiState.asStateFlow() - fun dispatch(action: FeedsViewAction) { - when (action) { - is FeedsViewAction.FetchAccount -> fetchAccount() - is FeedsViewAction.FetchData -> fetchData(action.filterState) - is FeedsViewAction.ExportAsString -> exportAsOpml(action.callback) - is FeedsViewAction.ScrollToItem -> scrollToItem(action.index) - } - } - - private fun fetchAccount() { + fun fetchAccount() { viewModelScope.launch(dispatcherIO) { - _viewState.update { + _feedsUiState.update { it.copy( account = accountRepository.getCurrentAccount() ) @@ -53,7 +44,7 @@ class FeedsViewModel @Inject constructor( } } - private fun exportAsOpml(callback: (String) -> Unit = {}) { + fun exportAsOpml(callback: (String) -> Unit = {}) { viewModelScope.launch(dispatcherDefault) { try { callback(opmlRepository.saveToString()) @@ -63,7 +54,7 @@ class FeedsViewModel @Inject constructor( } } - private fun fetchData(filterState: FilterState) { + fun fetchData(filterState: FilterState) { viewModelScope.launch(dispatcherIO) { pullFeeds( isStarred = filterState.filter.isStarred(), @@ -109,13 +100,25 @@ class FeedsViewModel @Inject constructor( } groupWithFeedList }.onEach { groupWithFeedList -> - _viewState.update { + _feedsUiState.update { it.copy( importantCount = groupWithFeedList.sumOf { it.group.important ?: 0 }.run { when { - isStarred -> stringsRepository.getQuantityString(R.plurals.starred_desc, this, this) - isUnread -> stringsRepository.getQuantityString(R.plurals.unread_desc, this, this) - else -> stringsRepository.getQuantityString(R.plurals.all_desc, this, this) + isStarred -> stringsRepository.getQuantityString( + R.plurals.starred_desc, + this, + this + ) + isUnread -> stringsRepository.getQuantityString( + R.plurals.unread_desc, + this, + this + ) + else -> stringsRepository.getQuantityString( + R.plurals.all_desc, + this, + this + ) } }, groupWithFeedList = groupWithFeedList, @@ -126,15 +129,9 @@ class FeedsViewModel @Inject constructor( Log.e("RLog", "catch in articleRepository.pullFeeds(): ${it.message}") }.flowOn(dispatcherDefault).collect() } - - private fun scrollToItem(index: Int) { - viewModelScope.launch { - _viewState.value.listState.scrollToItem(index) - } - } } -data class FeedsViewState( +data class FeedsUiState( val account: Account? = null, val importantCount: String = "", val groupWithFeedList: List = emptyList(), @@ -142,19 +139,3 @@ data class FeedsViewState( val listState: LazyListState = LazyListState(), val groupsVisible: Boolean = true, ) - -sealed class FeedsViewAction { - data class FetchData( - val filterState: FilterState, - ) : FeedsViewAction() - - object FetchAccount : FeedsViewAction() - - data class ExportAsString( - val callback: (String) -> Unit = {} - ) : FeedsViewAction() - - data class ScrollToItem( - val index: Int - ) : FeedsViewAction() -} \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feeds/GroupItem.kt b/app/src/main/java/me/ash/reader/ui/page/home/feeds/GroupItem.kt index 3afdd20..65e1ab6 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/feeds/GroupItem.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/feeds/GroupItem.kt @@ -28,7 +28,6 @@ import me.ash.reader.R import me.ash.reader.data.entity.Feed import me.ash.reader.data.entity.Group import me.ash.reader.ui.ext.alphaLN -import me.ash.reader.ui.page.home.feeds.drawer.group.GroupOptionViewAction import me.ash.reader.ui.page.home.feeds.drawer.group.GroupOptionViewModel @OptIn(androidx.compose.foundation.ExperimentalFoundationApi::class) @@ -61,7 +60,7 @@ fun GroupItem( }, onLongClick = { view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP) - groupOptionViewModel.dispatch(GroupOptionViewAction.Show(scope, group.id)) + groupOptionViewModel.showDrawer(scope, group.id) } ) .padding(top = 22.dp) diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/feed/ClearFeedDialog.kt b/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/feed/ClearFeedDialog.kt index 0ead3a7..412c76a 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/feed/ClearFeedDialog.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/feed/ClearFeedDialog.kt @@ -7,7 +7,6 @@ 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 @@ -18,19 +17,18 @@ import me.ash.reader.ui.ext.showToast @Composable fun ClearFeedDialog( - modifier: Modifier = Modifier, feedName: String, - viewModel: FeedOptionViewModel = hiltViewModel(), + feedOptionViewModel: FeedOptionViewModel = hiltViewModel(), ) { val context = LocalContext.current - val viewState = viewModel.viewState.collectAsStateValue() + val feedOptionUiState = feedOptionViewModel.feedOptionUiState.collectAsStateValue() val scope = rememberCoroutineScope() val toastString = stringResource(R.string.clear_articles_in_feed_toast, feedName) Dialog( - visible = viewState.clearDialogVisible, + visible = feedOptionUiState.clearDialogVisible, onDismissRequest = { - viewModel.dispatch(FeedOptionViewAction.HideClearDialog) + feedOptionViewModel.hideClearDialog() }, icon = { Icon( @@ -47,11 +45,11 @@ fun ClearFeedDialog( confirmButton = { TextButton( onClick = { - viewModel.dispatch(FeedOptionViewAction.Clear { - viewModel.dispatch(FeedOptionViewAction.HideClearDialog) - viewModel.dispatch(FeedOptionViewAction.Hide(scope)) + feedOptionViewModel.clearFeed { + feedOptionViewModel.hideClearDialog() + feedOptionViewModel.hideDrawer(scope) context.showToast(toastString) - }) + } } ) { Text( @@ -62,7 +60,7 @@ fun ClearFeedDialog( dismissButton = { TextButton( onClick = { - viewModel.dispatch(FeedOptionViewAction.HideClearDialog) + feedOptionViewModel.hideClearDialog() } ) { Text( diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/feed/DeleteFeedDialog.kt b/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/feed/DeleteFeedDialog.kt index c977abe..564d529 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/feed/DeleteFeedDialog.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/feed/DeleteFeedDialog.kt @@ -7,7 +7,6 @@ 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 @@ -18,19 +17,18 @@ import me.ash.reader.ui.ext.showToast @Composable fun DeleteFeedDialog( - modifier: Modifier = Modifier, feedName: String, - viewModel: FeedOptionViewModel = hiltViewModel(), + feedOptionViewModel: FeedOptionViewModel = hiltViewModel(), ) { val context = LocalContext.current - val viewState = viewModel.viewState.collectAsStateValue() + val feedOptionUiState = feedOptionViewModel.feedOptionUiState.collectAsStateValue() val scope = rememberCoroutineScope() val toastString = stringResource(R.string.delete_toast, feedName) Dialog( - visible = viewState.deleteDialogVisible, + visible = feedOptionUiState.deleteDialogVisible, onDismissRequest = { - viewModel.dispatch(FeedOptionViewAction.HideDeleteDialog) + feedOptionViewModel.hideDeleteDialog() }, icon = { Icon( @@ -47,11 +45,11 @@ fun DeleteFeedDialog( confirmButton = { TextButton( onClick = { - viewModel.dispatch(FeedOptionViewAction.Delete { - viewModel.dispatch(FeedOptionViewAction.HideDeleteDialog) - viewModel.dispatch(FeedOptionViewAction.Hide(scope)) + feedOptionViewModel.delete { + feedOptionViewModel.hideDeleteDialog() + feedOptionViewModel.hideDrawer(scope) context.showToast(toastString) - }) + } } ) { Text( @@ -62,7 +60,7 @@ fun DeleteFeedDialog( dismissButton = { TextButton( onClick = { - viewModel.dispatch(FeedOptionViewAction.HideDeleteDialog) + feedOptionViewModel.hideDeleteDialog() } ) { Text( diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/feed/FeedOptionDrawer.kt b/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/feed/FeedOptionDrawer.kt index d3d03b0..5672846 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/feed/FeedOptionDrawer.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/feed/FeedOptionDrawer.kt @@ -19,35 +19,34 @@ import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import kotlinx.coroutines.launch import me.ash.reader.R +import me.ash.reader.ui.component.FeedIcon import me.ash.reader.ui.component.base.BottomDrawer import me.ash.reader.ui.component.base.TextFieldDialog import me.ash.reader.ui.ext.collectAsStateValue import me.ash.reader.ui.ext.roundClick import me.ash.reader.ui.ext.showToast -import me.ash.reader.ui.component.FeedIcon import me.ash.reader.ui.page.home.feeds.subscribe.ResultView @OptIn(ExperimentalMaterialApi::class) @Composable fun FeedOptionDrawer( - modifier: Modifier = Modifier, 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) + val feedOptionUiState = feedOptionViewModel.feedOptionUiState.collectAsStateValue() + val feed = feedOptionUiState.feed + val toastString = stringResource(R.string.rename_toast, feedOptionUiState.newName) - BackHandler(viewState.drawerState.isVisible) { + BackHandler(feedOptionUiState.drawerState.isVisible) { scope.launch { - viewState.drawerState.hide() + feedOptionUiState.drawerState.hide() } } BottomDrawer( - drawerState = viewState.drawerState, + drawerState = feedOptionUiState.drawerState, sheetContent = { Column(modifier = Modifier.navigationBarsPadding()) { Column( @@ -65,7 +64,7 @@ fun FeedOptionDrawer( Spacer(modifier = Modifier.height(16.dp)) Text( modifier = Modifier.roundClick { - feedOptionViewModel.dispatch(FeedOptionViewAction.ShowRenameDialog) + feedOptionViewModel.showRenameDialog() }, text = feed?.name ?: stringResource(R.string.unknown), style = MaterialTheme.typography.headlineSmall, @@ -77,32 +76,32 @@ fun FeedOptionDrawer( 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, + groups = feedOptionUiState.groups, + selectedAllowNotificationPreset = feedOptionUiState.feed?.isNotification ?: false, + selectedParseFullContentPreset = feedOptionUiState.feed?.isFullContent ?: false, isMoveToGroup = true, showUnsubscribe = true, - selectedGroupId = viewState.feed?.groupId ?: "", + selectedGroupId = feedOptionUiState.feed?.groupId ?: "", allowNotificationPresetOnClick = { - feedOptionViewModel.dispatch(FeedOptionViewAction.ChangeAllowNotificationPreset) + feedOptionViewModel.changeAllowNotificationPreset() }, parseFullContentPresetOnClick = { - feedOptionViewModel.dispatch(FeedOptionViewAction.ChangeParseFullContentPreset) + feedOptionViewModel.changeParseFullContentPreset() }, clearArticlesOnClick = { - feedOptionViewModel.dispatch(FeedOptionViewAction.ShowClearDialog) + feedOptionViewModel.showClearDialog() }, unsubscribeOnClick = { - feedOptionViewModel.dispatch(FeedOptionViewAction.ShowDeleteDialog) + feedOptionViewModel.showDeleteDialog() }, onGroupClick = { - feedOptionViewModel.dispatch(FeedOptionViewAction.SelectedGroup(it)) + feedOptionViewModel.selectedGroup(it) }, onAddNewGroup = { - feedOptionViewModel.dispatch(FeedOptionViewAction.ShowNewGroupDialog) + feedOptionViewModel.showNewGroupDialog() }, onFeedUrlClick = { - feedOptionViewModel.dispatch(FeedOptionViewAction.ShowChangeUrlDialog) + feedOptionViewModel.showFeedUrlDialog() } ) } @@ -116,56 +115,56 @@ fun FeedOptionDrawer( ClearFeedDialog(feedName = feed?.name ?: "") TextFieldDialog( - visible = viewState.newGroupDialogVisible, + visible = feedOptionUiState.newGroupDialogVisible, title = stringResource(R.string.create_new_group), icon = Icons.Outlined.CreateNewFolder, - value = viewState.newGroupContent, + value = feedOptionUiState.newGroupContent, placeholder = stringResource(R.string.name), onValueChange = { - feedOptionViewModel.dispatch(FeedOptionViewAction.InputNewGroup(it)) + feedOptionViewModel.inputNewGroup(it) }, onDismissRequest = { - feedOptionViewModel.dispatch(FeedOptionViewAction.HideNewGroupDialog) + feedOptionViewModel.hideNewGroupDialog() }, onConfirm = { - feedOptionViewModel.dispatch(FeedOptionViewAction.AddNewGroup) + feedOptionViewModel.addNewGroup() } ) TextFieldDialog( - visible = viewState.renameDialogVisible, + visible = feedOptionUiState.renameDialogVisible, title = stringResource(R.string.rename), icon = Icons.Outlined.Edit, - value = viewState.newName, + value = feedOptionUiState.newName, placeholder = stringResource(R.string.name), onValueChange = { - feedOptionViewModel.dispatch(FeedOptionViewAction.InputNewName(it)) + feedOptionViewModel.inputNewName(it) }, onDismissRequest = { - feedOptionViewModel.dispatch(FeedOptionViewAction.HideRenameDialog) + feedOptionViewModel.hideRenameDialog() }, onConfirm = { - feedOptionViewModel.dispatch(FeedOptionViewAction.Rename) - feedOptionViewModel.dispatch(FeedOptionViewAction.Hide(scope)) + feedOptionViewModel.renameFeed() + feedOptionViewModel.hideDrawer(scope) context.showToast(toastString) } ) TextFieldDialog( - visible = viewState.changeUrlDialogVisible, + visible = feedOptionUiState.changeUrlDialogVisible, title = stringResource(R.string.change_url), icon = Icons.Outlined.Edit, - value = viewState.newUrl, + value = feedOptionUiState.newUrl, placeholder = stringResource(R.string.feed_url_placeholder), onValueChange = { - feedOptionViewModel.dispatch(FeedOptionViewAction.InputNewUrl(it)) + feedOptionViewModel.inputNewUrl(it) }, onDismissRequest = { - feedOptionViewModel.dispatch(FeedOptionViewAction.HideChangeUrlDialog) + feedOptionViewModel.hideFeedUrlDialog() }, onConfirm = { - feedOptionViewModel.dispatch(FeedOptionViewAction.ChangeUrl) - feedOptionViewModel.dispatch(FeedOptionViewAction.Hide(scope)) + feedOptionViewModel.changeFeedUrl() + feedOptionViewModel.hideDrawer(scope) } ) } \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/feed/FeedOptionViewModel.kt b/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/feed/FeedOptionViewModel.kt index 00db51a..6ed3941 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/feed/FeedOptionViewModel.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/feed/FeedOptionViewModel.kt @@ -21,9 +21,7 @@ import me.ash.reader.data.module.DispatcherMain import me.ash.reader.data.repository.RssRepository import javax.inject.Inject -@OptIn( - ExperimentalMaterialApi::class -) +@OptIn(ExperimentalMaterialApi::class) @HiltViewModel class FeedOptionViewModel @Inject constructor( private val rssRepository: RssRepository, @@ -32,13 +30,13 @@ class FeedOptionViewModel @Inject constructor( @DispatcherIO private val dispatcherIO: CoroutineDispatcher, ) : ViewModel() { - private val _viewState = MutableStateFlow(FeedOptionViewState()) - val viewState: StateFlow = _viewState.asStateFlow() + private val _feedOptionUiState = MutableStateFlow(FeedOptionUiState()) + val feedOptionUiState: StateFlow = _feedOptionUiState.asStateFlow() init { viewModelScope.launch(dispatcherIO) { rssRepository.get().pullGroups().collect { groups -> - _viewState.update { + _feedOptionUiState.update { it.copy( groups = groups ) @@ -47,37 +45,9 @@ class FeedOptionViewModel @Inject constructor( } } - fun dispatch(action: FeedOptionViewAction) { - when (action) { - is FeedOptionViewAction.Show -> show(action.scope, action.feedId) - is FeedOptionViewAction.Hide -> hide(action.scope) - is FeedOptionViewAction.SelectedGroup -> selectedGroup(action.groupId) - is FeedOptionViewAction.InputNewGroup -> inputNewGroup(action.content) - is FeedOptionViewAction.ChangeAllowNotificationPreset -> changeAllowNotificationPreset() - is FeedOptionViewAction.ChangeParseFullContentPreset -> changeParseFullContentPreset() - 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) - is FeedOptionViewAction.InputNewName -> inputNewName(action.content) - is FeedOptionViewAction.Rename -> rename() - is FeedOptionViewAction.ShowRenameDialog -> changeRenameDialogVisible(true) - is FeedOptionViewAction.HideRenameDialog -> changeRenameDialogVisible(false) - is FeedOptionViewAction.InputNewUrl -> inputNewUrl(action.content) - is FeedOptionViewAction.ChangeUrl -> changeFeedUrl() - is FeedOptionViewAction.HideChangeUrlDialog -> changeFeedUrlDialogVisible(false) - is FeedOptionViewAction.ShowChangeUrlDialog -> changeFeedUrlDialogVisible(true) - } - } - private suspend fun fetchFeed(feedId: String) { val feed = rssRepository.get().findFeedById(feedId) - _viewState.update { + _feedOptionUiState.update { it.copy( feed = feed, selectedGroupId = feed?.groupId ?: "", @@ -85,48 +55,57 @@ class FeedOptionViewModel @Inject constructor( } } - private fun show(scope: CoroutineScope, feedId: String) { + fun showDrawer(scope: CoroutineScope, feedId: String) { scope.launch { fetchFeed(feedId) - _viewState.value.drawerState.show() + _feedOptionUiState.value.drawerState.show() } } - private fun hide(scope: CoroutineScope) { + fun hideDrawer(scope: CoroutineScope) { scope.launch { - _viewState.value.drawerState.hide() + _feedOptionUiState.value.drawerState.hide() } } - private fun changeNewGroupDialogVisible(visible: Boolean) { - _viewState.update { + fun showNewGroupDialog() { + _feedOptionUiState.update { it.copy( - newGroupDialogVisible = visible, + newGroupDialogVisible = true, newGroupContent = "", ) } } - private fun inputNewGroup(content: String) { - _viewState.update { + fun hideNewGroupDialog() { + _feedOptionUiState.update { + it.copy( + newGroupDialogVisible = false, + newGroupContent = "", + ) + } + } + + fun inputNewGroup(content: String) { + _feedOptionUiState.update { it.copy( newGroupContent = content ) } } - private fun addNewGroup() { - if (_viewState.value.newGroupContent.isNotBlank()) { + fun addNewGroup() { + if (_feedOptionUiState.value.newGroupContent.isNotBlank()) { viewModelScope.launch { - selectedGroup(rssRepository.get().addGroup(_viewState.value.newGroupContent)) - changeNewGroupDialogVisible(false) + selectedGroup(rssRepository.get().addGroup(_feedOptionUiState.value.newGroupContent)) + hideNewGroupDialog() } } } - private fun selectedGroup(groupId: String) { + fun selectedGroup(groupId: String) { viewModelScope.launch(dispatcherIO) { - _viewState.value.feed?.let { + _feedOptionUiState.value.feed?.let { rssRepository.get().updateFeed( it.copy( groupId = groupId @@ -137,9 +116,9 @@ class FeedOptionViewModel @Inject constructor( } } - private fun changeParseFullContentPreset() { + fun changeParseFullContentPreset() { viewModelScope.launch(dispatcherIO) { - _viewState.value.feed?.let { + _feedOptionUiState.value.feed?.let { rssRepository.get().updateFeed( it.copy( isFullContent = !it.isFullContent @@ -150,9 +129,9 @@ class FeedOptionViewModel @Inject constructor( } } - private fun changeAllowNotificationPreset() { + fun changeAllowNotificationPreset() { viewModelScope.launch(dispatcherIO) { - _viewState.value.feed?.let { + _feedOptionUiState.value.feed?.let { rssRepository.get().updateFeed( it.copy( isNotification = !it.isNotification @@ -163,8 +142,8 @@ class FeedOptionViewModel @Inject constructor( } } - private fun delete(callback: () -> Unit = {}) { - _viewState.value.feed?.let { + fun delete(callback: () -> Unit = {}) { + _feedOptionUiState.value.feed?.let { viewModelScope.launch(dispatcherIO) { rssRepository.get().deleteFeed(it) withContext(dispatcherMain) { @@ -174,40 +153,40 @@ class FeedOptionViewModel @Inject constructor( } } - private fun hideDeleteDialog() { - _viewState.update { + fun hideDeleteDialog() { + _feedOptionUiState.update { it.copy( deleteDialogVisible = false, ) } } - private fun showDeleteDialog() { - _viewState.update { + fun showDeleteDialog() { + _feedOptionUiState.update { it.copy( deleteDialogVisible = true, ) } } - private fun showClearDialog() { - _viewState.update { + fun showClearDialog() { + _feedOptionUiState.update { it.copy( clearDialogVisible = true, ) } } - private fun hideClearDialog() { - _viewState.update { + fun hideClearDialog() { + _feedOptionUiState.update { it.copy( clearDialogVisible = false, ) } } - private fun clear(callback: () -> Unit = {}) { - _viewState.value.feed?.let { + fun clearFeed(callback: () -> Unit = {}) { + _feedOptionUiState.value.feed?.let { viewModelScope.launch(dispatcherIO) { rssRepository.get().deleteArticles(feed = it) withContext(dispatcherMain) { @@ -217,15 +196,15 @@ class FeedOptionViewModel @Inject constructor( } } - private fun rename() { - _viewState.value.feed?.let { + fun renameFeed() { + _feedOptionUiState.value.feed?.let { viewModelScope.launch { rssRepository.get().updateFeed( it.copy( - name = _viewState.value.newName + name = _feedOptionUiState.value.newName ) ) - _viewState.update { + _feedOptionUiState.update { it.copy( renameDialogVisible = false, ) @@ -234,49 +213,67 @@ class FeedOptionViewModel @Inject constructor( } } - private fun changeRenameDialogVisible(visible: Boolean) { - _viewState.update { + fun showRenameDialog() { + _feedOptionUiState.update { it.copy( - renameDialogVisible = visible, - newName = if (visible) _viewState.value.feed?.name ?: "" else "", + renameDialogVisible = true, + newName = _feedOptionUiState.value.feed?.name ?: "", ) } } - private fun inputNewName(content: String) { - _viewState.update { + fun hideRenameDialog() { + _feedOptionUiState.update { + it.copy( + renameDialogVisible = false, + newName = "", + ) + } + } + + fun inputNewName(content: String) { + _feedOptionUiState.update { it.copy( newName = content ) } } - private fun changeFeedUrlDialogVisible(visible: Boolean) { - _viewState.update { + fun showFeedUrlDialog() { + _feedOptionUiState.update { it.copy( - changeUrlDialogVisible = visible, - newUrl = if (visible) _viewState.value.feed?.url ?: "" else "", + changeUrlDialogVisible = true, + newUrl = _feedOptionUiState.value.feed?.url ?: "", ) } } - private fun inputNewUrl(content: String) { - _viewState.update { + fun hideFeedUrlDialog() { + _feedOptionUiState.update { + it.copy( + changeUrlDialogVisible = false, + newUrl = "", + ) + } + } + + fun inputNewUrl(content: String) { + _feedOptionUiState.update { it.copy( newUrl = content ) } } - private fun changeFeedUrl() { - _viewState.value.feed?.let { + fun changeFeedUrl() { + _feedOptionUiState.value.feed?.let { viewModelScope.launch { rssRepository.get().updateFeed( it.copy( - url = _viewState.value.newUrl + url = _feedOptionUiState.value.newUrl ) ) - _viewState.update { + _feedOptionUiState.update { it.copy( changeUrlDialogVisible = false, ) @@ -287,7 +284,7 @@ class FeedOptionViewModel @Inject constructor( } @OptIn(ExperimentalMaterialApi::class) -data class FeedOptionViewState( +data class FeedOptionUiState( var drawerState: ModalBottomSheetState = ModalBottomSheetState(ModalBottomSheetValue.Hidden), val feed: Feed? = null, val selectedGroupId: String = "", @@ -301,57 +298,3 @@ data class FeedOptionViewState( val newUrl: String = "", val changeUrlDialogVisible: Boolean = false, ) - -sealed class FeedOptionViewAction { - data class Show( - val scope: CoroutineScope, - val feedId: String - ) : FeedOptionViewAction() - - data class Hide( - val scope: CoroutineScope, - ) : FeedOptionViewAction() - - object ChangeAllowNotificationPreset : FeedOptionViewAction() - object ChangeParseFullContentPreset : FeedOptionViewAction() - - data class SelectedGroup( - val groupId: String - ) : FeedOptionViewAction() - - data class InputNewGroup( - val content: String - ) : FeedOptionViewAction() - - data class Delete( - val callback: () -> Unit = {} - ) : 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() - - object ShowRenameDialog : FeedOptionViewAction() - object HideRenameDialog : FeedOptionViewAction() - object Rename : FeedOptionViewAction() - data class InputNewName( - val content: String - ) : FeedOptionViewAction() - - object ShowChangeUrlDialog : FeedOptionViewAction() - object HideChangeUrlDialog : FeedOptionViewAction() - object ChangeUrl : FeedOptionViewAction() - data class InputNewUrl( - val content: String - ) : FeedOptionViewAction() -} diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/group/AllAllowNotificationDialog.kt b/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/group/AllAllowNotificationDialog.kt index 6ca6e7f..d9c5094 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/group/AllAllowNotificationDialog.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/group/AllAllowNotificationDialog.kt @@ -7,7 +7,6 @@ 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 @@ -18,20 +17,19 @@ import me.ash.reader.ui.ext.showToast @Composable fun AllAllowNotificationDialog( - modifier: Modifier = Modifier, groupName: String, - viewModel: GroupOptionViewModel = hiltViewModel(), + groupOptionViewModel: GroupOptionViewModel = hiltViewModel(), ) { val context = LocalContext.current - val viewState = viewModel.viewState.collectAsStateValue() + val groupOptionUiState = groupOptionViewModel.groupOptionUiState.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, + visible = groupOptionUiState.allAllowNotificationDialogVisible, onDismissRequest = { - viewModel.dispatch(GroupOptionViewAction.HideAllAllowNotificationDialog) + groupOptionViewModel.hideAllAllowNotificationDialog() }, icon = { Icon( @@ -48,11 +46,11 @@ fun AllAllowNotificationDialog( confirmButton = { TextButton( onClick = { - viewModel.dispatch(GroupOptionViewAction.AllAllowNotification(true) { - viewModel.dispatch(GroupOptionViewAction.HideAllAllowNotificationDialog) - viewModel.dispatch(GroupOptionViewAction.Hide(scope)) + groupOptionViewModel.allAllowNotification(true) { + groupOptionViewModel.hideAllAllowNotificationDialog() + groupOptionViewModel.hideDrawer(scope) context.showToast(allowToastString) - }) + } } ) { Text( @@ -63,11 +61,11 @@ fun AllAllowNotificationDialog( dismissButton = { TextButton( onClick = { - viewModel.dispatch(GroupOptionViewAction.AllAllowNotification(false) { - viewModel.dispatch(GroupOptionViewAction.HideAllAllowNotificationDialog) - viewModel.dispatch(GroupOptionViewAction.Hide(scope)) + groupOptionViewModel.allAllowNotification(false) { + groupOptionViewModel.hideAllAllowNotificationDialog() + groupOptionViewModel.hideDrawer(scope) context.showToast(denyToastString) - }) + } } ) { Text( diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/group/AllMoveToGroupDialog.kt b/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/group/AllMoveToGroupDialog.kt index 7e39a85..79c2f53 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/group/AllMoveToGroupDialog.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/group/AllMoveToGroupDialog.kt @@ -7,7 +7,6 @@ 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 @@ -18,20 +17,21 @@ import me.ash.reader.ui.ext.showToast @Composable fun AllMoveToGroupDialog( - modifier: Modifier = Modifier, groupName: String, - viewModel: GroupOptionViewModel = hiltViewModel(), + groupOptionViewModel: GroupOptionViewModel = hiltViewModel(), ) { val context = LocalContext.current - val viewState = viewModel.viewState.collectAsStateValue() + val groupOptionUiState = groupOptionViewModel.groupOptionUiState.collectAsStateValue() val scope = rememberCoroutineScope() - val toastString = - stringResource(R.string.all_move_to_group_toast, viewState.targetGroup?.name ?: "") + val toastString = stringResource( + R.string.all_move_to_group_toast, + groupOptionUiState.targetGroup?.name ?: "" + ) Dialog( - visible = viewState.allMoveToGroupDialogVisible, + visible = groupOptionUiState.allMoveToGroupDialogVisible, onDismissRequest = { - viewModel.dispatch(GroupOptionViewAction.HideAllMoveToGroupDialog) + groupOptionViewModel.hideAllMoveToGroupDialog() }, icon = { Icon( @@ -47,18 +47,18 @@ fun AllMoveToGroupDialog( text = stringResource( R.string.all_move_to_group_tips, groupName, - viewState.targetGroup?.name ?: "", + groupOptionUiState.targetGroup?.name ?: "", ) ) }, confirmButton = { TextButton( onClick = { - viewModel.dispatch(GroupOptionViewAction.AllMoveToGroup { - viewModel.dispatch(GroupOptionViewAction.HideAllMoveToGroupDialog) - viewModel.dispatch(GroupOptionViewAction.Hide(scope)) + groupOptionViewModel.allMoveToGroup { + groupOptionViewModel.hideAllMoveToGroupDialog() + groupOptionViewModel.hideDrawer(scope) context.showToast(toastString) - }) + } } ) { Text( @@ -69,7 +69,7 @@ fun AllMoveToGroupDialog( dismissButton = { TextButton( onClick = { - viewModel.dispatch(GroupOptionViewAction.HideAllMoveToGroupDialog) + groupOptionViewModel.hideAllMoveToGroupDialog() } ) { Text( diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/group/AllParseFullContentDialog.kt b/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/group/AllParseFullContentDialog.kt index bc8a92f..00e5abc 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/group/AllParseFullContentDialog.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/group/AllParseFullContentDialog.kt @@ -7,7 +7,6 @@ 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 @@ -18,20 +17,19 @@ import me.ash.reader.ui.ext.showToast @Composable fun AllParseFullContentDialog( - modifier: Modifier = Modifier, groupName: String, - viewModel: GroupOptionViewModel = hiltViewModel(), + groupOptionViewModel: GroupOptionViewModel = hiltViewModel(), ) { val context = LocalContext.current - val viewState = viewModel.viewState.collectAsStateValue() + val groupOptionUiState = groupOptionViewModel.groupOptionUiState.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, + visible = groupOptionUiState.allParseFullContentDialogVisible, onDismissRequest = { - viewModel.dispatch(GroupOptionViewAction.HideAllParseFullContentDialog) + groupOptionViewModel.hideAllParseFullContentDialog() }, icon = { Icon( @@ -48,11 +46,11 @@ fun AllParseFullContentDialog( confirmButton = { TextButton( onClick = { - viewModel.dispatch(GroupOptionViewAction.AllParseFullContent(true) { - viewModel.dispatch(GroupOptionViewAction.HideAllParseFullContentDialog) - viewModel.dispatch(GroupOptionViewAction.Hide(scope)) + groupOptionViewModel.allParseFullContent(true) { + groupOptionViewModel.hideAllParseFullContentDialog() + groupOptionViewModel.hideDrawer(scope) context.showToast(allowToastString) - }) + } } ) { Text( @@ -63,11 +61,11 @@ fun AllParseFullContentDialog( dismissButton = { TextButton( onClick = { - viewModel.dispatch(GroupOptionViewAction.AllParseFullContent(false) { - viewModel.dispatch(GroupOptionViewAction.HideAllParseFullContentDialog) - viewModel.dispatch(GroupOptionViewAction.Hide(scope)) + groupOptionViewModel.allParseFullContent(false) { + groupOptionViewModel.hideAllParseFullContentDialog() + groupOptionViewModel.hideDrawer(scope) context.showToast(denyToastString) - }) + } } ) { Text( diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/group/ClearGroupDialog.kt b/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/group/ClearGroupDialog.kt index ce02471..44b81b9 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/group/ClearGroupDialog.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/group/ClearGroupDialog.kt @@ -7,7 +7,6 @@ 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 @@ -18,19 +17,18 @@ import me.ash.reader.ui.ext.showToast @Composable fun ClearGroupDialog( - modifier: Modifier = Modifier, groupName: String, - viewModel: GroupOptionViewModel = hiltViewModel(), + groupOptionViewModel: GroupOptionViewModel = hiltViewModel(), ) { val context = LocalContext.current - val viewState = viewModel.viewState.collectAsStateValue() + val groupOptionUiState = groupOptionViewModel.groupOptionUiState.collectAsStateValue() val scope = rememberCoroutineScope() val toastString = stringResource(R.string.clear_articles_in_group_toast, groupName) Dialog( - visible = viewState.clearDialogVisible, + visible = groupOptionUiState.clearDialogVisible, onDismissRequest = { - viewModel.dispatch(GroupOptionViewAction.HideClearDialog) + groupOptionViewModel.hideClearDialog() }, icon = { Icon( @@ -47,11 +45,11 @@ fun ClearGroupDialog( confirmButton = { TextButton( onClick = { - viewModel.dispatch(GroupOptionViewAction.Clear { - viewModel.dispatch(GroupOptionViewAction.HideClearDialog) - viewModel.dispatch(GroupOptionViewAction.Hide(scope)) + groupOptionViewModel.clear { + groupOptionViewModel.hideClearDialog() + groupOptionViewModel.hideDrawer(scope) context.showToast(toastString) - }) + } } ) { Text( @@ -62,7 +60,7 @@ fun ClearGroupDialog( dismissButton = { TextButton( onClick = { - viewModel.dispatch(GroupOptionViewAction.HideClearDialog) + groupOptionViewModel.hideClearDialog() } ) { Text( diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/group/DeleteGroupDialog.kt b/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/group/DeleteGroupDialog.kt index ffa3a48..f9593dc 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/group/DeleteGroupDialog.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/group/DeleteGroupDialog.kt @@ -7,7 +7,6 @@ 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 @@ -18,19 +17,18 @@ import me.ash.reader.ui.ext.showToast @Composable fun DeleteGroupDialog( - modifier: Modifier = Modifier, groupName: String, - viewModel: GroupOptionViewModel = hiltViewModel(), + groupOptionViewModel: GroupOptionViewModel = hiltViewModel(), ) { val context = LocalContext.current - val viewState = viewModel.viewState.collectAsStateValue() + val groupOptionUiState = groupOptionViewModel.groupOptionUiState.collectAsStateValue() val scope = rememberCoroutineScope() val toastString = stringResource(R.string.delete_toast, groupName) Dialog( - visible = viewState.deleteDialogVisible, + visible = groupOptionUiState.deleteDialogVisible, onDismissRequest = { - viewModel.dispatch(GroupOptionViewAction.HideDeleteDialog) + groupOptionViewModel.hideDeleteDialog() }, icon = { Icon( @@ -47,11 +45,11 @@ fun DeleteGroupDialog( confirmButton = { TextButton( onClick = { - viewModel.dispatch(GroupOptionViewAction.Delete { - viewModel.dispatch(GroupOptionViewAction.HideDeleteDialog) - viewModel.dispatch(GroupOptionViewAction.Hide(scope)) + groupOptionViewModel.delete { + groupOptionViewModel.hideDeleteDialog() + groupOptionViewModel.hideDrawer(scope) context.showToast(toastString) - }) + } } ) { Text( @@ -62,7 +60,7 @@ fun DeleteGroupDialog( dismissButton = { TextButton( onClick = { - viewModel.dispatch(GroupOptionViewAction.HideDeleteDialog) + groupOptionViewModel.hideDeleteDialog() } ) { Text( diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/group/GroupOptionDrawer.kt b/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/group/GroupOptionDrawer.kt index ca30d36..d153ee7 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/group/GroupOptionDrawer.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/group/GroupOptionDrawer.kt @@ -42,24 +42,23 @@ import me.ash.reader.ui.ext.* @OptIn(ExperimentalMaterialApi::class) @Composable fun GroupOptionDrawer( - modifier: Modifier = Modifier, groupOptionViewModel: GroupOptionViewModel = hiltViewModel(), content: @Composable () -> Unit = {}, ) { val context = LocalContext.current val scope = rememberCoroutineScope() - val viewState = groupOptionViewModel.viewState.collectAsStateValue() - val group = viewState.group - val toastString = stringResource(R.string.rename_toast, viewState.newName) + val groupOptionUiState = groupOptionViewModel.groupOptionUiState.collectAsStateValue() + val group = groupOptionUiState.group + val toastString = stringResource(R.string.rename_toast, groupOptionUiState.newName) - BackHandler(viewState.drawerState.isVisible) { + BackHandler(groupOptionUiState.drawerState.isVisible) { scope.launch { - viewState.drawerState.hide() + groupOptionUiState.drawerState.hide() } } BottomDrawer( - drawerState = viewState.drawerState, + drawerState = groupOptionUiState.drawerState, sheetContent = { Column(modifier = Modifier.navigationBarsPadding()) { Column( @@ -75,7 +74,7 @@ fun GroupOptionDrawer( Spacer(modifier = Modifier.height(16.dp)) Text( modifier = Modifier.roundClick { - groupOptionViewModel.dispatch(GroupOptionViewAction.ShowRenameDialog) + groupOptionViewModel.showRenameDialog() }, text = group?.name ?: stringResource(R.string.unknown), style = MaterialTheme.typography.headlineSmall, @@ -106,15 +105,15 @@ fun GroupOptionDrawer( Spacer(modifier = Modifier.height(10.dp)) Preset(groupOptionViewModel, group, context) - if (viewState.groups.size != 1) { + if (groupOptionUiState.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) + if (groupOptionUiState.groups.size > 6) { + LazyRowGroups(groupOptionUiState, group, groupOptionViewModel) } else { - FlowRowGroups(viewState, group, groupOptionViewModel) + FlowRowGroups(groupOptionUiState, group, groupOptionViewModel) } } @@ -132,20 +131,20 @@ fun GroupOptionDrawer( AllParseFullContentDialog(groupName = group?.name ?: "") AllMoveToGroupDialog(groupName = group?.name ?: "") TextFieldDialog( - visible = viewState.renameDialogVisible, + visible = groupOptionUiState.renameDialogVisible, title = stringResource(R.string.rename), icon = Icons.Outlined.Edit, - value = viewState.newName, + value = groupOptionUiState.newName, placeholder = stringResource(R.string.name), onValueChange = { - groupOptionViewModel.dispatch(GroupOptionViewAction.InputNewName(it)) + groupOptionViewModel.inputNewName(it) }, onDismissRequest = { - groupOptionViewModel.dispatch(GroupOptionViewAction.HideRenameDialog) + groupOptionViewModel.hideRenameDialog() }, onConfirm = { - groupOptionViewModel.dispatch(GroupOptionViewAction.Rename) - groupOptionViewModel.dispatch(GroupOptionViewAction.Hide(scope)) + groupOptionViewModel.rename() + groupOptionViewModel.hideDrawer(scope) context.showToast(toastString) } ) @@ -177,7 +176,7 @@ private fun Preset( ) }, ) { - groupOptionViewModel.dispatch(GroupOptionViewAction.ShowAllAllowNotificationDialog) + groupOptionViewModel.showAllAllowNotificationDialog() } SelectionChip( modifier = Modifier.animateContentSize(), @@ -193,14 +192,14 @@ private fun Preset( ) }, ) { - groupOptionViewModel.dispatch(GroupOptionViewAction.ShowAllParseFullContentDialog) + groupOptionViewModel.showAllParseFullContentDialog() } SelectionChip( modifier = Modifier.animateContentSize(), content = stringResource(R.string.clear_articles), selected = false, ) { - groupOptionViewModel.dispatch(GroupOptionViewAction.ShowClearDialog) + groupOptionViewModel.showClearDialog() } if (group?.id != context.currentAccountId.getDefaultGroupId()) { SelectionChip( @@ -208,7 +207,7 @@ private fun Preset( content = stringResource(R.string.delete_group), selected = false, ) { - groupOptionViewModel.dispatch(GroupOptionViewAction.ShowDeleteDialog) + groupOptionViewModel.showDeleteDialog() } } } @@ -216,7 +215,7 @@ private fun Preset( @Composable private fun FlowRowGroups( - viewState: GroupOptionViewState, + groupOptionUiState: GroupOptionUiState, group: Group?, groupOptionViewModel: GroupOptionViewModel ) { @@ -226,16 +225,14 @@ private fun FlowRowGroups( crossAxisSpacing = 10.dp, mainAxisSpacing = 10.dp, ) { - viewState.groups.forEach { + groupOptionUiState.groups.forEach { if (it.id != group?.id) { SelectionChip( modifier = Modifier.animateContentSize(), content = it.name, selected = false, ) { - groupOptionViewModel.dispatch( - GroupOptionViewAction.ShowAllMoveToGroupDialog(it) - ) + groupOptionViewModel.showAllMoveToGroupDialog(it) } } } @@ -244,21 +241,19 @@ private fun FlowRowGroups( @Composable private fun LazyRowGroups( - viewState: GroupOptionViewState, + groupOptionUiState: GroupOptionUiState, group: Group?, groupOptionViewModel: GroupOptionViewModel ) { LazyRow { - items(viewState.groups) { + items(groupOptionUiState.groups) { if (it.id != group?.id) { SelectionChip( modifier = Modifier.animateContentSize(), content = it.name, selected = false, ) { - groupOptionViewModel.dispatch( - GroupOptionViewAction.ShowAllMoveToGroupDialog(it) - ) + groupOptionViewModel.showAllMoveToGroupDialog(it) } } Spacer(modifier = Modifier.width(10.dp)) diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/group/GroupOptionViewModel.kt b/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/group/GroupOptionViewModel.kt index e3226c8..1c2ee46 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/group/GroupOptionViewModel.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/group/GroupOptionViewModel.kt @@ -29,13 +29,13 @@ class GroupOptionViewModel @Inject constructor( @DispatcherIO private val dispatcherIO: CoroutineDispatcher, ) : ViewModel() { - private val _viewState = MutableStateFlow(GroupOptionViewState()) - val viewState: StateFlow = _viewState.asStateFlow() + private val _groupOptionUiState = MutableStateFlow(GroupOptionUiState()) + val groupOptionUiState: StateFlow = _groupOptionUiState.asStateFlow() init { viewModelScope.launch(dispatcherIO) { rssRepository.get().pullGroups().collect { groups -> - _viewState.update { + _groupOptionUiState.update { it.copy( groups = groups ) @@ -44,70 +44,25 @@ class GroupOptionViewModel @Inject constructor( } } - 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.ShowClearDialog -> showClearDialog() - is GroupOptionViewAction.HideClearDialog -> hideClearDialog() - is GroupOptionViewAction.Clear -> clear(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) - - is GroupOptionViewAction.ShowAllMoveToGroupDialog -> - changeAllMoveToGroupDialogVisible(action.targetGroup, true) - is GroupOptionViewAction.HideAllMoveToGroupDialog -> - 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) - } - } - - 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) { + fun showDrawer(scope: CoroutineScope, groupId: String) { scope.launch { - fetchGroup(groupId) - _viewState.value.drawerState.show() + _groupOptionUiState.update { + it.copy( + group = rssRepository.get().findGroupById(groupId), + ) + } + _groupOptionUiState.value.drawerState.show() } } - private fun hide(scope: CoroutineScope) { + fun hideDrawer(scope: CoroutineScope) { scope.launch { - _viewState.value.drawerState.hide() + _groupOptionUiState.value.drawerState.hide() } } - private fun allAllowNotification(isNotification: Boolean, callback: () -> Unit = {}) { - _viewState.value.group?.let { + fun allAllowNotification(isNotification: Boolean, callback: () -> Unit = {}) { + _groupOptionUiState.value.group?.let { viewModelScope.launch(dispatcherIO) { rssRepository.get().groupAllowNotification(it, isNotification) withContext(dispatcherMain) { @@ -117,16 +72,24 @@ class GroupOptionViewModel @Inject constructor( } } - private fun changeAllAllowNotificationDialogVisible(visible: Boolean) { - _viewState.update { + fun showAllAllowNotificationDialog() { + _groupOptionUiState.update { it.copy( - allAllowNotificationDialogVisible = visible, + allAllowNotificationDialogVisible = true, ) } } - private fun allParseFullContent(isFullContent: Boolean, callback: () -> Unit = {}) { - _viewState.value.group?.let { + fun hideAllAllowNotificationDialog() { + _groupOptionUiState.update { + it.copy( + allAllowNotificationDialogVisible = false, + ) + } + } + + fun allParseFullContent(isFullContent: Boolean, callback: () -> Unit = {}) { + _groupOptionUiState.value.group?.let { viewModelScope.launch(dispatcherIO) { rssRepository.get().groupParseFullContent(it, isFullContent) withContext(dispatcherMain) { @@ -136,16 +99,24 @@ class GroupOptionViewModel @Inject constructor( } } - private fun changeAllParseFullContentDialogVisible(visible: Boolean) { - _viewState.update { + fun showAllParseFullContentDialog() { + _groupOptionUiState.update { it.copy( - allParseFullContentDialogVisible = visible, + allParseFullContentDialogVisible = true, ) } } - private fun delete(callback: () -> Unit = {}) { - _viewState.value.group?.let { + fun hideAllParseFullContentDialog() { + _groupOptionUiState.update { + it.copy( + allParseFullContentDialogVisible = false, + ) + } + } + + fun delete(callback: () -> Unit = {}) { + _groupOptionUiState.value.group?.let { viewModelScope.launch(dispatcherIO) { rssRepository.get().deleteGroup(it) withContext(dispatcherMain) { @@ -155,32 +126,40 @@ class GroupOptionViewModel @Inject constructor( } } - private fun changeDeleteDialogVisible(visible: Boolean) { - _viewState.update { + fun showDeleteDialog() { + _groupOptionUiState.update { it.copy( - deleteDialogVisible = visible, + deleteDialogVisible = true, ) } } - private fun showClearDialog() { - _viewState.update { + fun hideDeleteDialog() { + _groupOptionUiState.update { + it.copy( + deleteDialogVisible = false, + ) + } + } + + fun showClearDialog() { + _groupOptionUiState.update { it.copy( clearDialogVisible = true, ) } } - private fun hideClearDialog() { - _viewState.update { + fun hideClearDialog() { + _groupOptionUiState.update { it.copy( clearDialogVisible = false, ) } } - private fun clear(callback: () -> Unit = {}) { - _viewState.value.group?.let { + fun clear(callback: () -> Unit = {}) { + _groupOptionUiState.value.group?.let { viewModelScope.launch(dispatcherIO) { rssRepository.get().deleteArticles(group = it) withContext(dispatcherMain) { @@ -190,9 +169,9 @@ class GroupOptionViewModel @Inject constructor( } } - private fun allMoveToGroup(callback: () -> Unit) { - _viewState.value.group?.let { group -> - _viewState.value.targetGroup?.let { targetGroup -> + fun allMoveToGroup(callback: () -> Unit) { + _groupOptionUiState.value.group?.let { group -> + _groupOptionUiState.value.targetGroup?.let { targetGroup -> viewModelScope.launch(dispatcherIO) { rssRepository.get().groupMoveToTargetGroup(group, targetGroup) withContext(dispatcherMain) { @@ -203,24 +182,33 @@ class GroupOptionViewModel @Inject constructor( } } - private fun changeAllMoveToGroupDialogVisible(targetGroup: Group? = null, visible: Boolean) { - _viewState.update { + fun showAllMoveToGroupDialog(targetGroup: Group) { + _groupOptionUiState.update { it.copy( - targetGroup = if (visible) targetGroup else null, - allMoveToGroupDialogVisible = visible, + targetGroup = targetGroup, + allMoveToGroupDialogVisible = true, ) } } - private fun rename() { - _viewState.value.group?.let { + fun hideAllMoveToGroupDialog() { + _groupOptionUiState.update { + it.copy( + targetGroup = null, + allMoveToGroupDialogVisible = false, + ) + } + } + + fun rename() { + _groupOptionUiState.value.group?.let { viewModelScope.launch { rssRepository.get().updateGroup( it.copy( - name = _viewState.value.newName + name = _groupOptionUiState.value.newName ) ) - _viewState.update { + _groupOptionUiState.update { it.copy( renameDialogVisible = false, ) @@ -229,17 +217,26 @@ class GroupOptionViewModel @Inject constructor( } } - private fun changeRenameDialogVisible(visible: Boolean) { - _viewState.update { + fun showRenameDialog() { + _groupOptionUiState.update { it.copy( - renameDialogVisible = visible, - newName = if (visible) _viewState.value.group?.name ?: "" else "", + renameDialogVisible = true, + newName = _groupOptionUiState.value.group?.name ?: "", ) } } - private fun inputNewName(content: String) { - _viewState.update { + fun hideRenameDialog() { + _groupOptionUiState.update { + it.copy( + renameDialogVisible = false, + newName = "", + ) + } + } + + fun inputNewName(content: String) { + _groupOptionUiState.update { it.copy( newName = content ) @@ -248,7 +245,7 @@ class GroupOptionViewModel @Inject constructor( } @OptIn(ExperimentalMaterialApi::class) -data class GroupOptionViewState( +data class GroupOptionUiState( var drawerState: ModalBottomSheetState = ModalBottomSheetState(ModalBottomSheetValue.Hidden), val group: Group? = null, val targetGroup: Group? = null, @@ -261,61 +258,3 @@ data class GroupOptionViewState( val newName: String = "", val renameDialogVisible: 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 Clear( - val callback: () -> Unit = {} - ) : GroupOptionViewAction() - - object ShowClearDialog : GroupOptionViewAction() - object HideClearDialog : 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() - - data class AllMoveToGroup( - val callback: () -> Unit = {} - ) : GroupOptionViewAction() - - data class ShowAllMoveToGroupDialog( - val targetGroup: Group - ) : GroupOptionViewAction() - - object HideAllMoveToGroupDialog : GroupOptionViewAction() - - object ShowRenameDialog : GroupOptionViewAction() - object HideRenameDialog : GroupOptionViewAction() - object Rename : GroupOptionViewAction() - data class InputNewName( - val content: String - ) : GroupOptionViewAction() -} diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feeds/subscribe/SubscribeDialog.kt b/app/src/main/java/me/ash/reader/ui/page/home/feeds/subscribe/SubscribeDialog.kt index 02a6e6e..675d160 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/feeds/subscribe/SubscribeDialog.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/feeds/subscribe/SubscribeDialog.kt @@ -41,32 +41,32 @@ fun SubscribeDialog( ) { val context = LocalContext.current val focusManager = LocalFocusManager.current - val viewState = subscribeViewModel.viewState.collectAsStateValue() - val groupsState = viewState.groups.collectAsState(initial = emptyList()) + val subscribeUiState = subscribeViewModel.subscribeUiState.collectAsStateValue() + val groupsState = subscribeUiState.groups.collectAsState(initial = emptyList()) val launcher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { it?.let { uri -> context.contentResolver.openInputStream(uri)?.let { inputStream -> - subscribeViewModel.dispatch(SubscribeViewAction.ImportFromInputStream(inputStream)) + subscribeViewModel.importFromInputStream(inputStream) } } } - LaunchedEffect(viewState.visible) { - if (viewState.visible) { - subscribeViewModel.dispatch(SubscribeViewAction.Init) + LaunchedEffect(subscribeUiState.visible) { + if (subscribeUiState.visible) { + subscribeViewModel.init() } else { - subscribeViewModel.dispatch(SubscribeViewAction.Reset) - subscribeViewModel.dispatch(SubscribeViewAction.SwitchPage(true)) + subscribeViewModel.reset() + subscribeViewModel.switchPage(true) } } Dialog( modifier = Modifier.padding(horizontal = 44.dp), - visible = viewState.visible, + visible = subscribeUiState.visible, properties = DialogProperties(usePlatformDefaultWidth = false), onDismissRequest = { focusManager.clearFocus() - subscribeViewModel.dispatch(SubscribeViewAction.Hide) + subscribeViewModel.hideDrawer() }, icon = { Icon( @@ -76,10 +76,10 @@ fun SubscribeDialog( }, title = { Text( - text = if (viewState.isSearchPage) { - viewState.title + text = if (subscribeUiState.isSearchPage) { + subscribeUiState.title } else { - viewState.feed?.name ?: stringResource(R.string.unknown) + subscribeUiState.feed?.name ?: stringResource(R.string.unknown) }, maxLines = 1, overflow = TextOverflow.Ellipsis, @@ -87,7 +87,7 @@ fun SubscribeDialog( }, text = { AnimatedContent( - targetState = viewState.isSearchPage, + targetState = subscribeUiState.isSearchPage, transitionSpec = { slideInHorizontally { width -> width } + fadeIn() with slideOutHorizontally { width -> -width } + fadeOut() @@ -95,55 +95,55 @@ fun SubscribeDialog( ) { targetExpanded -> if (targetExpanded) { ClipboardTextField( - readOnly = viewState.lockLinkInput, - value = viewState.linkContent, + readOnly = subscribeUiState.lockLinkInput, + value = subscribeUiState.linkContent, onValueChange = { - subscribeViewModel.dispatch(SubscribeViewAction.InputLink(it)) + subscribeViewModel.inputLink(it) }, placeholder = stringResource(R.string.feed_or_site_url), - errorText = viewState.errorMessage, + errorText = subscribeUiState.errorMessage, imeAction = ImeAction.Search, focusManager = focusManager, onConfirm = { - subscribeViewModel.dispatch(SubscribeViewAction.Search) + subscribeViewModel.search() }, ) } else { ResultView( - link = viewState.linkContent, + link = subscribeUiState.linkContent, groups = groupsState.value, - selectedAllowNotificationPreset = viewState.allowNotificationPreset, - selectedParseFullContentPreset = viewState.parseFullContentPreset, - selectedGroupId = viewState.selectedGroupId, + selectedAllowNotificationPreset = subscribeUiState.allowNotificationPreset, + selectedParseFullContentPreset = subscribeUiState.parseFullContentPreset, + selectedGroupId = subscribeUiState.selectedGroupId, allowNotificationPresetOnClick = { - subscribeViewModel.dispatch(SubscribeViewAction.ChangeAllowNotificationPreset) + subscribeViewModel.changeAllowNotificationPreset() }, parseFullContentPresetOnClick = { - subscribeViewModel.dispatch(SubscribeViewAction.ChangeParseFullContentPreset) + subscribeViewModel.changeParseFullContentPreset() }, onGroupClick = { - subscribeViewModel.dispatch(SubscribeViewAction.SelectedGroup(it)) + subscribeViewModel.selectedGroup(it) }, onAddNewGroup = { - subscribeViewModel.dispatch(SubscribeViewAction.ShowNewGroupDialog) + subscribeViewModel.showNewGroupDialog() }, ) } } }, confirmButton = { - if (viewState.isSearchPage) { + if (subscribeUiState.isSearchPage) { TextButton( - enabled = viewState.linkContent.isNotBlank() - && viewState.title != stringResource(R.string.searching), + enabled = subscribeUiState.linkContent.isNotBlank() + && subscribeUiState.title != stringResource(R.string.searching), onClick = { focusManager.clearFocus() - subscribeViewModel.dispatch(SubscribeViewAction.Search) + subscribeViewModel.search() } ) { Text( text = stringResource(R.string.search), - color = if (viewState.linkContent.isNotBlank()) { + color = if (subscribeUiState.linkContent.isNotBlank()) { Color.Unspecified } else { MaterialTheme.colorScheme.outline.copy(alpha = 0.7f) @@ -154,7 +154,7 @@ fun SubscribeDialog( TextButton( onClick = { focusManager.clearFocus() - subscribeViewModel.dispatch(SubscribeViewAction.Subscribe) + subscribeViewModel.subscribe() } ) { Text(stringResource(R.string.subscribe)) @@ -162,12 +162,12 @@ fun SubscribeDialog( } }, dismissButton = { - if (viewState.isSearchPage) { + if (subscribeUiState.isSearchPage) { TextButton( onClick = { focusManager.clearFocus() launcher.launch("*/*") - subscribeViewModel.dispatch(SubscribeViewAction.Hide) + subscribeViewModel.hideDrawer() } ) { Text(text = stringResource(R.string.import_from_opml)) @@ -176,7 +176,7 @@ fun SubscribeDialog( TextButton( onClick = { focusManager.clearFocus() - subscribeViewModel.dispatch(SubscribeViewAction.Hide) + subscribeViewModel.hideDrawer() } ) { Text(text = stringResource(R.string.cancel)) @@ -186,19 +186,19 @@ fun SubscribeDialog( ) TextFieldDialog( - visible = viewState.newGroupDialogVisible, + visible = subscribeUiState.newGroupDialogVisible, title = stringResource(R.string.create_new_group), icon = Icons.Outlined.CreateNewFolder, - value = viewState.newGroupContent, + value = subscribeUiState.newGroupContent, placeholder = stringResource(R.string.name), onValueChange = { - subscribeViewModel.dispatch(SubscribeViewAction.InputNewGroup(it)) + subscribeViewModel.inputNewGroup(it) }, onDismissRequest = { - subscribeViewModel.dispatch(SubscribeViewAction.HideNewGroupDialog) + subscribeViewModel.hideNewGroupDialog() }, onConfirm = { - subscribeViewModel.dispatch(SubscribeViewAction.AddNewGroup) + subscribeViewModel.addNewGroup() } ) } \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feeds/subscribe/SubscribeViewModel.kt b/app/src/main/java/me/ash/reader/ui/page/home/feeds/subscribe/SubscribeViewModel.kt index 243ed9c..df4e143 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/feeds/subscribe/SubscribeViewModel.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/feeds/subscribe/SubscribeViewModel.kt @@ -3,7 +3,6 @@ package me.ash.reader.ui.page.home.feeds.subscribe import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.google.accompanist.pager.ExperimentalPagerApi import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Job @@ -32,35 +31,12 @@ class SubscribeViewModel @Inject constructor( @DispatcherIO private val dispatcherIO: CoroutineDispatcher, ) : ViewModel() { - private val _viewState = MutableStateFlow(SubscribeViewState()) - val viewState: StateFlow = _viewState.asStateFlow() + private val _subscribeUiState = MutableStateFlow(SubscribeUiState()) + val subscribeUiState: StateFlow = _subscribeUiState.asStateFlow() private var searchJob: Job? = null - fun dispatch(action: SubscribeViewAction) { - when (action) { - is SubscribeViewAction.Init -> init() - is SubscribeViewAction.Reset -> reset() - is SubscribeViewAction.Show -> changeVisible(true) - is SubscribeViewAction.Hide -> changeVisible(false) - is SubscribeViewAction.ShowNewGroupDialog -> changeNewGroupDialogVisible(true) - is SubscribeViewAction.HideNewGroupDialog -> changeNewGroupDialogVisible(false) - is SubscribeViewAction.SwitchPage -> switchPage(action.isSearchPage) - is SubscribeViewAction.ImportFromInputStream -> importFromInputStream(action.inputStream) - is SubscribeViewAction.InputLink -> inputLink(action.content) - is SubscribeViewAction.Search -> search() - is SubscribeViewAction.ChangeAllowNotificationPreset -> - changeAllowNotificationPreset() - is SubscribeViewAction.ChangeParseFullContentPreset -> - changeParseFullContentPreset() - is SubscribeViewAction.SelectedGroup -> selectedGroup(action.groupId) - is SubscribeViewAction.InputNewGroup -> inputNewGroup(action.content) - is SubscribeViewAction.AddNewGroup -> addNewGroup() - is SubscribeViewAction.Subscribe -> subscribe() - } - } - - private fun init() { - _viewState.update { + fun init() { + _subscribeUiState.update { it.copy( title = stringsRepository.getString(R.string.subscribe), groups = rssRepository.get().pullGroups(), @@ -68,17 +44,17 @@ class SubscribeViewModel @Inject constructor( } } - private fun reset() { + fun reset() { searchJob?.cancel() searchJob = null - _viewState.update { - SubscribeViewState().copy( + _subscribeUiState.update { + SubscribeUiState().copy( title = stringsRepository.getString(R.string.subscribe), ) } } - private fun importFromInputStream(inputStream: InputStream) { + fun importFromInputStream(inputStream: InputStream) { viewModelScope.launch(dispatcherIO) { try { opmlRepository.saveToDatabase(inputStream) @@ -89,38 +65,38 @@ class SubscribeViewModel @Inject constructor( } } - private fun subscribe() { - val feed = _viewState.value.feed ?: return - val articles = _viewState.value.articles + fun subscribe() { + val feed = _subscribeUiState.value.feed ?: return + val articles = _subscribeUiState.value.articles viewModelScope.launch(dispatcherIO) { val groupId = async { - _viewState.value.selectedGroupId + _subscribeUiState.value.selectedGroupId } rssRepository.get().subscribe( feed.copy( groupId = groupId.await(), - isNotification = _viewState.value.allowNotificationPreset, - isFullContent = _viewState.value.parseFullContentPreset, + isNotification = _subscribeUiState.value.allowNotificationPreset, + isFullContent = _subscribeUiState.value.parseFullContentPreset, ), articles ) - changeVisible(false) + hideDrawer() } } - private fun selectedGroup(groupId: String) { - _viewState.update { + fun selectedGroup(groupId: String) { + _subscribeUiState.update { it.copy( selectedGroupId = groupId, ) } } - private fun addNewGroup() { - if (_viewState.value.newGroupContent.isNotBlank()) { + fun addNewGroup() { + if (_subscribeUiState.value.newGroupContent.isNotBlank()) { viewModelScope.launch { - selectedGroup(rssRepository.get().addGroup(_viewState.value.newGroupContent)) - changeNewGroupDialogVisible(false) - _viewState.update { + selectedGroup(rssRepository.get().addGroup(_subscribeUiState.value.newGroupContent)) + hideNewGroupDialog() + _subscribeUiState.update { it.copy( newGroupContent = "", ) @@ -129,48 +105,48 @@ class SubscribeViewModel @Inject constructor( } } - private fun changeParseFullContentPreset() { - _viewState.update { + fun changeParseFullContentPreset() { + _subscribeUiState.update { it.copy( - parseFullContentPreset = !_viewState.value.parseFullContentPreset + parseFullContentPreset = !_subscribeUiState.value.parseFullContentPreset ) } } - private fun changeAllowNotificationPreset() { - _viewState.update { + fun changeAllowNotificationPreset() { + _subscribeUiState.update { it.copy( - allowNotificationPreset = !_viewState.value.allowNotificationPreset + allowNotificationPreset = !_subscribeUiState.value.allowNotificationPreset ) } } - private fun search() { + fun search() { searchJob?.cancel() viewModelScope.launch(dispatcherIO) { try { - _viewState.update { + _subscribeUiState.update { it.copy( errorMessage = "", ) } - _viewState.value.linkContent.formatUrl().let { str -> - if (str != _viewState.value.linkContent) { - _viewState.update { + _subscribeUiState.value.linkContent.formatUrl().let { str -> + if (str != _subscribeUiState.value.linkContent) { + _subscribeUiState.update { it.copy( linkContent = str ) } } } - _viewState.update { + _subscribeUiState.update { it.copy( title = stringsRepository.getString(R.string.searching), lockLinkInput = true, ) } - if (rssRepository.get().isFeedExist(_viewState.value.linkContent)) { - _viewState.update { + if (rssRepository.get().isFeedExist(_subscribeUiState.value.linkContent)) { + _subscribeUiState.update { it.copy( title = stringsRepository.getString(R.string.subscribe), errorMessage = stringsRepository.getString(R.string.already_subscribed), @@ -179,8 +155,8 @@ class SubscribeViewModel @Inject constructor( } return@launch } - val feedWithArticle = rssHelper.searchFeed(_viewState.value.linkContent) - _viewState.update { + val feedWithArticle = rssHelper.searchFeed(_subscribeUiState.value.linkContent) + _subscribeUiState.update { it.copy( feed = feedWithArticle.feed, articles = feedWithArticle.articles, @@ -189,7 +165,7 @@ class SubscribeViewModel @Inject constructor( switchPage(false) } catch (e: Exception) { e.printStackTrace() - _viewState.update { + _subscribeUiState.update { it.copy( title = stringsRepository.getString(R.string.subscribe), errorMessage = e.message ?: stringsRepository.getString(R.string.unknown), @@ -202,8 +178,8 @@ class SubscribeViewModel @Inject constructor( } } - private fun inputLink(content: String) { - _viewState.update { + fun inputLink(content: String) { + _subscribeUiState.update { it.copy( linkContent = content, errorMessage = "", @@ -211,32 +187,48 @@ class SubscribeViewModel @Inject constructor( } } - private fun inputNewGroup(content: String) { - _viewState.update { + fun inputNewGroup(content: String) { + _subscribeUiState.update { it.copy( newGroupContent = content ) } } - private fun changeVisible(visible: Boolean) { - _viewState.update { + fun showDrawer() { + _subscribeUiState.update { it.copy( - visible = visible + visible = true ) } } - private fun changeNewGroupDialogVisible(visible: Boolean) { - _viewState.update { + fun hideDrawer() { + _subscribeUiState.update { it.copy( - newGroupDialogVisible = visible, + visible = false ) } } - private fun switchPage(isSearchPage: Boolean) { - _viewState.update { + fun showNewGroupDialog() { + _subscribeUiState.update { + it.copy( + newGroupDialogVisible = true, + ) + } + } + + fun hideNewGroupDialog() { + _subscribeUiState.update { + it.copy( + newGroupDialogVisible = false, + ) + } + } + + fun switchPage(isSearchPage: Boolean) { + _subscribeUiState.update { it.copy( isSearchPage = isSearchPage ) @@ -244,7 +236,7 @@ class SubscribeViewModel @Inject constructor( } } -data class SubscribeViewState( +data class SubscribeUiState( val visible: Boolean = false, val title: String = "", val errorMessage: String = "", @@ -260,42 +252,3 @@ data class SubscribeViewState( val groups: Flow> = emptyFlow(), val isSearchPage: Boolean = true, ) - -sealed class SubscribeViewAction { - object Init : SubscribeViewAction() - object Reset : SubscribeViewAction() - - object Show : SubscribeViewAction() - object Hide : SubscribeViewAction() - - object ShowNewGroupDialog : SubscribeViewAction() - object HideNewGroupDialog : SubscribeViewAction() - object AddNewGroup : SubscribeViewAction() - - data class SwitchPage( - val isSearchPage: Boolean - ) : SubscribeViewAction() - - data class ImportFromInputStream( - val inputStream: InputStream - ) : SubscribeViewAction() - - data class InputLink( - val content: String - ) : SubscribeViewAction() - - object Search : SubscribeViewAction() - - object ChangeAllowNotificationPreset : SubscribeViewAction() - object ChangeParseFullContentPreset : SubscribeViewAction() - - data class SelectedGroup( - val groupId: String - ) : SubscribeViewAction() - - data class InputNewGroup( - val content: String - ) : SubscribeViewAction() - - object Subscribe : SubscribeViewAction() -} diff --git a/app/src/main/java/me/ash/reader/ui/page/home/flow/FlowPage.kt b/app/src/main/java/me/ash/reader/ui/page/home/flow/FlowPage.kt index fc8c6b1..0aebc58 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/flow/FlowPage.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/flow/FlowPage.kt @@ -34,7 +34,6 @@ import me.ash.reader.ui.component.base.SwipeRefresh import me.ash.reader.ui.ext.collectAsStateValue import me.ash.reader.ui.page.common.RouteName import me.ash.reader.ui.page.home.FilterState -import me.ash.reader.ui.page.home.HomeViewAction import me.ash.reader.ui.page.home.HomeViewModel @OptIn( @@ -47,8 +46,6 @@ fun FlowPage( flowViewModel: FlowViewModel = hiltViewModel(), homeViewModel: HomeViewModel, ) { - val homeViewView = homeViewModel.viewState.collectAsStateValue() - val pagingItems = homeViewView.pagingData.collectAsLazyPagingItems() val keyboardController = LocalSoftwareKeyboardController.current val topBarTonalElevation = LocalFlowTopBarTonalElevation.current val articleListTonalElevation = LocalFlowArticleListTonalElevation.current @@ -59,16 +56,18 @@ fun FlowPage( val filterBarPadding = LocalFlowFilterBarPadding.current val filterBarTonalElevation = LocalFlowFilterBarTonalElevation.current + val homeUiState = homeViewModel.homeUiState.collectAsStateValue() + val flowUiState = flowViewModel.flowUiState.collectAsStateValue() + val filterUiState = homeViewModel.filterUiState.collectAsStateValue() + val pagingItems = homeUiState.pagingData.collectAsLazyPagingItems() + val listState = + if (pagingItems.itemCount > 0) flowUiState.listState else rememberLazyListState() + val scope = rememberCoroutineScope() val focusRequester = remember { FocusRequester() } var markAsRead by remember { mutableStateOf(false) } var onSearch by remember { mutableStateOf(false) } - val viewState = flowViewModel.viewState.collectAsStateValue() - val filterState = homeViewModel.filterState.collectAsStateValue() - val homeViewState = homeViewModel.viewState.collectAsStateValue() - val listState = if (pagingItems.itemCount > 0) viewState.listState else rememberLazyListState() - val owner = LocalLifecycleOwner.current var isSyncing by remember { mutableStateOf(false) } homeViewModel.syncWorkLiveData.observe(owner) { @@ -82,15 +81,15 @@ fun FlowPage( focusRequester.requestFocus() } else { keyboardController?.hide() - if (homeViewState.searchContent.isNotBlank()) { - homeViewModel.dispatch(HomeViewAction.InputSearchContent("")) + if (homeUiState.searchContent.isNotBlank()) { + homeViewModel.inputSearchContent("") } } } } - LaunchedEffect(viewState.listState) { - snapshotFlow { viewState.listState.firstVisibleItemIndex }.collect { + LaunchedEffect(flowUiState.listState) { + snapshotFlow { flowUiState.listState.firstVisibleItemIndex }.collect { if (it > 0) { keyboardController?.hide() } @@ -122,7 +121,7 @@ fun FlowPage( }, actions = { AnimatedVisibility( - visible = !filterState.filter.isStarred(), + visible = !filterUiState.filter.isStarred(), enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically(), ) { @@ -136,7 +135,7 @@ fun FlowPage( }, ) { scope.launch { - viewState.listState.scrollToItem(0) + flowUiState.listState.scrollToItem(0) markAsRead = !markAsRead onSearch = false } @@ -152,7 +151,7 @@ fun FlowPage( }, ) { scope.launch { - viewState.listState.scrollToItem(0) + flowUiState.listState.scrollToItem(0) onSearch = !onSearch } } @@ -161,7 +160,7 @@ fun FlowPage( SwipeRefresh( onRefresh = { if (!isSyncing) { - flowViewModel.dispatch(FlowViewAction.Sync) + flowViewModel.sync() } } ) { @@ -170,7 +169,7 @@ fun FlowPage( state = listState, ) { item { - DisplayTextHeader(filterState, isSyncing, articleListFeedIcon.value) + DisplayTextHeader(filterUiState, isSyncing, articleListFeedIcon.value) AnimatedVisibility( visible = markAsRead, enter = fadeIn() + expandVertically(), @@ -186,13 +185,11 @@ fun FlowPage( }, ) { markAsRead = false - flowViewModel.dispatch( - FlowViewAction.MarkAsRead( - groupId = filterState.group?.id, - feedId = filterState.feed?.id, - articleId = null, - markAsReadBefore = it, - ) + flowViewModel.markAsRead( + groupId = filterUiState.group?.id, + feedId = filterUiState.feed?.id, + articleId = null, + markAsReadBefore = it, ) } AnimatedVisibility( @@ -201,30 +198,30 @@ fun FlowPage( exit = fadeOut() + shrinkVertically(), ) { SearchBar( - value = homeViewState.searchContent, + value = homeUiState.searchContent, placeholder = when { - filterState.group != null -> stringResource( + filterUiState.group != null -> stringResource( R.string.search_for_in, - filterState.filter.getName(), - filterState.group.name + filterUiState.filter.getName(), + filterUiState.group.name ) - filterState.feed != null -> stringResource( + filterUiState.feed != null -> stringResource( R.string.search_for_in, - filterState.filter.getName(), - filterState.feed.name + filterUiState.filter.getName(), + filterUiState.feed.name ) else -> stringResource( R.string.search_for, - filterState.filter.getName() + filterUiState.filter.getName() ) }, focusRequester = focusRequester, onValueChange = { - homeViewModel.dispatch(HomeViewAction.InputSearchContent(it)) + homeViewModel.inputSearchContent(it) }, onClose = { onSearch = false - homeViewModel.dispatch(HomeViewAction.InputSearchContent("")) + homeViewModel.inputSearchContent("") } ) Spacer(modifier = Modifier.height((56 + 24 + 10).dp)) @@ -253,15 +250,15 @@ fun FlowPage( }, bottomBar = { FilterBar( - filter = filterState.filter, + filter = filterUiState.filter, filterBarStyle = filterBarStyle.value, filterBarFilled = filterBarFilled.value, filterBarPadding = filterBarPadding.dp, filterBarTonalElevation = filterBarTonalElevation.value.dp, ) { - flowViewModel.dispatch(FlowViewAction.ScrollToItem(0)) - homeViewModel.dispatch(HomeViewAction.ChangeFilter(filterState.copy(filter = it))) - homeViewModel.dispatch(HomeViewAction.FetchArticles) + flowViewModel.scrollToItem(0) + homeViewModel.changeFilter(filterUiState.copy(filter = it)) + homeViewModel.fetchArticles() } } ) diff --git a/app/src/main/java/me/ash/reader/ui/page/home/flow/FlowViewModel.kt b/app/src/main/java/me/ash/reader/ui/page/home/flow/FlowViewModel.kt index 257609b..4144f9d 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/flow/FlowViewModel.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/flow/FlowViewModel.kt @@ -7,7 +7,6 @@ import dagger.hilt.android.lifecycle.HiltViewModel 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 me.ash.reader.data.entity.ArticleWithFeed import me.ash.reader.data.repository.RssRepository @@ -19,40 +18,20 @@ import javax.inject.Inject class FlowViewModel @Inject constructor( private val rssRepository: RssRepository, ) : ViewModel() { - private val _viewState = MutableStateFlow(ArticleViewState()) - val viewState: StateFlow = _viewState.asStateFlow() + private val _flowUiState = MutableStateFlow(FlowUiState()) + val flowUiState: StateFlow = _flowUiState.asStateFlow() - fun dispatch(action: FlowViewAction) { - when (action) { - is FlowViewAction.Sync -> sync() - is FlowViewAction.ChangeIsBack -> changeIsBack(action.isBack) - is FlowViewAction.ScrollToItem -> scrollToItem(action.index) - is FlowViewAction.MarkAsRead -> markAsRead( - action.groupId, - action.feedId, - action.articleId, - action.markAsReadBefore, - ) - } - } - - private fun sync() { + fun sync() { rssRepository.get().doSync() } - private fun scrollToItem(index: Int) { + fun scrollToItem(index: Int) { viewModelScope.launch { - _viewState.value.listState.scrollToItem(index) + _flowUiState.value.listState.scrollToItem(index) } } - private fun changeIsBack(isBack: Boolean) { - _viewState.update { - it.copy(isBack = isBack) - } - } - - private fun markAsRead( + fun markAsRead( groupId: String?, feedId: String?, articleId: String?, @@ -84,32 +63,13 @@ class FlowViewModel @Inject constructor( } } -data class ArticleViewState( +data class FlowUiState( val filterImportant: Int = 0, val listState: LazyListState = LazyListState(), val isBack: Boolean = false, val syncWorkInfo: String = "", ) -sealed class FlowViewAction { - object Sync : FlowViewAction() - - data class ChangeIsBack( - val isBack: Boolean - ) : FlowViewAction() - - data class ScrollToItem( - val index: Int - ) : FlowViewAction() - - data class MarkAsRead( - val groupId: String?, - val feedId: String?, - val articleId: String?, - val markAsReadBefore: MarkAsReadBefore - ) : FlowViewAction() -} - enum class MarkAsReadBefore { SevenDays, ThreeDays, diff --git a/app/src/main/java/me/ash/reader/ui/page/home/read/Header.kt b/app/src/main/java/me/ash/reader/ui/page/home/reading/Header.kt similarity index 97% rename from app/src/main/java/me/ash/reader/ui/page/home/read/Header.kt rename to app/src/main/java/me/ash/reader/ui/page/home/reading/Header.kt index 44cccb2..40f2552 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/read/Header.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/reading/Header.kt @@ -1,4 +1,4 @@ -package me.ash.reader.ui.page.home.read +package me.ash.reader.ui.page.home.reading import android.content.Intent import android.net.Uri diff --git a/app/src/main/java/me/ash/reader/ui/page/home/read/ReadBar.kt b/app/src/main/java/me/ash/reader/ui/page/home/reading/ReadBar.kt similarity index 99% rename from app/src/main/java/me/ash/reader/ui/page/home/read/ReadBar.kt rename to app/src/main/java/me/ash/reader/ui/page/home/reading/ReadBar.kt index 14c7ac4..e3bf3a2 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/read/ReadBar.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/reading/ReadBar.kt @@ -1,4 +1,4 @@ -package me.ash.reader.ui.page.home.read +package me.ash.reader.ui.page.home.reading import android.view.HapticFeedbackConstants import androidx.compose.foundation.background diff --git a/app/src/main/java/me/ash/reader/ui/page/home/read/ReadPage.kt b/app/src/main/java/me/ash/reader/ui/page/home/reading/ReadingPage.kt similarity index 84% rename from app/src/main/java/me/ash/reader/ui/page/home/read/ReadPage.kt rename to app/src/main/java/me/ash/reader/ui/page/home/reading/ReadingPage.kt index 3f4958c..b856446 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/read/ReadPage.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/reading/ReadingPage.kt @@ -1,4 +1,4 @@ -package me.ash.reader.ui.page.home.read +package me.ash.reader.ui.page.home.reading import android.content.Intent import android.util.Log @@ -32,26 +32,26 @@ import me.ash.reader.ui.ext.collectAsStateValue import me.ash.reader.ui.ext.drawVerticalScrollbar @Composable -fun ReadPage( +fun ReadingPage( navController: NavHostController, - readViewModel: ReadViewModel = hiltViewModel(), + readingViewModel: ReadingViewModel = hiltViewModel(), ) { - val viewState = readViewModel.viewState.collectAsStateValue() - val isScrollDown = viewState.listState.isScrollDown() + val readingUiState = readingViewModel.readingUiState.collectAsStateValue() + val isScrollDown = readingUiState.listState.isScrollDown() LaunchedEffect(Unit) { navController.currentBackStackEntryFlow.collect { it.arguments?.getString("articleId")?.let { - readViewModel.dispatch(ReadViewAction.InitData(it)) + readingViewModel.initData(it) } } } - LaunchedEffect(viewState.articleWithFeed?.article?.id) { - Log.i("RLog", "ReadPage: ${viewState.articleWithFeed}") - viewState.articleWithFeed?.let { + LaunchedEffect(readingUiState.articleWithFeed?.article?.id) { + Log.i("RLog", "ReadPage: ${readingUiState.articleWithFeed}") + readingUiState.articleWithFeed?.let { if (it.article.isUnread) { - readViewModel.dispatch(ReadViewAction.MarkUnread(false)) + readingViewModel.markUnread(false) } } } @@ -66,19 +66,19 @@ fun ReadPage( contentAlignment = Alignment.TopCenter ) { TopBar( - isShow = viewState.articleWithFeed == null || !isScrollDown, - title = viewState.articleWithFeed?.article?.title, - link = viewState.articleWithFeed?.article?.link, + isShow = readingUiState.articleWithFeed == null || !isScrollDown, + title = readingUiState.articleWithFeed?.article?.title, + link = readingUiState.articleWithFeed?.article?.link, onClose = { navController.popBackStack() }, ) } Content( - content = viewState.content ?: "", - articleWithFeed = viewState.articleWithFeed, - isLoading = viewState.isLoading, - listState = viewState.listState, + content = readingUiState.content ?: "", + articleWithFeed = readingUiState.articleWithFeed, + isLoading = readingUiState.isLoading, + listState = readingUiState.listState, ) Box( modifier = Modifier @@ -87,17 +87,17 @@ fun ReadPage( contentAlignment = Alignment.BottomCenter ) { BottomBar( - isShow = viewState.articleWithFeed != null && !isScrollDown, - articleWithFeed = viewState.articleWithFeed, + isShow = readingUiState.articleWithFeed != null && !isScrollDown, + articleWithFeed = readingUiState.articleWithFeed, unreadOnClick = { - readViewModel.dispatch(ReadViewAction.MarkUnread(it)) + readingViewModel.markUnread(it) }, starredOnClick = { - readViewModel.dispatch(ReadViewAction.MarkStarred(it)) + readingViewModel.markStarred(it) }, fullContentOnClick = { afterIsFullContent -> - if (afterIsFullContent) readViewModel.dispatch(ReadViewAction.RenderFullContent) - else readViewModel.dispatch(ReadViewAction.RenderDescriptionContent) + if (afterIsFullContent) readingViewModel.renderFullContent() + else readingViewModel.renderDescriptionContent() }, ) } diff --git a/app/src/main/java/me/ash/reader/ui/page/home/read/ReadViewModel.kt b/app/src/main/java/me/ash/reader/ui/page/home/reading/ReadingViewModel.kt similarity index 51% rename from app/src/main/java/me/ash/reader/ui/page/home/read/ReadViewModel.kt rename to app/src/main/java/me/ash/reader/ui/page/home/reading/ReadingViewModel.kt index 1aeadf2..4a345d3 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/read/ReadViewModel.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/reading/ReadingViewModel.kt @@ -1,4 +1,4 @@ -package me.ash.reader.ui.page.home.read +package me.ash.reader.ui.page.home.reading import android.util.Log import androidx.compose.foundation.lazy.LazyListState @@ -17,41 +17,29 @@ import me.ash.reader.data.repository.RssRepository import javax.inject.Inject @HiltViewModel -class ReadViewModel @Inject constructor( +class ReadingViewModel @Inject constructor( val rssRepository: RssRepository, private val rssHelper: RssHelper, ) : ViewModel() { - private val _viewState = MutableStateFlow(ReadViewState()) - val viewState: StateFlow = _viewState.asStateFlow() + private val _readingUiState = MutableStateFlow(ReadingUiState()) + val readingUiState: StateFlow = _readingUiState.asStateFlow() - fun dispatch(action: ReadViewAction) { - when (action) { - is ReadViewAction.InitData -> bindArticleWithFeed(action.articleId) - is ReadViewAction.RenderDescriptionContent -> renderDescriptionContent() - is ReadViewAction.RenderFullContent -> renderFullContent() - is ReadViewAction.MarkUnread -> markUnread(action.isUnread) - is ReadViewAction.MarkStarred -> markStarred(action.isStarred) - is ReadViewAction.ClearArticle -> clearArticle() - is ReadViewAction.ChangeLoading -> changeLoading(action.isLoading) - } - } - - private fun bindArticleWithFeed(articleId: String) { - changeLoading(true) + fun initData(articleId: String) { + showLoading() viewModelScope.launch { - _viewState.update { + _readingUiState.update { it.copy(articleWithFeed = rssRepository.get().findArticleById(articleId)) } - _viewState.value.articleWithFeed?.let { + _readingUiState.value.articleWithFeed?.let { if (it.feed.isFullContent) internalRenderFullContent() else renderDescriptionContent() } - changeLoading(false) + hideLoading() } } - private fun renderDescriptionContent() { - _viewState.update { + fun renderDescriptionContent() { + _readingUiState.update { it.copy( content = it.articleWithFeed?.article?.fullContent ?: it.articleWithFeed?.article?.rawDescription ?: "", @@ -59,38 +47,38 @@ class ReadViewModel @Inject constructor( } } - private fun renderFullContent() { + fun renderFullContent() { viewModelScope.launch { internalRenderFullContent() } } - private suspend fun internalRenderFullContent() { - changeLoading(true) + suspend fun internalRenderFullContent() { + showLoading() try { - _viewState.update { + _readingUiState.update { it.copy( content = rssHelper.parseFullContent( - _viewState.value.articleWithFeed?.article?.link ?: "", - _viewState.value.articleWithFeed?.article?.title ?: "" + _readingUiState.value.articleWithFeed?.article?.link ?: "", + _readingUiState.value.articleWithFeed?.article?.title ?: "" ) ) } } catch (e: Exception) { Log.i("RLog", "renderFullContent: ${e.message}") - _viewState.update { + _readingUiState.update { it.copy( content = e.message ) } } - changeLoading(false) + hideLoading() } - private fun markUnread(isUnread: Boolean) { - val articleWithFeed = _viewState.value.articleWithFeed ?: return + fun markUnread(isUnread: Boolean) { + val articleWithFeed = _readingUiState.value.articleWithFeed ?: return viewModelScope.launch { - _viewState.update { + _readingUiState.update { it.copy( articleWithFeed = articleWithFeed.copy( article = articleWithFeed.article.copy( @@ -102,17 +90,17 @@ class ReadViewModel @Inject constructor( rssRepository.get().markAsRead( groupId = null, feedId = null, - articleId = _viewState.value.articleWithFeed!!.article.id, + articleId = _readingUiState.value.articleWithFeed!!.article.id, before = null, isUnread = isUnread, ) } } - private fun markStarred(isStarred: Boolean) { - val articleWithFeed = _viewState.value.articleWithFeed ?: return + fun markStarred(isStarred: Boolean) { + val articleWithFeed = _readingUiState.value.articleWithFeed ?: return viewModelScope.launch(Dispatchers.IO) { - _viewState.update { + _readingUiState.update { it.copy( articleWithFeed = articleWithFeed.copy( article = articleWithFeed.article.copy( @@ -129,47 +117,22 @@ class ReadViewModel @Inject constructor( } } - private fun clearArticle() { - _viewState.update { - it.copy(articleWithFeed = null) + private fun showLoading() { + _readingUiState.update { + it.copy(isLoading = true) } } - private fun changeLoading(isLoading: Boolean) { - _viewState.update { - it.copy(isLoading = isLoading) + private fun hideLoading() { + _readingUiState.update { + it.copy(isLoading = false) } } } -data class ReadViewState( +data class ReadingUiState( val articleWithFeed: ArticleWithFeed? = null, val content: String? = null, val isLoading: Boolean = true, -// val scrollState: ScrollState = ScrollState(0), val listState: LazyListState = LazyListState(), -) - -sealed class ReadViewAction { - data class InitData( - val articleId: String, - ) : ReadViewAction() - - object RenderDescriptionContent : ReadViewAction() - - object RenderFullContent : ReadViewAction() - - data class MarkUnread( - val isUnread: Boolean, - ) : ReadViewAction() - - data class MarkStarred( - val isStarred: Boolean, - ) : ReadViewAction() - - object ClearArticle : ReadViewAction() - - data class ChangeLoading( - val isLoading: Boolean - ) : ReadViewAction() -} \ No newline at end of file +) \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/ui/page/settings/SettingsPage.kt b/app/src/main/java/me/ash/reader/ui/page/settings/SettingsPage.kt index df492d9..a643c41 100644 --- a/app/src/main/java/me/ash/reader/ui/page/settings/SettingsPage.kt +++ b/app/src/main/java/me/ash/reader/ui/page/settings/SettingsPage.kt @@ -28,7 +28,6 @@ import me.ash.reader.ui.component.base.FeedbackIconButton import me.ash.reader.ui.ext.getCurrentVersion import me.ash.reader.ui.page.common.RouteName import me.ash.reader.ui.page.settings.tips.UpdateDialog -import me.ash.reader.ui.page.settings.tips.UpdateViewAction import me.ash.reader.ui.page.settings.tips.UpdateViewModel import me.ash.reader.ui.theme.palette.onLight @@ -76,7 +75,7 @@ fun SettingsPage( ) }, ) { - updateViewModel.dispatch(UpdateViewAction.Show) + updateViewModel.showDialog() } } Banner( diff --git a/app/src/main/java/me/ash/reader/ui/page/settings/tips/TipsAndSupportPage.kt b/app/src/main/java/me/ash/reader/ui/page/settings/tips/TipsAndSupportPage.kt index da76ae8..f8f7752 100644 --- a/app/src/main/java/me/ash/reader/ui/page/settings/tips/TipsAndSupportPage.kt +++ b/app/src/main/java/me/ash/reader/ui/page/settings/tips/TipsAndSupportPage.kt @@ -103,23 +103,21 @@ fun TipsAndSupportPage( onTap = { if (System.currentTimeMillis() - clickTime > 2000) { clickTime = System.currentTimeMillis() - updateViewModel.dispatch( - UpdateViewAction.CheckUpdate( - { - context.showToast(context.getString(R.string.checking_updates)) - context.dataStore.put( - DataStoreKeys.SkipVersionNumber, - "" + updateViewModel.checkUpdate( + { + context.showToast(context.getString(R.string.checking_updates)) + context.dataStore.put( + DataStoreKeys.SkipVersionNumber, + "" + ) + }, + { + if (!it) { + context.showToast( + context.getString(R.string.is_latest_version) ) - }, - { - if (!it) { - context.showToast( - context.getString(R.string.is_latest_version) - ) - } } - ) + } ) } else { clickTime = System.currentTimeMillis() diff --git a/app/src/main/java/me/ash/reader/ui/page/settings/tips/UpdateDialog.kt b/app/src/main/java/me/ash/reader/ui/page/settings/tips/UpdateDialog.kt index d93b2e8..8080431 100644 --- a/app/src/main/java/me/ash/reader/ui/page/settings/tips/UpdateDialog.kt +++ b/app/src/main/java/me/ash/reader/ui/page/settings/tips/UpdateDialog.kt @@ -42,8 +42,8 @@ fun UpdateDialog( updateViewModel: UpdateViewModel = hiltViewModel(), ) { val context = LocalContext.current - val viewState = updateViewModel.viewState.collectAsStateValue() - val downloadState = viewState.downloadFlow.collectAsState(initial = Download.NotYet).value + val updateUiState = updateViewModel.updateUiState.collectAsStateValue() + val downloadState = updateUiState.downloadFlow.collectAsState(initial = Download.NotYet).value val scope = rememberCoroutineScope { Dispatchers.IO } val newVersionNumber = LocalNewVersionNumber.current val newVersionPublishDate = LocalNewVersionPublishDate.current @@ -73,8 +73,8 @@ fun UpdateDialog( Dialog( modifier = Modifier.heightIn(max = 400.dp), - visible = viewState.updateDialogVisible, - onDismissRequest = { updateViewModel.dispatch(UpdateViewAction.Hide) }, + visible = updateUiState.updateDialogVisible, + onDismissRequest = { updateViewModel.hideDialog() }, icon = { Icon( imageVector = Icons.Rounded.Update, @@ -147,7 +147,7 @@ fun UpdateDialog( TextButton( onClick = { SkipVersionNumberPreference.put(context, scope, newVersionNumber.toString()) - updateViewModel.dispatch(UpdateViewAction.Hide) + updateViewModel.hideDialog() } ) { Text(text = stringResource(R.string.skip_this_version)) diff --git a/app/src/main/java/me/ash/reader/ui/page/settings/tips/UpdateViewModel.kt b/app/src/main/java/me/ash/reader/ui/page/settings/tips/UpdateViewModel.kt index 1a4e54a..a90405e 100644 --- a/app/src/main/java/me/ash/reader/ui/page/settings/tips/UpdateViewModel.kt +++ b/app/src/main/java/me/ash/reader/ui/page/settings/tips/UpdateViewModel.kt @@ -14,22 +14,10 @@ import javax.inject.Inject class UpdateViewModel @Inject constructor( private val appRepository: AppRepository, ) : ViewModel() { - private val _viewState = MutableStateFlow(UpdateViewState()) - val viewState: StateFlow = _viewState.asStateFlow() + private val _updateUiState = MutableStateFlow(UpdateUiState()) + val updateUiState: StateFlow = _updateUiState.asStateFlow() - fun dispatch(action: UpdateViewAction) { - when (action) { - is UpdateViewAction.Show -> changeUpdateDialogVisible(true) - is UpdateViewAction.Hide -> changeUpdateDialogVisible(false) - is UpdateViewAction.CheckUpdate -> checkUpdate( - action.preProcessor, - action.postProcessor - ) - is UpdateViewAction.DownloadUpdate -> downloadUpdate(action.url) - } - } - - private fun checkUpdate( + fun checkUpdate( preProcessor: suspend () -> Unit = {}, postProcessor: suspend (Boolean) -> Unit = {} ) { @@ -38,7 +26,11 @@ class UpdateViewModel @Inject constructor( preProcessor() appRepository.checkUpdate().let { it?.let { - changeUpdateDialogVisible(it) + if (it) { + showDialog() + } else { + hideDialog() + } postProcessor(it) } } @@ -46,22 +38,30 @@ class UpdateViewModel @Inject constructor( } } - private fun changeUpdateDialogVisible(visible: Boolean) { - _viewState.update { + fun showDialog() { + _updateUiState.update { it.copy( - updateDialogVisible = visible + updateDialogVisible = true ) } } - private fun downloadUpdate(url: String) { + fun hideDialog() { + _updateUiState.update { + it.copy( + updateDialogVisible = false + ) + } + } + + fun downloadUpdate(url: String) { viewModelScope.launch { - _viewState.update { + _updateUiState.update { it.copy( downloadFlow = flow { emit(Download.Progress(0)) } ) } - _viewState.update { + _updateUiState.update { it.copy( downloadFlow = appRepository.downloadFile(url) ) @@ -70,21 +70,7 @@ class UpdateViewModel @Inject constructor( } } -data class UpdateViewState( +data class UpdateUiState( val updateDialogVisible: Boolean = false, val downloadFlow: Flow = emptyFlow(), ) - -sealed class UpdateViewAction { - object Show : UpdateViewAction() - object Hide : UpdateViewAction() - - data class CheckUpdate( - val preProcessor: suspend () -> Unit = {}, - val postProcessor: suspend (Boolean) -> Unit = {} - ) : UpdateViewAction() - - data class DownloadUpdate( - val url: String, - ) : UpdateViewAction() -} \ No newline at end of file