From e4028b9af63f440c373ea810b0df3f1bd2d41c74 Mon Sep 17 00:00:00 2001 From: SkyD666 Date: Thu, 6 Jun 2024 22:58:28 +0800 Subject: [PATCH] [feature] Support mark all articles of a Group, Feed as read (#50); support refresh Feed via a button on top bar (#56) --- .../java/com/skyd/anivu/ext/PreferenceExt.kt | 4 + .../java/com/skyd/anivu/ext/SnackbarExt.kt | 4 +- .../com/skyd/anivu/model/db/dao/ArticleDao.kt | 22 +++ .../skyd/anivu/model/preference/Settings.kt | 8 ++ .../ShowArticlePullRefreshPreference.kt | 26 ++++ .../ShowArticleTopBarRefreshPreference.kt | 26 ++++ .../anivu/model/repository/FeedRepository.kt | 17 ++- .../ui/fragment/article/ArticleFragment.kt | 53 +++++-- .../skyd/anivu/ui/fragment/article/Filter.kt | 12 +- .../anivu/ui/fragment/feed/EditFeedSheet.kt | 9 ++ .../anivu/ui/fragment/feed/EditGroupSheet.kt | 2 + .../skyd/anivu/ui/fragment/feed/FeedEvent.kt | 5 + .../skyd/anivu/ui/fragment/feed/FeedIntent.kt | 2 + .../fragment/feed/FeedPartialStateChange.kt | 17 +++ .../skyd/anivu/ui/fragment/feed/FeedScreen.kt | 43 ++++-- .../anivu/ui/fragment/feed/FeedViewModel.kt | 135 ++++++++---------- .../anivu/ui/fragment/media/MediaFragment.kt | 9 +- .../ui/fragment/search/SearchFragment.kt | 6 +- .../article/ArticleStyleFragment.kt | 36 +++++ .../exportopml/ExportOpmlScreen.kt | 7 +- .../com/skyd/anivu/ui/local/LocalValue.kt | 5 + app/src/main/res/values/strings.xml | 9 ++ 22 files changed, 334 insertions(+), 123 deletions(-) create mode 100644 app/src/main/java/com/skyd/anivu/model/preference/appearance/article/ShowArticlePullRefreshPreference.kt create mode 100644 app/src/main/java/com/skyd/anivu/model/preference/appearance/article/ShowArticleTopBarRefreshPreference.kt diff --git a/app/src/main/java/com/skyd/anivu/ext/PreferenceExt.kt b/app/src/main/java/com/skyd/anivu/ext/PreferenceExt.kt index 4268d76e..e4e473d4 100644 --- a/app/src/main/java/com/skyd/anivu/ext/PreferenceExt.kt +++ b/app/src/main/java/com/skyd/anivu/ext/PreferenceExt.kt @@ -11,6 +11,8 @@ import com.skyd.anivu.model.preference.appearance.ThemePreference import com.skyd.anivu.model.preference.appearance.article.ArticleItemTonalElevationPreference import com.skyd.anivu.model.preference.appearance.article.ArticleListTonalElevationPreference import com.skyd.anivu.model.preference.appearance.article.ArticleTopBarTonalElevationPreference +import com.skyd.anivu.model.preference.appearance.article.ShowArticlePullRefreshPreference +import com.skyd.anivu.model.preference.appearance.article.ShowArticleTopBarRefreshPreference import com.skyd.anivu.model.preference.appearance.feed.FeedGroupExpandPreference import com.skyd.anivu.model.preference.appearance.feed.FeedListTonalElevationPreference import com.skyd.anivu.model.preference.appearance.feed.FeedTopBarTonalElevationPreference @@ -46,6 +48,8 @@ fun Preferences.toSettings(): Settings { articleItemTonalElevation = ArticleItemTonalElevationPreference.fromPreferences(this), searchListTonalElevation = SearchListTonalElevationPreference.fromPreferences(this), searchTopBarTonalElevation = SearchTopBarTonalElevationPreference.fromPreferences(this), + showArticleTopBarRefresh = ShowArticleTopBarRefreshPreference.fromPreferences(this), + showArticlePullRefresh = ShowArticlePullRefreshPreference.fromPreferences(this), // Update ignoreUpdateVersion = IgnoreUpdateVersionPreference.fromPreferences(this), diff --git a/app/src/main/java/com/skyd/anivu/ext/SnackbarExt.kt b/app/src/main/java/com/skyd/anivu/ext/SnackbarExt.kt index 11212301..3f93c547 100644 --- a/app/src/main/java/com/skyd/anivu/ext/SnackbarExt.kt +++ b/app/src/main/java/com/skyd/anivu/ext/SnackbarExt.kt @@ -27,9 +27,9 @@ fun SnackbarHostState.showSnackbar( @Composable fun SnackbarHostState.showSnackbarWithLaunchedEffect( message: String, - key1: Any? = this, + key1: Any? = null, key2: Any? = null, - key3: Any? = null, + key3: Any? = this, actionLabel: String? = null, withDismissAction: Boolean = true, duration: SnackbarDuration = if (actionLabel == null) SnackbarDuration.Short else SnackbarDuration.Indefinite diff --git a/app/src/main/java/com/skyd/anivu/model/db/dao/ArticleDao.kt b/app/src/main/java/com/skyd/anivu/model/db/dao/ArticleDao.kt index 13f5d870..c2936c9c 100644 --- a/app/src/main/java/com/skyd/anivu/model/db/dao/ArticleDao.kt +++ b/app/src/main/java/com/skyd/anivu/model/db/dao/ArticleDao.kt @@ -180,4 +180,26 @@ interface ArticleDao { """ ) fun readArticle(articleId: String, read: Boolean) + + @Transaction + @Query( + """ + UPDATE $ARTICLE_TABLE_NAME SET ${ArticleBean.IS_READ_COLUMN} = 1 + WHERE ${ArticleBean.IS_READ_COLUMN} = 0 AND ${ArticleBean.FEED_URL_COLUMN} = :feedUrl + """ + ) + fun readAllInFeed(feedUrl: String): Int + + @Transaction + @Query( + """ + UPDATE $ARTICLE_TABLE_NAME SET ${ArticleBean.IS_READ_COLUMN} = 1 + WHERE ${ArticleBean.IS_READ_COLUMN} = 0 AND ${ArticleBean.FEED_URL_COLUMN} IN ( + SELECT DISTINCT ${FeedBean.URL_COLUMN} FROM $FEED_TABLE_NAME + WHERE ${FeedBean.GROUP_ID_COLUMN} = :groupId OR + :groupId IS NULL AND ${FeedBean.GROUP_ID_COLUMN} IS NULL + ) + """ + ) + fun readAllInGroup(groupId: String?): Int } \ No newline at end of file diff --git a/app/src/main/java/com/skyd/anivu/model/preference/Settings.kt b/app/src/main/java/com/skyd/anivu/model/preference/Settings.kt index 4bdd30e6..b6a62294 100644 --- a/app/src/main/java/com/skyd/anivu/model/preference/Settings.kt +++ b/app/src/main/java/com/skyd/anivu/model/preference/Settings.kt @@ -16,6 +16,8 @@ import com.skyd.anivu.model.preference.appearance.ThemePreference import com.skyd.anivu.model.preference.appearance.article.ArticleItemTonalElevationPreference import com.skyd.anivu.model.preference.appearance.article.ArticleListTonalElevationPreference import com.skyd.anivu.model.preference.appearance.article.ArticleTopBarTonalElevationPreference +import com.skyd.anivu.model.preference.appearance.article.ShowArticlePullRefreshPreference +import com.skyd.anivu.model.preference.appearance.article.ShowArticleTopBarRefreshPreference import com.skyd.anivu.model.preference.appearance.feed.FeedGroupExpandPreference import com.skyd.anivu.model.preference.appearance.feed.FeedListTonalElevationPreference import com.skyd.anivu.model.preference.appearance.feed.FeedTopBarTonalElevationPreference @@ -58,6 +60,8 @@ import com.skyd.anivu.ui.local.LocalPlayerShow85sButton import com.skyd.anivu.ui.local.LocalPlayerShowScreenshotButton import com.skyd.anivu.ui.local.LocalSearchListTonalElevation import com.skyd.anivu.ui.local.LocalSearchTopBarTonalElevation +import com.skyd.anivu.ui.local.LocalShowArticlePullRefresh +import com.skyd.anivu.ui.local.LocalShowArticleTopBarRefresh import com.skyd.anivu.ui.local.LocalTextFieldStyle import com.skyd.anivu.ui.local.LocalTheme import com.skyd.anivu.ui.local.LocalUseAutoDelete @@ -79,6 +83,8 @@ data class Settings( val articleItemTonalElevation: Float = ArticleItemTonalElevationPreference.default, val searchListTonalElevation: Float = SearchListTonalElevationPreference.default, val searchTopBarTonalElevation: Float = SearchTopBarTonalElevationPreference.default, + val showArticleTopBarRefresh: Boolean = ShowArticleTopBarRefreshPreference.default, + val showArticlePullRefresh: Boolean = ShowArticlePullRefreshPreference.default, // Update val ignoreUpdateVersion: Long = IgnoreUpdateVersionPreference.default, // Behavior @@ -122,6 +128,8 @@ fun SettingsProvider( LocalArticleItemTonalElevation provides settings.articleItemTonalElevation, LocalSearchListTonalElevation provides settings.searchListTonalElevation, LocalSearchTopBarTonalElevation provides settings.searchTopBarTonalElevation, + LocalShowArticleTopBarRefresh provides settings.showArticleTopBarRefresh, + LocalShowArticlePullRefresh provides settings.showArticlePullRefresh, // Update LocalIgnoreUpdateVersion provides settings.ignoreUpdateVersion, // Behavior diff --git a/app/src/main/java/com/skyd/anivu/model/preference/appearance/article/ShowArticlePullRefreshPreference.kt b/app/src/main/java/com/skyd/anivu/model/preference/appearance/article/ShowArticlePullRefreshPreference.kt new file mode 100644 index 00000000..ec87ff34 --- /dev/null +++ b/app/src/main/java/com/skyd/anivu/model/preference/appearance/article/ShowArticlePullRefreshPreference.kt @@ -0,0 +1,26 @@ +package com.skyd.anivu.model.preference.appearance.article + +import android.content.Context +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.booleanPreferencesKey +import com.skyd.anivu.base.BasePreference +import com.skyd.anivu.ext.dataStore +import com.skyd.anivu.ext.put +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +object ShowArticlePullRefreshPreference : BasePreference { + private const val SHOW_ARTICLE_PULL_REFRESH = "showArticlePullRefresh" + override val default = true + + val key = booleanPreferencesKey(SHOW_ARTICLE_PULL_REFRESH) + + fun put(context: Context, scope: CoroutineScope, value: Boolean) { + scope.launch(Dispatchers.IO) { + context.dataStore.put(key, value) + } + } + + override fun fromPreferences(preferences: Preferences): Boolean = preferences[key] ?: default +} \ No newline at end of file diff --git a/app/src/main/java/com/skyd/anivu/model/preference/appearance/article/ShowArticleTopBarRefreshPreference.kt b/app/src/main/java/com/skyd/anivu/model/preference/appearance/article/ShowArticleTopBarRefreshPreference.kt new file mode 100644 index 00000000..18f32b41 --- /dev/null +++ b/app/src/main/java/com/skyd/anivu/model/preference/appearance/article/ShowArticleTopBarRefreshPreference.kt @@ -0,0 +1,26 @@ +package com.skyd.anivu.model.preference.appearance.article + +import android.content.Context +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.booleanPreferencesKey +import com.skyd.anivu.base.BasePreference +import com.skyd.anivu.ext.dataStore +import com.skyd.anivu.ext.put +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +object ShowArticleTopBarRefreshPreference : BasePreference { + private const val SHOW_ARTICLE_TOP_BAR_REFRESH = "showArticleTopBarRefresh" + override val default = false + + val key = booleanPreferencesKey(SHOW_ARTICLE_TOP_BAR_REFRESH) + + fun put(context: Context, scope: CoroutineScope, value: Boolean) { + scope.launch(Dispatchers.IO) { + context.dataStore.put(key, value) + } + } + + override fun fromPreferences(preferences: Preferences): Boolean = preferences[key] ?: default +} \ No newline at end of file diff --git a/app/src/main/java/com/skyd/anivu/model/repository/FeedRepository.kt b/app/src/main/java/com/skyd/anivu/model/repository/FeedRepository.kt index dd3680de..3630ca7b 100644 --- a/app/src/main/java/com/skyd/anivu/model/repository/FeedRepository.kt +++ b/app/src/main/java/com/skyd/anivu/model/repository/FeedRepository.kt @@ -9,6 +9,7 @@ import com.skyd.anivu.ext.isLocal import com.skyd.anivu.ext.isNetwork import com.skyd.anivu.model.bean.FeedBean import com.skyd.anivu.model.bean.GroupBean +import com.skyd.anivu.model.db.dao.ArticleDao import com.skyd.anivu.model.db.dao.FeedDao import com.skyd.anivu.model.db.dao.GroupDao import kotlinx.coroutines.Dispatchers @@ -23,8 +24,9 @@ import java.util.UUID import javax.inject.Inject class FeedRepository @Inject constructor( - private val feedDao: FeedDao, private val groupDao: GroupDao, + private val feedDao: FeedDao, + private val articleDao: ArticleDao, private val rssHelper: RssHelper, ) : BaseRepository() { suspend fun requestGroupAnyList(): Flow> { @@ -182,6 +184,19 @@ class FeedRepository @Inject constructor( } }.flowOn(Dispatchers.IO) } + + suspend fun readAllInGroup(groupId: String?): Flow { + return flow { + val realGroupId = if (groupId == GroupBean.DEFAULT_GROUP_ID) null else groupId + emit(articleDao.readAllInGroup(realGroupId)) + }.flowOn(Dispatchers.IO) + } + + suspend fun readAllInFeed(feedUrl: String): Flow { + return flow { + emit(articleDao.readAllInFeed(feedUrl)) + }.flowOn(Dispatchers.IO) + } } fun tryDeleteFeedIconFile(path: String?) { diff --git a/app/src/main/java/com/skyd/anivu/ui/fragment/article/ArticleFragment.kt b/app/src/main/java/com/skyd/anivu/ui/fragment/article/ArticleFragment.kt index 3af4e767..bfa35ee5 100644 --- a/app/src/main/java/com/skyd/anivu/ui/fragment/article/ArticleFragment.kt +++ b/app/src/main/java/com/skyd/anivu/ui/fragment/article/ArticleFragment.kt @@ -5,6 +5,11 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.rememberInfiniteTransition +import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.foundation.layout.Arrangement @@ -19,6 +24,7 @@ import androidx.compose.foundation.lazy.grid.LazyGridState import androidx.compose.foundation.lazy.grid.rememberLazyGridState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.ArrowUpward +import androidx.compose.material.icons.outlined.Refresh import androidx.compose.material.icons.outlined.Search import androidx.compose.material.pullrefresh.PullRefreshIndicator import androidx.compose.material.pullrefresh.pullRefresh @@ -59,7 +65,7 @@ import com.skyd.anivu.base.BaseComposeFragment import com.skyd.anivu.base.mvi.getDispatcher import com.skyd.anivu.ext.plus import com.skyd.anivu.ext.popBackStackWithLifecycle -import com.skyd.anivu.ext.showSnackbar +import com.skyd.anivu.ext.showSnackbarWithLaunchedEffect import com.skyd.anivu.model.bean.ArticleWithFeed import com.skyd.anivu.ui.component.AniVuFloatingActionButton import com.skyd.anivu.ui.component.AniVuIconButton @@ -75,6 +81,8 @@ import com.skyd.anivu.ui.fragment.search.SearchFragment import com.skyd.anivu.ui.local.LocalArticleListTonalElevation import com.skyd.anivu.ui.local.LocalArticleTopBarTonalElevation import com.skyd.anivu.ui.local.LocalNavController +import com.skyd.anivu.ui.local.LocalShowArticlePullRefresh +import com.skyd.anivu.ui.local.LocalShowArticleTopBarRefresh import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.launch @@ -157,6 +165,27 @@ private fun ArticleContentScreen( ), ), actions = { + if (LocalShowArticleTopBarRefresh.current) { + val angle = if (uiState.articleListState.loading) { + val infiniteTransition = + rememberInfiniteTransition(label = "topBarRefreshTransition") + infiniteTransition.animateFloat( + initialValue = 0f, + targetValue = 360f, + animationSpec = infiniteRepeatable( + animation = tween(1000, easing = LinearEasing) + ), + label = "topBarRefreshAnimate", + ).value + } else 0f + AniVuIconButton( + onClick = { dispatch(ArticleIntent.Refresh(feedUrls)) }, + imageVector = Icons.Outlined.Refresh, + contentDescription = stringResource(id = R.string.refresh), + rotate = angle, + enabled = !uiState.articleListState.loading, + ) + } AniVuIconButton( onClick = { navController.navigate( @@ -230,16 +259,16 @@ private fun ArticleContentScreen( when (val event = uiEvent) { is ArticleEvent.InitArticleListResultEvent.Failed -> - snackbarHostState.showSnackbar(message = event.msg, scope = scope) + snackbarHostState.showSnackbarWithLaunchedEffect(message = event.msg, key1 = event) is ArticleEvent.RefreshArticleListResultEvent.Failed -> - snackbarHostState.showSnackbar(message = event.msg, scope = scope) + snackbarHostState.showSnackbarWithLaunchedEffect(message = event.msg, key1 = event) is ArticleEvent.FavoriteArticleResultEvent.Failed -> - snackbarHostState.showSnackbar(message = event.msg, scope = scope) + snackbarHostState.showSnackbarWithLaunchedEffect(message = event.msg, key1 = event) is ArticleEvent.ReadArticleResultEvent.Failed -> - snackbarHostState.showSnackbar(message = event.msg, scope = scope) + snackbarHostState.showSnackbarWithLaunchedEffect(message = event.msg, key1 = event) null -> Unit } @@ -264,7 +293,7 @@ private fun Content( ) Box( modifier = Modifier - .pullRefresh(state) + .pullRefresh(state = state, enabled = LocalShowArticlePullRefresh.current) .padding(top = contentPadding.calculateTopPadding()), ) { Column { @@ -291,11 +320,13 @@ private fun Content( ) } - PullRefreshIndicator( - refreshing = uiState.articleListState.loading, - state = state, - modifier = Modifier.align(Alignment.TopCenter), - ) + if (LocalShowArticlePullRefresh.current) { + PullRefreshIndicator( + refreshing = uiState.articleListState.loading, + state = state, + modifier = Modifier.align(Alignment.TopCenter), + ) + } } } diff --git a/app/src/main/java/com/skyd/anivu/ui/fragment/article/Filter.kt b/app/src/main/java/com/skyd/anivu/ui/fragment/article/Filter.kt index e90ee61a..de3ebec3 100644 --- a/app/src/main/java/com/skyd/anivu/ui/fragment/article/Filter.kt +++ b/app/src/main/java/com/skyd/anivu/ui/fragment/article/Filter.kt @@ -3,8 +3,6 @@ package com.skyd.anivu.ui.fragment.article import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.animateContentSize import androidx.compose.animation.expandHorizontally -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkHorizontally import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.Arrangement @@ -65,15 +63,12 @@ internal fun FilterRow( ) { AnimatedVisibility( visible = favoriteFilterValue != null || readFilterValue != null, - enter = fadeIn() + expandHorizontally(), - exit = fadeOut() + shrinkHorizontally(), + enter = expandHorizontally(), + exit = shrinkHorizontally(), ) { FilterSetting( filterCount = { - listOf( - favoriteFilterValue, - readFilterValue - ).map { if (it != null) 1 else 0 }.sum() + listOfNotNull(favoriteFilterValue, readFilterValue).size }, onClearAllFilters = { onFilterFavorite(null) @@ -252,5 +247,4 @@ private fun FavoriteReadFilter( } } } - } \ No newline at end of file diff --git a/app/src/main/java/com/skyd/anivu/ui/fragment/feed/EditFeedSheet.kt b/app/src/main/java/com/skyd/anivu/ui/fragment/feed/EditFeedSheet.kt index 8b2590c6..63d3a942 100644 --- a/app/src/main/java/com/skyd/anivu/ui/fragment/feed/EditFeedSheet.kt +++ b/app/src/main/java/com/skyd/anivu/ui/fragment/feed/EditFeedSheet.kt @@ -29,6 +29,7 @@ import androidx.compose.material.icons.filled.Edit import androidx.compose.material.icons.outlined.Add import androidx.compose.material.icons.outlined.Check import androidx.compose.material.icons.outlined.Delete +import androidx.compose.material.icons.outlined.DoneAll import androidx.compose.material.icons.outlined.History import androidx.compose.material.icons.outlined.Image import androidx.compose.material.icons.outlined.Link @@ -78,6 +79,7 @@ fun EditFeedSheet( onDismissRequest: () -> Unit, feed: FeedBean, groups: List, + onReadAll: (String) -> Unit, onRefresh: (String) -> Unit, onDelete: (String) -> Unit, onUrlChange: (String) -> Unit, @@ -118,6 +120,7 @@ fun EditFeedSheet( // Options OptionArea( + onReadAll = { onReadAll(feed.url) }, onRefresh = { onRefresh(feed.url) }, onDelete = { onDelete(feed.url) @@ -342,6 +345,7 @@ private fun LinkArea(link: String, onLinkClick: () -> Unit) { internal fun OptionArea( deleteEnabled: Boolean = true, deleteWarningText: String = stringResource(id = R.string.feed_screen_delete_feed_warning), + onReadAll: () -> Unit, onRefresh: () -> Unit, onDelete: () -> Unit, ) { @@ -358,6 +362,11 @@ internal fun OptionArea( verticalArrangement = Arrangement.spacedBy(8.dp), overflow = FlowRowOverflow.Visible ) { + SheetChip( + icon = Icons.Outlined.DoneAll, + text = stringResource(id = R.string.read_all), + onClick = onReadAll, + ) SheetChip( icon = Icons.Outlined.Refresh, text = stringResource(id = R.string.refresh), diff --git a/app/src/main/java/com/skyd/anivu/ui/fragment/feed/EditGroupSheet.kt b/app/src/main/java/com/skyd/anivu/ui/fragment/feed/EditGroupSheet.kt index b3f073cf..64e3ddee 100644 --- a/app/src/main/java/com/skyd/anivu/ui/fragment/feed/EditGroupSheet.kt +++ b/app/src/main/java/com/skyd/anivu/ui/fragment/feed/EditGroupSheet.kt @@ -33,6 +33,7 @@ fun EditGroupSheet( onDismissRequest: () -> Unit, group: GroupBean, groups: List, + onReadAll: (String) -> Unit, onRefresh: (String) -> Unit, onDelete: (String) -> Unit, onNameChange: (String) -> Unit, @@ -67,6 +68,7 @@ fun EditGroupSheet( id = R.string.feed_screen_delete_group_warning, group.name, ), + onReadAll = { onReadAll(group.groupId) }, onRefresh = { onRefresh(group.groupId) }, onDelete = { onDelete(group.groupId) diff --git a/app/src/main/java/com/skyd/anivu/ui/fragment/feed/FeedEvent.kt b/app/src/main/java/com/skyd/anivu/ui/fragment/feed/FeedEvent.kt index a678ba36..149092ea 100644 --- a/app/src/main/java/com/skyd/anivu/ui/fragment/feed/FeedEvent.kt +++ b/app/src/main/java/com/skyd/anivu/ui/fragment/feed/FeedEvent.kt @@ -48,4 +48,9 @@ sealed interface FeedEvent : MviSingleEvent { data class Success(val group: GroupBean) : EditGroupResultEvent data class Failed(val msg: String) : EditGroupResultEvent } + + sealed interface ReadAllResultEvent : FeedEvent { + data class Success(val count: Int) : ReadAllResultEvent + data class Failed(val msg: String) : ReadAllResultEvent + } } \ No newline at end of file diff --git a/app/src/main/java/com/skyd/anivu/ui/fragment/feed/FeedIntent.kt b/app/src/main/java/com/skyd/anivu/ui/fragment/feed/FeedIntent.kt index 2907721c..9a4d727a 100644 --- a/app/src/main/java/com/skyd/anivu/ui/fragment/feed/FeedIntent.kt +++ b/app/src/main/java/com/skyd/anivu/ui/fragment/feed/FeedIntent.kt @@ -21,6 +21,8 @@ sealed interface FeedIntent : MviIntent { data class EditFeedCustomIcon(val url: String, val customIcon: Uri?) : FeedIntent data class RemoveFeed(val url: String) : FeedIntent + data class ReadAllInFeed(val feedUrl: String) : FeedIntent + data class ReadAllInGroup(val groupId: String?) : FeedIntent data class RefreshFeed(val url: String) : FeedIntent data class RefreshGroupFeed(val groupId: String?) : FeedIntent data class CreateGroup(val group: GroupBean) : FeedIntent diff --git a/app/src/main/java/com/skyd/anivu/ui/fragment/feed/FeedPartialStateChange.kt b/app/src/main/java/com/skyd/anivu/ui/fragment/feed/FeedPartialStateChange.kt index d46d8838..a0db78e3 100644 --- a/app/src/main/java/com/skyd/anivu/ui/fragment/feed/FeedPartialStateChange.kt +++ b/app/src/main/java/com/skyd/anivu/ui/fragment/feed/FeedPartialStateChange.kt @@ -64,6 +64,23 @@ internal sealed interface FeedPartialStateChange { data class Failed(val msg: String) : RemoveFeed } + sealed interface ReadAll : FeedPartialStateChange { + override fun reduce(oldState: FeedState): FeedState { + return when (this) { + is Success -> oldState.copy( + loadingDialog = false, + ) + + is Failed -> oldState.copy( + loadingDialog = false, + ) + } + } + + data class Success(val count: Int) : ReadAll + data class Failed(val msg: String) : ReadAll + } + sealed interface RefreshFeed : FeedPartialStateChange { override fun reduce(oldState: FeedState): FeedState { return when (this) { diff --git a/app/src/main/java/com/skyd/anivu/ui/fragment/feed/FeedScreen.kt b/app/src/main/java/com/skyd/anivu/ui/fragment/feed/FeedScreen.kt index 164457a4..0ebb2a45 100644 --- a/app/src/main/java/com/skyd/anivu/ui/fragment/feed/FeedScreen.kt +++ b/app/src/main/java/com/skyd/anivu/ui/fragment/feed/FeedScreen.kt @@ -43,7 +43,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateMapOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier @@ -56,6 +55,7 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.unit.dp @@ -67,7 +67,7 @@ import com.skyd.anivu.ext.dataStore import com.skyd.anivu.ext.getOrDefault import com.skyd.anivu.ext.isCompact import com.skyd.anivu.ext.plus -import com.skyd.anivu.ext.showSnackbar +import com.skyd.anivu.ext.showSnackbarWithLaunchedEffect import com.skyd.anivu.ext.snapshotStateMapSaver import com.skyd.anivu.model.bean.FeedBean import com.skyd.anivu.model.bean.FeedBean.Companion.isDefaultGroup @@ -177,7 +177,6 @@ private fun FeedList( val navController = LocalNavController.current val snackbarHostState = remember { SnackbarHostState() } val windowSizeClass = LocalWindowSizeClass.current - val scope = rememberCoroutineScope() var openAddDialog by rememberSaveable { mutableStateOf(false) } var addDialogUrl by rememberSaveable { mutableStateOf("") } var openEditFeedDialog by rememberSaveable { mutableStateOf(null) } @@ -281,31 +280,34 @@ private fun FeedList( when (val event = uiEvent) { is FeedEvent.AddFeedResultEvent.Failed -> - snackbarHostState.showSnackbar(message = event.msg, scope = scope) + snackbarHostState.showSnackbarWithLaunchedEffect(message = event.msg, key1 = event) is FeedEvent.EditFeedResultEvent.Failed -> - snackbarHostState.showSnackbar(message = event.msg, scope = scope) + snackbarHostState.showSnackbarWithLaunchedEffect(message = event.msg, key1 = event) is FeedEvent.InitFeetListResultEvent.Failed -> - snackbarHostState.showSnackbar(message = event.msg, scope = scope) + snackbarHostState.showSnackbarWithLaunchedEffect(message = event.msg, key1 = event) is FeedEvent.RemoveFeedResultEvent.Failed -> - snackbarHostState.showSnackbar(message = event.msg, scope = scope) + snackbarHostState.showSnackbarWithLaunchedEffect(message = event.msg, key1 = event) is FeedEvent.RefreshFeedResultEvent.Failed -> - snackbarHostState.showSnackbar(message = event.msg, scope = scope) + snackbarHostState.showSnackbarWithLaunchedEffect(message = event.msg, key1 = event) is FeedEvent.CreateGroupResultEvent.Failed -> - snackbarHostState.showSnackbar(message = event.msg, scope = scope) + snackbarHostState.showSnackbarWithLaunchedEffect(message = event.msg, key1 = event) is FeedEvent.MoveFeedsToGroupResultEvent.Failed -> - snackbarHostState.showSnackbar(message = event.msg, scope = scope) + snackbarHostState.showSnackbarWithLaunchedEffect(message = event.msg, key1 = event) is FeedEvent.DeleteGroupResultEvent.Failed -> - snackbarHostState.showSnackbar(message = event.msg, scope = scope) + snackbarHostState.showSnackbarWithLaunchedEffect(message = event.msg, key1 = event) is FeedEvent.EditGroupResultEvent.Failed -> - snackbarHostState.showSnackbar(message = event.msg, scope = scope) + snackbarHostState.showSnackbarWithLaunchedEffect(message = event.msg, key1 = event) + + is FeedEvent.ReadAllResultEvent.Failed -> + snackbarHostState.showSnackbarWithLaunchedEffect(message = event.msg, key1 = event) is FeedEvent.EditFeedResultEvent.Success -> LaunchedEffect(event) { if (openEditFeedDialog != null) openEditFeedDialog = event.feed @@ -319,12 +321,23 @@ private fun FeedList( openEditFeedDialog = event.feed } + is FeedEvent.ReadAllResultEvent.Success -> + snackbarHostState.showSnackbarWithLaunchedEffect( + message = pluralStringResource( + id = R.plurals.feed_screen_read_all_result, + count = event.count, + event.count, + ), + key1 = event, + ) + FeedEvent.RemoveFeedResultEvent.Success, is FeedEvent.RefreshFeedResultEvent.Success, FeedEvent.CreateGroupResultEvent.Success, FeedEvent.MoveFeedsToGroupResultEvent.Success, FeedEvent.DeleteGroupResultEvent.Success, null -> Unit + } if (openAddDialog) { @@ -354,8 +367,9 @@ private fun FeedList( onDismissRequest = { openEditFeedDialog = null }, feed = openEditFeedDialog!!, groups = groups, - onDelete = { dispatch(FeedIntent.RemoveFeed(it)) }, + onReadAll = { dispatch(FeedIntent.ReadAllInFeed(it)) }, onRefresh = { dispatch(FeedIntent.RefreshFeed(it)) }, + onDelete = { dispatch(FeedIntent.RemoveFeed(it)) }, onUrlChange = { dispatch(FeedIntent.EditFeedUrl(oldUrl = openEditFeedDialog!!.url, newUrl = it)) }, @@ -406,8 +420,9 @@ private fun FeedList( onDismissRequest = { openEditGroupDialog = null }, group = openEditGroupDialog!!, groups = groups, - onDelete = { dispatch(FeedIntent.DeleteGroup(it)) }, + onReadAll = { dispatch(FeedIntent.ReadAllInGroup(it)) }, onRefresh = { dispatch(FeedIntent.RefreshGroupFeed(it)) }, + onDelete = { dispatch(FeedIntent.DeleteGroup(it)) }, onNameChange = { dispatch( FeedIntent.RenameGroup(groupId = openEditGroupDialog!!.groupId, name = it) diff --git a/app/src/main/java/com/skyd/anivu/ui/fragment/feed/FeedViewModel.kt b/app/src/main/java/com/skyd/anivu/ui/fragment/feed/FeedViewModel.kt index 0a86e1f9..ce29cf7f 100644 --- a/app/src/main/java/com/skyd/anivu/ui/fragment/feed/FeedViewModel.kt +++ b/app/src/main/java/com/skyd/anivu/ui/fragment/feed/FeedViewModel.kt @@ -53,73 +53,62 @@ class FeedViewModel @Inject constructor( private fun Flow.sendSingleEvent(): Flow { return onEach { change -> val event = when (change) { - is FeedPartialStateChange.AddFeed.Success -> { + is FeedPartialStateChange.AddFeed.Success -> FeedEvent.AddFeedResultEvent.Success(change.feed) - } - is FeedPartialStateChange.AddFeed.Failed -> { + is FeedPartialStateChange.AddFeed.Failed -> FeedEvent.AddFeedResultEvent.Failed(change.msg) - } - is FeedPartialStateChange.EditFeed.Success -> { + is FeedPartialStateChange.EditFeed.Success -> FeedEvent.EditFeedResultEvent.Success(change.feed) - } - is FeedPartialStateChange.EditFeed.Failed -> { + is FeedPartialStateChange.EditFeed.Failed -> FeedEvent.EditFeedResultEvent.Failed(change.msg) - } - is FeedPartialStateChange.RemoveFeed.Success -> { + is FeedPartialStateChange.RemoveFeed.Success -> FeedEvent.RemoveFeedResultEvent.Success - } - is FeedPartialStateChange.RemoveFeed.Failed -> { + is FeedPartialStateChange.RemoveFeed.Failed -> FeedEvent.RemoveFeedResultEvent.Failed(change.msg) - } - is FeedPartialStateChange.RefreshFeed.Success -> { + is FeedPartialStateChange.RefreshFeed.Success -> FeedEvent.RefreshFeedResultEvent.Success - } - is FeedPartialStateChange.RefreshFeed.Failed -> { + is FeedPartialStateChange.RefreshFeed.Failed -> FeedEvent.RefreshFeedResultEvent.Failed(change.msg) - } - is FeedPartialStateChange.FeedList.Failed -> { + is FeedPartialStateChange.FeedList.Failed -> FeedEvent.InitFeetListResultEvent.Failed(change.msg) - } - is FeedPartialStateChange.CreateGroup.Success -> { + is FeedPartialStateChange.CreateGroup.Success -> FeedEvent.CreateGroupResultEvent.Success - } - is FeedPartialStateChange.CreateGroup.Failed -> { + is FeedPartialStateChange.CreateGroup.Failed -> FeedEvent.CreateGroupResultEvent.Failed(change.msg) - } - is FeedPartialStateChange.DeleteGroup.Success -> { + is FeedPartialStateChange.DeleteGroup.Success -> FeedEvent.DeleteGroupResultEvent.Success - } - is FeedPartialStateChange.DeleteGroup.Failed -> { + is FeedPartialStateChange.DeleteGroup.Failed -> FeedEvent.DeleteGroupResultEvent.Failed(change.msg) - } - is FeedPartialStateChange.MoveFeedsToGroup.Success -> { + is FeedPartialStateChange.MoveFeedsToGroup.Success -> FeedEvent.MoveFeedsToGroupResultEvent.Success - } - is FeedPartialStateChange.MoveFeedsToGroup.Failed -> { + is FeedPartialStateChange.MoveFeedsToGroup.Failed -> FeedEvent.MoveFeedsToGroupResultEvent.Failed(change.msg) - } - is FeedPartialStateChange.EditGroup.Success -> { + is FeedPartialStateChange.EditGroup.Success -> FeedEvent.EditGroupResultEvent.Success(change.group) - } - is FeedPartialStateChange.EditGroup.Failed -> { + is FeedPartialStateChange.EditGroup.Failed -> FeedEvent.EditGroupResultEvent.Failed(change.msg) - } + + is FeedPartialStateChange.ReadAll.Success -> + FeedEvent.ReadAllResultEvent.Success(change.count) + + is FeedPartialStateChange.ReadAll.Failed -> + FeedEvent.ReadAllResultEvent.Failed(change.msg) else -> return@onEach } @@ -144,37 +133,27 @@ class FeedViewModel @Inject constructor( }.startWith(FeedPartialStateChange.LoadingDialog.Show) .catchMap { FeedPartialStateChange.AddFeed.Failed(it.message.toString()) } }, - filterIsInstance().flatMapConcat { intent -> - feedRepo.editFeedUrl(oldUrl = intent.oldUrl, newUrl = intent.newUrl).map { - FeedPartialStateChange.EditFeed.Success(it) - }.startWith(FeedPartialStateChange.LoadingDialog.Show) - .catchMap { FeedPartialStateChange.EditFeed.Failed(it.message.toString()) } - }, - filterIsInstance().flatMapConcat { intent -> - feedRepo.editFeedGroup(url = intent.url, groupId = intent.groupId).map { - FeedPartialStateChange.EditFeed.Success(it) - }.startWith(FeedPartialStateChange.LoadingDialog.Show) - .catchMap { FeedPartialStateChange.EditFeed.Failed(it.message.toString()) } - }, - filterIsInstance().flatMapConcat { intent -> - feedRepo.editFeedCustomDescription( - url = intent.url, - customDescription = intent.customDescription, - ).map { - FeedPartialStateChange.EditFeed.Success(it) - }.startWith(FeedPartialStateChange.LoadingDialog.Show) - .catchMap { FeedPartialStateChange.EditFeed.Failed(it.message.toString()) } - }, - filterIsInstance().flatMapConcat { intent -> - feedRepo.editFeedCustomIcon(url = intent.url, customIcon = intent.customIcon).map { - FeedPartialStateChange.EditFeed.Success(it) - }.startWith(FeedPartialStateChange.LoadingDialog.Show) - .catchMap { FeedPartialStateChange.EditFeed.Failed(it.message.toString()) } - }, - filterIsInstance().flatMapConcat { intent -> - feedRepo.editFeedNickname(url = intent.url, nickname = intent.nickname).map { - FeedPartialStateChange.EditFeed.Success(it) - }.startWith(FeedPartialStateChange.LoadingDialog.Show) + merge( + filterIsInstance().map { intent -> + feedRepo.editFeedUrl(oldUrl = intent.oldUrl, newUrl = intent.newUrl) + }, + filterIsInstance().map { intent -> + feedRepo.editFeedGroup(url = intent.url, groupId = intent.groupId) + }, + filterIsInstance().map { intent -> + feedRepo.editFeedCustomDescription( + url = intent.url, customDescription = intent.customDescription, + ) + }, + filterIsInstance().map { intent -> + feedRepo.editFeedCustomIcon(url = intent.url, customIcon = intent.customIcon) + }, + filterIsInstance().map { intent -> + feedRepo.editFeedNickname(url = intent.url, nickname = intent.nickname) + }, + ).flatMapConcat { flow -> + flow.map { FeedPartialStateChange.EditFeed.Success(it) } + .startWith(FeedPartialStateChange.LoadingDialog.Show) .catchMap { FeedPartialStateChange.EditFeed.Failed(it.message.toString()) } }, filterIsInstance().flatMapConcat { intent -> @@ -183,16 +162,24 @@ class FeedViewModel @Inject constructor( else FeedPartialStateChange.RemoveFeed.Failed("Remove failed!") }.startWith(FeedPartialStateChange.LoadingDialog.Show) }, - filterIsInstance().flatMapConcat { intent -> - articleRepo.refreshArticleList(listOf(intent.url)).map { - FeedPartialStateChange.RefreshFeed.Success - }.startWith(FeedPartialStateChange.LoadingDialog.Show) - .catchMap { FeedPartialStateChange.RefreshFeed.Failed(it.message.toString()) } + merge( + filterIsInstance() + .map { intent -> feedRepo.readAllInGroup(intent.groupId) }, + filterIsInstance() + .map { intent -> feedRepo.readAllInFeed(intent.feedUrl) }, + ).flatMapConcat { flow -> + flow.map { FeedPartialStateChange.ReadAll.Success(it) } + .startWith(FeedPartialStateChange.LoadingDialog.Show) + .catchMap { FeedPartialStateChange.ReadAll.Failed(it.message.toString()) } }, - filterIsInstance().flatMapConcat { intent -> - articleRepo.refreshGroupArticles(intent.groupId).map { - FeedPartialStateChange.RefreshFeed.Success - }.startWith(FeedPartialStateChange.LoadingDialog.Show) + merge( + filterIsInstance() + .map { intent -> articleRepo.refreshArticleList(listOf(intent.url)) }, + filterIsInstance() + .map { intent -> articleRepo.refreshGroupArticles(intent.groupId) }, + ).flatMapConcat { flow -> + flow.map { FeedPartialStateChange.RefreshFeed.Success } + .startWith(FeedPartialStateChange.LoadingDialog.Show) .catchMap { FeedPartialStateChange.RefreshFeed.Failed(it.message.toString()) } }, filterIsInstance().flatMapConcat { intent -> diff --git a/app/src/main/java/com/skyd/anivu/ui/fragment/media/MediaFragment.kt b/app/src/main/java/com/skyd/anivu/ui/fragment/media/MediaFragment.kt index cc8e292c..f5a46924 100644 --- a/app/src/main/java/com/skyd/anivu/ui/fragment/media/MediaFragment.kt +++ b/app/src/main/java/com/skyd/anivu/ui/fragment/media/MediaFragment.kt @@ -32,7 +32,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -51,7 +50,7 @@ import com.skyd.anivu.config.Const import com.skyd.anivu.ext.activity import com.skyd.anivu.ext.isCompact import com.skyd.anivu.ext.popBackStackWithLifecycle -import com.skyd.anivu.ext.showSnackbar +import com.skyd.anivu.ext.showSnackbarWithLaunchedEffect import com.skyd.anivu.ext.toUri import com.skyd.anivu.model.bean.VideoBean import com.skyd.anivu.ui.activity.PlayActivity @@ -94,7 +93,6 @@ fun MediaScreen(path: String, hasParentDir: Boolean, viewModel: MediaViewModel = val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) val snackbarHostState = remember { SnackbarHostState() } val navController = LocalNavController.current - val scope = rememberCoroutineScope() val context = LocalContext.current val windowSizeClass = LocalWindowSizeClass.current @@ -206,9 +204,8 @@ fun MediaScreen(path: String, hasParentDir: Boolean, viewModel: MediaViewModel = WaitingDialog(visible = uiState.loadingDialog) when (val event = uiEvent) { - is MediaEvent.DeleteUriResultEvent.Failed -> snackbarHostState.showSnackbar( - scope = scope, message = event.msg, - ) + is MediaEvent.DeleteUriResultEvent.Failed -> + snackbarHostState.showSnackbarWithLaunchedEffect(message = event.msg, key1 = event) null -> Unit } diff --git a/app/src/main/java/com/skyd/anivu/ui/fragment/search/SearchFragment.kt b/app/src/main/java/com/skyd/anivu/ui/fragment/search/SearchFragment.kt index 67c642fa..02256e57 100644 --- a/app/src/main/java/com/skyd/anivu/ui/fragment/search/SearchFragment.kt +++ b/app/src/main/java/com/skyd/anivu/ui/fragment/search/SearchFragment.kt @@ -70,7 +70,7 @@ import com.skyd.anivu.R import com.skyd.anivu.base.BaseComposeFragment import com.skyd.anivu.base.mvi.getDispatcher import com.skyd.anivu.ext.plus -import com.skyd.anivu.ext.showSnackbar +import com.skyd.anivu.ext.showSnackbarWithLaunchedEffect import com.skyd.anivu.model.bean.ArticleWithFeed import com.skyd.anivu.model.bean.FeedViewBean import com.skyd.anivu.ui.component.AniVuFloatingActionButton @@ -241,10 +241,10 @@ fun SearchScreen( when (val event = uiEvent) { is SearchEvent.FavoriteArticleResultEvent.Failed -> - snackbarHostState.showSnackbar(message = event.msg, scope = scope) + snackbarHostState.showSnackbarWithLaunchedEffect(message = event.msg, key1 = event) is SearchEvent.ReadArticleResultEvent.Failed -> - snackbarHostState.showSnackbar(message = event.msg, scope = scope) + snackbarHostState.showSnackbarWithLaunchedEffect(message = event.msg, key1 = event) null -> Unit } diff --git a/app/src/main/java/com/skyd/anivu/ui/fragment/settings/appearance/article/ArticleStyleFragment.kt b/app/src/main/java/com/skyd/anivu/ui/fragment/settings/appearance/article/ArticleStyleFragment.kt index 7f284adc..65a71e35 100644 --- a/app/src/main/java/com/skyd/anivu/ui/fragment/settings/appearance/article/ArticleStyleFragment.kt +++ b/app/src/main/java/com/skyd/anivu/ui/fragment/settings/appearance/article/ArticleStyleFragment.kt @@ -7,6 +7,7 @@ import android.view.ViewGroup import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Refresh import androidx.compose.material.icons.outlined.Tonality import androidx.compose.material3.Scaffold import androidx.compose.material3.Text @@ -27,15 +28,20 @@ import com.skyd.anivu.base.BaseComposeFragment import com.skyd.anivu.model.preference.appearance.article.ArticleItemTonalElevationPreference import com.skyd.anivu.model.preference.appearance.article.ArticleListTonalElevationPreference import com.skyd.anivu.model.preference.appearance.article.ArticleTopBarTonalElevationPreference +import com.skyd.anivu.model.preference.appearance.article.ShowArticlePullRefreshPreference +import com.skyd.anivu.model.preference.appearance.article.ShowArticleTopBarRefreshPreference import com.skyd.anivu.model.preference.appearance.feed.TonalElevationPreferenceUtil import com.skyd.anivu.ui.component.AniVuTopBar import com.skyd.anivu.ui.component.AniVuTopBarStyle import com.skyd.anivu.ui.component.BaseSettingsItem import com.skyd.anivu.ui.component.CategorySettingsItem +import com.skyd.anivu.ui.component.SwitchSettingsItem import com.skyd.anivu.ui.fragment.settings.appearance.feed.TonalElevationDialog import com.skyd.anivu.ui.local.LocalArticleItemTonalElevation import com.skyd.anivu.ui.local.LocalArticleListTonalElevation import com.skyd.anivu.ui.local.LocalArticleTopBarTonalElevation +import com.skyd.anivu.ui.local.LocalShowArticlePullRefresh +import com.skyd.anivu.ui.local.LocalShowArticleTopBarRefresh import dagger.hilt.android.AndroidEntryPoint @@ -86,6 +92,21 @@ fun ArticleStyleScreen() { onClick = { openTopBarTonalElevationDialog = true } ) } + item { + SwitchSettingsItem( + imageVector = Icons.Outlined.Refresh, + text = stringResource(id = R.string.article_style_screen_top_bar_refresh), + description = stringResource(id = R.string.article_style_screen_top_bar_refresh_description), + checked = LocalShowArticleTopBarRefresh.current, + onCheckedChange = { + ShowArticleTopBarRefreshPreference.put( + context = context, + scope = scope, + value = it, + ) + } + ) + } item { CategorySettingsItem(text = stringResource(id = R.string.article_style_screen_article_list_category)) } @@ -99,6 +120,21 @@ fun ArticleStyleScreen() { onClick = { openArticleListTonalElevationDialog = true } ) } + item { + SwitchSettingsItem( + imageVector = Icons.Outlined.Refresh, + text = stringResource(id = R.string.article_style_screen_pull_refresh), + description = stringResource(id = R.string.article_style_screen_pull_refresh_description), + checked = LocalShowArticlePullRefresh.current, + onCheckedChange = { + ShowArticlePullRefreshPreference.put( + context = context, + scope = scope, + value = it, + ) + } + ) + } item { CategorySettingsItem(text = stringResource(id = R.string.article_style_screen_article_item_category)) } diff --git a/app/src/main/java/com/skyd/anivu/ui/fragment/settings/data/importexport/exportopml/ExportOpmlScreen.kt b/app/src/main/java/com/skyd/anivu/ui/fragment/settings/data/importexport/exportopml/ExportOpmlScreen.kt index 2ba4fe59..769d7e87 100644 --- a/app/src/main/java/com/skyd/anivu/ui/fragment/settings/data/importexport/exportopml/ExportOpmlScreen.kt +++ b/app/src/main/java/com/skyd/anivu/ui/fragment/settings/data/importexport/exportopml/ExportOpmlScreen.kt @@ -41,6 +41,7 @@ import com.skyd.anivu.base.mvi.getDispatcher import com.skyd.anivu.ext.plus import com.skyd.anivu.ext.safeLaunch import com.skyd.anivu.ext.showSnackbar +import com.skyd.anivu.ext.showSnackbarWithLaunchedEffect import com.skyd.anivu.model.preference.data.OpmlExportDirPreference import com.skyd.anivu.ui.component.AniVuExtendedFloatingActionButton import com.skyd.anivu.ui.component.AniVuTopBar @@ -136,12 +137,12 @@ fun ExportOpmlScreen(viewModel: ExportOpmlViewModel = hiltViewModel()) { when (val event = uiEvent) { is ExportOpmlEvent.ExportOpmlResultEvent.Failed -> - snackbarHostState.showSnackbar(message = event.msg, scope = scope) + snackbarHostState.showSnackbarWithLaunchedEffect(message = event.msg, key1 = event) is ExportOpmlEvent.ExportOpmlResultEvent.Success -> - snackbarHostState.showSnackbar( + snackbarHostState.showSnackbarWithLaunchedEffect( message = stringResource(id = R.string.success_time_msg, event.time / 1000f), - scope = scope, + key1 = event, ) null -> Unit diff --git a/app/src/main/java/com/skyd/anivu/ui/local/LocalValue.kt b/app/src/main/java/com/skyd/anivu/ui/local/LocalValue.kt index f6a7acef..ecd7d9bb 100644 --- a/app/src/main/java/com/skyd/anivu/ui/local/LocalValue.kt +++ b/app/src/main/java/com/skyd/anivu/ui/local/LocalValue.kt @@ -12,6 +12,8 @@ import com.skyd.anivu.model.preference.appearance.ThemePreference import com.skyd.anivu.model.preference.appearance.article.ArticleItemTonalElevationPreference import com.skyd.anivu.model.preference.appearance.article.ArticleListTonalElevationPreference import com.skyd.anivu.model.preference.appearance.article.ArticleTopBarTonalElevationPreference +import com.skyd.anivu.model.preference.appearance.article.ShowArticlePullRefreshPreference +import com.skyd.anivu.model.preference.appearance.article.ShowArticleTopBarRefreshPreference import com.skyd.anivu.model.preference.appearance.feed.FeedGroupExpandPreference import com.skyd.anivu.model.preference.appearance.feed.FeedListTonalElevationPreference import com.skyd.anivu.model.preference.appearance.feed.FeedTopBarTonalElevationPreference @@ -59,6 +61,9 @@ val LocalSearchListTonalElevation = compositionLocalOf { SearchListTonalElevationPreference.default } val LocalSearchTopBarTonalElevation = compositionLocalOf { SearchTopBarTonalElevationPreference.default } +val LocalShowArticleTopBarRefresh = + compositionLocalOf { ShowArticleTopBarRefreshPreference.default } +val LocalShowArticlePullRefresh = compositionLocalOf { ShowArticlePullRefreshPreference.default } // Update val LocalIgnoreUpdateVersion = compositionLocalOf { IgnoreUpdateVersionPreference.default } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 39d13398..a1f789f9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -275,6 +275,15 @@ Search screen Top bar Search list + Read all + Top bar refresh + Pull refresh + Enable pull to refresh + Show Refresh button on top bar + + Read %d item + Read %d items + Imported %d item, takes %.2f seconds Imported %d items, takes %.2f seconds