From d721f36f6aab53f574d0a5ac31d4ff90374f2969 Mon Sep 17 00:00:00 2001 From: junkfood <69683722+JunkFood02@users.noreply.github.com> Date: Wed, 7 Feb 2024 18:50:00 +0800 Subject: [PATCH] feat(ui): configure swipe gestures --- .../infrastructure/preference/Preference.kt | 2 + .../infrastructure/preference/Settings.kt | 6 + .../preference/SwipeActionPreference.kt | 100 +++++++++ .../java/me/ash/reader/ui/ext/DataStoreExt.kt | 12 + .../reader/ui/page/home/flow/ArticleItem.kt | 206 +++++++++++++----- .../ash/reader/ui/page/home/flow/FlowPage.kt | 49 +++-- .../settings/interaction/InteractionPage.kt | 67 +++++- app/src/main/res/values/strings.xml | 5 + 8 files changed, 373 insertions(+), 74 deletions(-) create mode 100644 app/src/main/java/me/ash/reader/infrastructure/preference/SwipeActionPreference.kt diff --git a/app/src/main/java/me/ash/reader/infrastructure/preference/Preference.kt b/app/src/main/java/me/ash/reader/infrastructure/preference/Preference.kt index 371062920..8f062570e 100644 --- a/app/src/main/java/me/ash/reader/infrastructure/preference/Preference.kt +++ b/app/src/main/java/me/ash/reader/infrastructure/preference/Preference.kt @@ -76,6 +76,8 @@ fun Preferences.toSettings(): Settings { // Interaction initialPage = InitialPagePreference.fromPreferences(this), initialFilter = InitialFilterPreference.fromPreferences(this), + swipeStartAction = SwipeStartActionPreference.fromPreferences(this), + swipeEndAction = SwipeEndActionPreference.fromPreferences(this), openLink = OpenLinkPreference.fromPreferences(this), openLinkSpecificBrowser = OpenLinkSpecificBrowserPreference.fromPreferences(this), diff --git a/app/src/main/java/me/ash/reader/infrastructure/preference/Settings.kt b/app/src/main/java/me/ash/reader/infrastructure/preference/Settings.kt index 6dfeb5058..39adcf552 100644 --- a/app/src/main/java/me/ash/reader/infrastructure/preference/Settings.kt +++ b/app/src/main/java/me/ash/reader/infrastructure/preference/Settings.kt @@ -75,6 +75,8 @@ data class Settings( // Interaction val initialPage: InitialPagePreference = InitialPagePreference.default, val initialFilter: InitialFilterPreference = InitialFilterPreference.default, + val swipeStartAction: SwipeStartActionPreference = SwipeStartActionPreference.default, + val swipeEndAction: SwipeEndActionPreference = SwipeEndActionPreference.default, val openLink: OpenLinkPreference = OpenLinkPreference.default, val openLinkSpecificBrowser: OpenLinkSpecificBrowserPreference = OpenLinkSpecificBrowserPreference.default, @@ -177,6 +179,8 @@ val LocalReadingImageMaximize = val LocalInitialPage = compositionLocalOf { InitialPagePreference.default } val LocalInitialFilter = compositionLocalOf { InitialFilterPreference.default } +val LocalArticleListSwipeEndAction = compositionLocalOf { SwipeEndActionPreference.default } +val LocalArticleListSwipeStartAction = compositionLocalOf { SwipeStartActionPreference.default } val LocalOpenLink = compositionLocalOf { OpenLinkPreference.default } val LocalOpenLinkSpecificBrowser = @@ -263,6 +267,8 @@ fun SettingsProvider( // Interaction LocalInitialPage provides settings.initialPage, LocalInitialFilter provides settings.initialFilter, + LocalArticleListSwipeStartAction provides settings.swipeStartAction, + LocalArticleListSwipeEndAction provides settings.swipeEndAction, LocalOpenLink provides settings.openLink, LocalOpenLinkSpecificBrowser provides settings.openLinkSpecificBrowser, diff --git a/app/src/main/java/me/ash/reader/infrastructure/preference/SwipeActionPreference.kt b/app/src/main/java/me/ash/reader/infrastructure/preference/SwipeActionPreference.kt new file mode 100644 index 000000000..7274450af --- /dev/null +++ b/app/src/main/java/me/ash/reader/infrastructure/preference/SwipeActionPreference.kt @@ -0,0 +1,100 @@ +package me.ash.reader.infrastructure.preference + +import android.content.Context +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource +import androidx.datastore.preferences.core.Preferences +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import me.ash.reader.R +import me.ash.reader.ui.ext.DataStoreKeys +import me.ash.reader.ui.ext.dataStore +import me.ash.reader.ui.ext.put + +data object SwipeGestureActions { + const val None = 0 + const val ToggleRead = 1 + const val ToggleStarred = 2 +} + +sealed class SwipeEndActionPreference(val action: Int) : Preference() { + override fun put(context: Context, scope: CoroutineScope) { + scope.launch { + context.dataStore.put( + DataStoreKeys.SwipeEndAction, action + ) + } + } + + data object None : SwipeEndActionPreference(SwipeGestureActions.None) + data object ToggleRead : SwipeEndActionPreference(SwipeGestureActions.ToggleRead) + data object ToggleStarred : + SwipeEndActionPreference(SwipeGestureActions.ToggleStarred) + + val desc: String + @Composable get() = when (this) { + None -> stringResource(id = R.string.none) + ToggleRead -> stringResource(id = R.string.toggle_read) + ToggleStarred -> stringResource(id = R.string.toggle_starred) + } + + companion object { + val default: SwipeEndActionPreference = ToggleRead + val values = listOf( + None, + ToggleRead, + ToggleStarred + ) + + fun fromPreferences(preferences: Preferences): SwipeEndActionPreference { + return when (preferences[DataStoreKeys.SwipeEndAction.key] + ?: SwipeGestureActions.ToggleStarred) { + SwipeGestureActions.None -> None + SwipeGestureActions.ToggleRead -> ToggleRead + SwipeGestureActions.ToggleStarred -> ToggleStarred + else -> default + } + } + } +} + +sealed class SwipeStartActionPreference(val action: Int) : Preference() { + override fun put(context: Context, scope: CoroutineScope) { + scope.launch { + context.dataStore.put( + DataStoreKeys.SwipeStartAction, action + ) + } + } + + data object None : SwipeStartActionPreference(SwipeGestureActions.None) + data object ToggleRead : SwipeStartActionPreference(SwipeGestureActions.ToggleRead) + data object ToggleStarred : + SwipeStartActionPreference(SwipeGestureActions.ToggleStarred) + + val desc: String + @Composable get() = when (this) { + None -> stringResource(id = R.string.none) + ToggleRead -> stringResource(id = R.string.toggle_read) + ToggleStarred -> stringResource(id = R.string.toggle_starred) + } + + companion object { + val default: SwipeStartActionPreference = ToggleStarred + val values = listOf( + None, + ToggleRead, + ToggleStarred + ) + + fun fromPreferences(preferences: Preferences): SwipeStartActionPreference { + return when (preferences[DataStoreKeys.SwipeStartAction.key] + ?: SwipeGestureActions.ToggleStarred) { + SwipeGestureActions.None -> None + SwipeGestureActions.ToggleRead -> ToggleRead + SwipeGestureActions.ToggleStarred -> ToggleStarred + else -> default + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/ui/ext/DataStoreExt.kt b/app/src/main/java/me/ash/reader/ui/ext/DataStoreExt.kt index 1b69ee433..6a5ceb248 100644 --- a/app/src/main/java/me/ash/reader/ui/ext/DataStoreExt.kt +++ b/app/src/main/java/me/ash/reader/ui/ext/DataStoreExt.kt @@ -404,6 +404,18 @@ sealed class DataStoreKeys { get() = intPreferencesKey("initialFilter") } + data object SwipeStartAction : DataStoreKeys() { + + override val key: Preferences.Key + get() = intPreferencesKey("swipeStartAction") + } + + data object SwipeEndAction : DataStoreKeys() { + + override val key: Preferences.Key + get() = intPreferencesKey("swipeEndAction") + } + object OpenLink : DataStoreKeys() { override val key: Preferences.Key diff --git a/app/src/main/java/me/ash/reader/ui/page/home/flow/ArticleItem.kt b/app/src/main/java/me/ash/reader/ui/page/home/flow/ArticleItem.kt index a19a546e5..3e23c6159 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/flow/ArticleItem.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/flow/ArticleItem.kt @@ -1,6 +1,8 @@ package me.ash.reader.ui.page.home.flow import android.view.HapticFeedbackConstants +import androidx.compose.animation.Animatable +import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.AnimationSpec import androidx.compose.animation.core.Spring import androidx.compose.animation.core.spring @@ -10,6 +12,7 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -18,7 +21,6 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Circle -import androidx.compose.material.icons.outlined.Star import androidx.compose.material.icons.outlined.StarOutline import androidx.compose.material.icons.rounded.CheckCircleOutline import androidx.compose.material.icons.rounded.Star @@ -28,11 +30,16 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalView @@ -44,13 +51,18 @@ import coil.size.Precision import coil.size.Scale import me.ash.reader.R import me.ash.reader.domain.model.article.ArticleWithFeed +import me.ash.reader.domain.model.constant.ElevationTokens import me.ash.reader.infrastructure.preference.FlowArticleReadIndicatorPreference +import me.ash.reader.infrastructure.preference.LocalArticleListSwipeEndAction +import me.ash.reader.infrastructure.preference.LocalArticleListSwipeStartAction import me.ash.reader.infrastructure.preference.LocalFlowArticleListDesc import me.ash.reader.infrastructure.preference.LocalFlowArticleListFeedIcon import me.ash.reader.infrastructure.preference.LocalFlowArticleListFeedName import me.ash.reader.infrastructure.preference.LocalFlowArticleListImage import me.ash.reader.infrastructure.preference.LocalFlowArticleListReadIndicator import me.ash.reader.infrastructure.preference.LocalFlowArticleListTime +import me.ash.reader.infrastructure.preference.SwipeEndActionPreference +import me.ash.reader.infrastructure.preference.SwipeStartActionPreference import me.ash.reader.ui.component.FeedIcon import me.ash.reader.ui.component.base.RYAsyncImage import me.ash.reader.ui.component.base.SIZE_1000 @@ -76,19 +88,17 @@ fun ArticleItem( .clip(Shape20) .clickable { onClick(articleWithFeed) } .padding(horizontal = 12.dp, vertical = 12.dp) - .alpha( - articleWithFeed.article.run { - when (articleListReadIndicator) { - FlowArticleReadIndicatorPreference.AllRead -> { - if (isUnread) 1f else 0.5f - } - - FlowArticleReadIndicatorPreference.ExcludingStarred -> { - if (isUnread || isStarred) 1f else 0.5f - } + .alpha(articleWithFeed.article.run { + when (articleListReadIndicator) { + FlowArticleReadIndicatorPreference.AllRead -> { + if (isUnread) 1f else 0.5f + } + + FlowArticleReadIndicatorPreference.ExcludingStarred -> { + if (isUnread || isStarred) 1f else 0.5f } } - ), + }), ) { // Top Row( @@ -235,8 +245,7 @@ fun SwipeableArticleItem( val velocityThreshold: () -> Float = { Float.POSITIVE_INFINITY } val animationSpec: AnimationSpec = spring(stiffness = Spring.StiffnessMediumLow) val swipeState = rememberSaveable( - articleWithFeed, - saver = SwipeToDismissBoxState.Saver( + articleWithFeed, saver = SwipeToDismissBoxState.Saver( confirmValueChange = confirmValueChange, density = density, animationSpec = animationSpec, @@ -255,9 +264,13 @@ fun SwipeableArticleItem( } // val swipeState = rememberSwipeToDismissBoxState(positionalThreshold =, confirmValueChange =) val view = LocalView.current + var isActive by remember(articleWithFeed) { mutableStateOf(false) } LaunchedEffect(swipeState.progress > PositionalThresholdFraction) { if (swipeState.progress > PositionalThresholdFraction && swipeState.targetValue != SwipeToDismissBoxValue.Settled) { + isActive = true view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP) + } else { + isActive = false } } @@ -274,49 +287,12 @@ fun SwipeableArticleItem( enabled = !isScrollInProgress(), /*** create dismiss alert background box */ backgroundContent = { - if (swipeState.dismissDirection == SwipeToDismissBoxValue.StartToEnd) { - Box( - modifier = Modifier - .fillMaxSize() - .padding(24.dp) - ) { - Column(modifier = Modifier.align(Alignment.CenterStart)) { - Icon( - imageVector = if (articleWithFeed.article.isUnread) Icons.Rounded.CheckCircleOutline else Icons.Outlined.Circle, - contentDescription = null, - tint = MaterialTheme.colorScheme.tertiary, - modifier = Modifier.align(Alignment.CenterHorizontally) - ) - Text( - text = stringResource(if (articleWithFeed.article.isUnread) R.string.mark_as_read else R.string.mark_as_unread), - textAlign = TextAlign.Center, - color = MaterialTheme.colorScheme.tertiary, - style = MaterialTheme.typography.labelLarge, - ) - } - } - } else if (swipeState.dismissDirection == SwipeToDismissBoxValue.EndToStart) { - Box( - modifier = Modifier - .fillMaxSize() - .padding(24.dp) - ) { - Column(modifier = Modifier.align(Alignment.CenterEnd)) { - Icon( - imageVector = if (articleWithFeed.article.isStarred) Icons.Rounded.Star else Icons.Outlined.StarOutline, - contentDescription = null, - tint = MaterialTheme.colorScheme.tertiary, - modifier = Modifier.align(Alignment.CenterHorizontally) - ) - Text( - text = stringResource(if (articleWithFeed.article.isStarred) R.string.mark_as_unstar else R.string.mark_as_starred), - textAlign = TextAlign.Center, - color = MaterialTheme.colorScheme.tertiary, - style = MaterialTheme.typography.labelLarge, - ) - } - } - } + SwipeToDismissBoxBackgroundContent( + direction = swipeState.dismissDirection, + isActive = isActive, + isStarred = articleWithFeed.article.isStarred, + isRead = !articleWithFeed.article.isUnread + ) }, /**** Dismiss Content */ content = { @@ -337,3 +313,119 @@ fun SwipeableArticleItem( enableDismissFromStartToEnd = onSwipeStartToEnd != null ) } + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun RowScope.SwipeToDismissBoxBackgroundContent( + direction: SwipeToDismissBoxValue, + isActive: Boolean, + isStarred: Boolean, + isRead: Boolean, +) { + val containerColor = MaterialTheme.colorScheme.surface + val containerColorElevated = MaterialTheme.colorScheme.tertiaryContainer + val backgroundColor = remember { Animatable(containerColor) } + + LaunchedEffect(isActive) { + backgroundColor.animateTo( + if (isActive) { + containerColorElevated + } else { + containerColor + } + ) + } + val alignment = when (direction) { + SwipeToDismissBoxValue.StartToEnd -> Alignment.CenterStart + SwipeToDismissBoxValue.EndToStart -> Alignment.CenterEnd + SwipeToDismissBoxValue.Settled -> Alignment.Center + } + val swipeToStartAction = LocalArticleListSwipeStartAction.current + val swipeToEndAction = LocalArticleListSwipeEndAction.current + + val starImageVector = + remember(isStarred) { if (isStarred) Icons.Outlined.StarOutline else Icons.Rounded.Star } + + val readImageVector = + remember(isRead) { if (isRead) Icons.Outlined.Circle else Icons.Rounded.CheckCircleOutline } + + val starText = + stringResource(if (isStarred) R.string.mark_as_unstar else R.string.mark_as_starred) + + val readText = + stringResource(if (isRead) R.string.mark_as_unread else R.string.mark_as_read) + + val imageVector = remember(direction) { + when (direction) { + SwipeToDismissBoxValue.StartToEnd -> { + + when (swipeToEndAction) { + SwipeEndActionPreference.None -> null + SwipeEndActionPreference.ToggleRead -> readImageVector + SwipeEndActionPreference.ToggleStarred -> starImageVector + } + } + + SwipeToDismissBoxValue.EndToStart -> { + when (swipeToStartAction) { + SwipeStartActionPreference.None -> null + SwipeStartActionPreference.ToggleRead -> readImageVector + SwipeStartActionPreference.ToggleStarred -> starImageVector + } + } + + SwipeToDismissBoxValue.Settled -> null + } + } + + val text = remember(direction) { + when (direction) { + SwipeToDismissBoxValue.StartToEnd -> { + when (swipeToEndAction) { + SwipeEndActionPreference.None -> null + SwipeEndActionPreference.ToggleRead -> readText + SwipeEndActionPreference.ToggleStarred -> starText + } + } + + SwipeToDismissBoxValue.EndToStart -> { + when (swipeToStartAction) { + SwipeStartActionPreference.None -> null + SwipeStartActionPreference.ToggleRead -> readText + SwipeStartActionPreference.ToggleStarred -> starText + } + } + + SwipeToDismissBoxValue.Settled -> null + } + } + + + Box( + modifier = Modifier + .fillMaxSize() + .drawBehind { drawRect(backgroundColor.value) } + .padding(24.dp) + ) { + Column(modifier = Modifier.align(alignment = alignment)) { + imageVector?.let { + Icon( + imageVector = it, + contentDescription = null, + tint = MaterialTheme.colorScheme.tertiary, + modifier = Modifier.align(Alignment.CenterHorizontally) + ) + } + text?.let { + Text( + text = it, + textAlign = TextAlign.Center, + color = MaterialTheme.colorScheme.tertiary, + style = MaterialTheme.typography.labelLarge, + ) + } + } + } + + +} \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/ui/page/home/flow/FlowPage.kt b/app/src/main/java/me/ash/reader/ui/page/home/flow/FlowPage.kt index 69cb437bf..a1ecb36b4 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/flow/FlowPage.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/flow/FlowPage.kt @@ -23,6 +23,7 @@ import androidx.work.WorkInfo import kotlinx.coroutines.delay import kotlinx.coroutines.launch import me.ash.reader.R +import me.ash.reader.domain.model.article.ArticleWithFeed import me.ash.reader.domain.model.general.Filter import me.ash.reader.domain.model.general.MarkAsReadConditions import me.ash.reader.infrastructure.preference.* @@ -51,6 +52,8 @@ fun FlowPage( val filterBarFilled = LocalFlowFilterBarFilled.current val filterBarPadding = LocalFlowFilterBarPadding.current val filterBarTonalElevation = LocalFlowFilterBarTonalElevation.current + val swipeToStartAction = LocalArticleListSwipeStartAction.current + val swipeToEndAction = LocalArticleListSwipeEndAction.current val homeUiState = homeViewModel.homeUiState.collectAsStateValue() val flowUiState = flowViewModel.flowUiState.collectAsStateValue() @@ -70,6 +73,35 @@ fun FlowPage( it?.let { isSyncing = it.any { it.state == WorkInfo.State.RUNNING } } } + val onToggleStarred: State<(ArticleWithFeed) -> Unit> = rememberUpdatedState { + flowViewModel.updateStarredStatus( + articleId = it.article.id, + isStarred = !it.article.isStarred + ) + } + + val onToggleRead: State<(ArticleWithFeed) -> Unit> = rememberUpdatedState { + flowViewModel.updateReadStatus( + groupId = null, + feedId = null, + articleId = it.article.id, + conditions = MarkAsReadConditions.All, + isUnread = !it.article.isUnread + ) + } + + val onSwipeEndToStart = when (swipeToStartAction) { + SwipeStartActionPreference.None -> null + SwipeStartActionPreference.ToggleRead -> onToggleRead.value + SwipeStartActionPreference.ToggleStarred -> onToggleStarred.value + } + + val onSwipeStartToEnd = when (swipeToEndAction) { + SwipeEndActionPreference.None -> null + SwipeEndActionPreference.ToggleRead -> onToggleRead.value + SwipeEndActionPreference.ToggleStarred -> onToggleStarred.value + } + LaunchedEffect(onSearch) { snapshotFlow { onSearch }.collect { if (it) { @@ -245,21 +277,8 @@ fun FlowPage( launchSingleTop = true } }, - onSwipeStartToEnd = { - flowViewModel.updateReadStatus( - groupId = null, - feedId = null, - articleId = it.article.id, - conditions = MarkAsReadConditions.All, - isUnread = !it.article.isUnread - ) - }, - onSwipeEndToStart = { - flowViewModel.updateStarredStatus( - articleId = it.article.id, - isStarred = !it.article.isStarred - ) - } + onSwipeStartToEnd = onSwipeStartToEnd, + onSwipeEndToStart = onSwipeEndToStart ) item { Spacer(modifier = Modifier.height(128.dp)) diff --git a/app/src/main/java/me/ash/reader/ui/page/settings/interaction/InteractionPage.kt b/app/src/main/java/me/ash/reader/ui/page/settings/interaction/InteractionPage.kt index 2be4887d0..991669e34 100644 --- a/app/src/main/java/me/ash/reader/ui/page/settings/interaction/InteractionPage.kt +++ b/app/src/main/java/me/ash/reader/ui/page/settings/interaction/InteractionPage.kt @@ -9,6 +9,8 @@ import androidx.compose.foundation.layout.windowInsetsBottomHeight import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.ArrowBack +import androidx.compose.material.icons.rounded.SwipeLeft +import androidx.compose.material.icons.rounded.SwipeRight import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -24,11 +26,15 @@ import androidx.navigation.NavHostController import me.ash.reader.R import me.ash.reader.infrastructure.preference.InitialFilterPreference import me.ash.reader.infrastructure.preference.InitialPagePreference +import me.ash.reader.infrastructure.preference.LocalArticleListSwipeEndAction +import me.ash.reader.infrastructure.preference.LocalArticleListSwipeStartAction import me.ash.reader.infrastructure.preference.LocalInitialFilter import me.ash.reader.infrastructure.preference.LocalInitialPage import me.ash.reader.infrastructure.preference.LocalOpenLink import me.ash.reader.infrastructure.preference.LocalOpenLinkSpecificBrowser import me.ash.reader.infrastructure.preference.OpenLinkPreference +import me.ash.reader.infrastructure.preference.SwipeEndActionPreference +import me.ash.reader.infrastructure.preference.SwipeStartActionPreference import me.ash.reader.ui.component.base.DisplayText import me.ash.reader.ui.component.base.FeedbackIconButton import me.ash.reader.ui.component.base.RYScaffold @@ -46,6 +52,8 @@ fun InteractionPage( val context = LocalContext.current val initialPage = LocalInitialPage.current val initialFilter = LocalInitialFilter.current + val swipeToStartAction = LocalArticleListSwipeStartAction.current + val swipeToEndAction = LocalArticleListSwipeEndAction.current val openLink = LocalOpenLink.current val openLinkSpecificBrowser = LocalOpenLinkSpecificBrowser.current val scope = rememberCoroutineScope() @@ -54,6 +62,8 @@ fun InteractionPage( } var initialPageDialogVisible by remember { mutableStateOf(false) } var initialFilterDialogVisible by remember { mutableStateOf(false) } + var swipeStartDialogVisible by remember { mutableStateOf(false) } + var swipeEndDialogVisible by remember { mutableStateOf(false) } var openLinkDialogVisible by remember { mutableStateOf(false) } var openLinkSpecificBrowserDialogVisible by remember { mutableStateOf(false) } @@ -93,6 +103,27 @@ fun InteractionPage( initialFilterDialogVisible = true }, ) {} + Subtitle( + modifier = Modifier.padding(horizontal = 24.dp), + text = stringResource(R.string.article_list), + ) + + SettingItem( + title = stringResource(R.string.swipe_to_start), + desc = swipeToStartAction.desc, + onClick = { + swipeStartDialogVisible = true + }, + ) {} + + SettingItem( + title = stringResource(R.string.swipe_to_end), + desc = swipeToEndAction.desc, + onClick = { + swipeEndDialogVisible = true + }, + ) {} + Subtitle( modifier = Modifier.padding(horizontal = 24.dp), text = stringResource(R.string.external_links), @@ -154,6 +185,37 @@ fun InteractionPage( initialFilterDialogVisible = false } + RadioDialog( + visible = swipeStartDialogVisible, + title = stringResource(R.string.swipe_to_start), + options = SwipeStartActionPreference.values.map { + RadioDialogOption( + text = it.desc, + selected = it == swipeToStartAction, + ) { + it.put(context, scope) + } + }, + ) { + swipeStartDialogVisible = false + } + + RadioDialog( + visible = swipeEndDialogVisible, + title = stringResource(R.string.swipe_to_end), + options = SwipeEndActionPreference.values.map { + RadioDialogOption( + text = it.desc, + selected = it == swipeToEndAction, + ) { + it.put(context, scope) + } + }, + ) { + swipeEndDialogVisible = false + } + + RadioDialog( visible = openLinkDialogVisible, title = stringResource(R.string.initial_open_app), @@ -174,14 +236,15 @@ fun InteractionPage( } RadioDialog( - visible = openLinkSpecificBrowserDialogVisible , + visible = openLinkSpecificBrowserDialogVisible, title = stringResource(R.string.open_link_specific_browser), options = browserList.map { RadioDialogOption( text = it.loadLabel(context.packageManager).toString(), selected = it.activityInfo.packageName == openLinkSpecificBrowser.packageName, ) { - openLinkSpecificBrowser.copy(packageName = it.activityInfo.packageName).put(context, scope) + openLinkSpecificBrowser.copy(packageName = it.activityInfo.packageName) + .put(context, scope) } }, onDismissRequest = { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 90d1f5ac3..a473ce3de 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -414,4 +414,9 @@ submit a bug report on GitHub The app encountered an unexpected error and had to close.\n\nTo help us identify and fix this issue quickly, you can %1$s with the error stack trace below. Next article + Swipe left + Swipe right + None + Toggle read + Toggle starred