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