From a187d381499683c2a8c9a0c56ba96c8d06e37d7f Mon Sep 17 00:00:00 2001 From: junkfood <69683722+JunkFood02@users.noreply.github.com> Date: Tue, 20 Feb 2024 22:36:02 +0800 Subject: [PATCH] feat(ui): configuration for pull-to-load gesture --- .../infrastructure/preference/Preference.kt | 1 + .../PullToSwitchArticlePreference.kt | 29 ++++++++++++++++ .../infrastructure/preference/Settings.kt | 33 +++++++++++++------ .../java/me/ash/reader/ui/ext/DataStoreExt.kt | 7 +++- .../reader/ui/page/home/reading/PullToLoad.kt | 32 +++++++++++++++--- .../ui/page/home/reading/ReadingPage.kt | 30 ++++++++--------- .../color/reading/ReadingStylePage.kt | 7 ++++ .../settings/interaction/InteractionPage.kt | 16 +++++++-- app/src/main/res/values/strings.xml | 1 + 9 files changed, 122 insertions(+), 34 deletions(-) create mode 100644 app/src/main/java/me/ash/reader/infrastructure/preference/PullToSwitchArticlePreference.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 8f062570e..ad8225bf5 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 @@ -78,6 +78,7 @@ fun Preferences.toSettings(): Settings { initialFilter = InitialFilterPreference.fromPreferences(this), swipeStartAction = SwipeStartActionPreference.fromPreferences(this), swipeEndAction = SwipeEndActionPreference.fromPreferences(this), + pullToSwitchArticle = PullToSwitchArticlePreference.fromPreference(this), openLink = OpenLinkPreference.fromPreferences(this), openLinkSpecificBrowser = OpenLinkSpecificBrowserPreference.fromPreferences(this), diff --git a/app/src/main/java/me/ash/reader/infrastructure/preference/PullToSwitchArticlePreference.kt b/app/src/main/java/me/ash/reader/infrastructure/preference/PullToSwitchArticlePreference.kt new file mode 100644 index 000000000..fac614597 --- /dev/null +++ b/app/src/main/java/me/ash/reader/infrastructure/preference/PullToSwitchArticlePreference.kt @@ -0,0 +1,29 @@ +package me.ash.reader.infrastructure.preference + +import android.content.Context +import androidx.datastore.preferences.core.Preferences +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import me.ash.reader.ui.ext.DataStoreKeys +import me.ash.reader.ui.ext.dataStore +import me.ash.reader.ui.ext.put + +class PullToSwitchArticlePreference(val value: Boolean) : Preference() { + override fun put(context: Context, scope: CoroutineScope) { + scope.launch { + context.dataStore.put(DataStoreKeys.PullToSwitchArticle, value) + } + } + + fun toggle(context: Context, scope: CoroutineScope) = + PullToSwitchArticlePreference(!value).put(context, scope) + + companion object { + val default = PullToSwitchArticlePreference(true) + fun fromPreference(preference: Preferences): PullToSwitchArticlePreference { + return PullToSwitchArticlePreference( + preference[DataStoreKeys.PullToSwitchArticle.key] ?: return default + ) + } + } +} \ No newline at end of file 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 39adcf552..474bbd1a5 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 @@ -77,6 +77,7 @@ data class Settings( val initialFilter: InitialFilterPreference = InitialFilterPreference.default, val swipeStartAction: SwipeStartActionPreference = SwipeStartActionPreference.default, val swipeEndAction: SwipeEndActionPreference = SwipeEndActionPreference.default, + val pullToSwitchArticle: PullToSwitchArticlePreference = PullToSwitchArticlePreference.default, val openLink: OpenLinkPreference = OpenLinkPreference.default, val openLinkSpecificBrowser: OpenLinkSpecificBrowserPreference = OpenLinkSpecificBrowserPreference.default, @@ -148,30 +149,40 @@ val LocalFlowArticleListReadIndicator = compositionLocalOf { FlowArticleReadIndicatorPreference.default } // Reading page -val LocalReadingTheme = compositionLocalOf { ReadingThemePreference.default } -val LocalReadingDarkTheme = compositionLocalOf { ReadingDarkThemePreference.default } +val LocalReadingTheme = + compositionLocalOf { ReadingThemePreference.default } +val LocalReadingDarkTheme = + compositionLocalOf { ReadingDarkThemePreference.default } val LocalReadingPageTonalElevation = compositionLocalOf { ReadingPageTonalElevationPreference.default } val LocalReadingAutoHideToolbar = compositionLocalOf { ReadingAutoHideToolbarPreference.default } val LocalReadingTextFontSize = compositionLocalOf { ReadingTextFontSizePreference.default } val LocalReadingLetterSpacing = compositionLocalOf { ReadingLetterSpacingPreference.default } -val LocalReadingTextHorizontalPadding = compositionLocalOf { ReadingTextHorizontalPaddingPreference.default } -val LocalReadingTextAlign = compositionLocalOf { ReadingTextAlignPreference.default } -val LocalReadingTextBold = compositionLocalOf { ReadingTextBoldPreference.default } -val LocalReadingTitleAlign = compositionLocalOf { ReadingTitleAlignPreference.default } +val LocalReadingTextHorizontalPadding = + compositionLocalOf { ReadingTextHorizontalPaddingPreference.default } +val LocalReadingTextAlign = + compositionLocalOf { ReadingTextAlignPreference.default } +val LocalReadingTextBold = + compositionLocalOf { ReadingTextBoldPreference.default } +val LocalReadingTitleAlign = + compositionLocalOf { ReadingTitleAlignPreference.default } val LocalReadingSubheadAlign = compositionLocalOf { ReadingSubheadAlignPreference.default } -val LocalReadingFonts = compositionLocalOf { ReadingFontsPreference.default } -val LocalReadingTitleBold = compositionLocalOf { ReadingTitleBoldPreference.default } +val LocalReadingFonts = + compositionLocalOf { ReadingFontsPreference.default } +val LocalReadingTitleBold = + compositionLocalOf { ReadingTitleBoldPreference.default } val LocalReadingSubheadBold = compositionLocalOf { ReadingSubheadBoldPreference.default } val LocalReadingTitleUpperCase = compositionLocalOf { ReadingTitleUpperCasePreference.default } val LocalReadingSubheadUpperCase = compositionLocalOf { ReadingSubheadUpperCasePreference.default } -val LocalReadingImageHorizontalPadding = compositionLocalOf { ReadingImageHorizontalPaddingPreference.default } -val LocalReadingImageRoundedCorners = compositionLocalOf { ReadingImageRoundedCornersPreference.default } +val LocalReadingImageHorizontalPadding = + compositionLocalOf { ReadingImageHorizontalPaddingPreference.default } +val LocalReadingImageRoundedCorners = + compositionLocalOf { ReadingImageRoundedCornersPreference.default } val LocalReadingImageMaximize = compositionLocalOf { ReadingImageMaximizePreference.default } @@ -181,6 +192,7 @@ val LocalInitialFilter = compositionLocalOf { InitialFilterPreference.default } val LocalArticleListSwipeEndAction = compositionLocalOf { SwipeEndActionPreference.default } val LocalArticleListSwipeStartAction = compositionLocalOf { SwipeStartActionPreference.default } +val LocalPullToSwitchArticle = compositionLocalOf { PullToSwitchArticlePreference.default } val LocalOpenLink = compositionLocalOf { OpenLinkPreference.default } val LocalOpenLinkSpecificBrowser = @@ -269,6 +281,7 @@ fun SettingsProvider( LocalInitialFilter provides settings.initialFilter, LocalArticleListSwipeStartAction provides settings.swipeStartAction, LocalArticleListSwipeEndAction provides settings.swipeEndAction, + LocalPullToSwitchArticle provides settings.pullToSwitchArticle, LocalOpenLink provides settings.openLink, LocalOpenLinkSpecificBrowser provides settings.openLinkSpecificBrowser, 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 6a5ceb248..4d0264dd5 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 @@ -416,6 +416,11 @@ sealed class DataStoreKeys { get() = intPreferencesKey("swipeEndAction") } + data object PullToSwitchArticle : DataStoreKeys() { + override val key: Preferences.Key + get() = booleanPreferencesKey("pullToSwitchArticle") + } + object OpenLink : DataStoreKeys() { override val key: Preferences.Key @@ -425,7 +430,7 @@ sealed class DataStoreKeys { object OpenLinkAppSpecificBrowser : DataStoreKeys() { override val key: Preferences.Key - get() = stringPreferencesKey("openLppSpecificBrowser") + get() = stringPreferencesKey("openLppSpecificBrowser") } // Languages diff --git a/app/src/main/java/me/ash/reader/ui/page/home/reading/PullToLoad.kt b/app/src/main/java/me/ash/reader/ui/page/home/reading/PullToLoad.kt index f4283b564..7d9b75dc9 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/reading/PullToLoad.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/reading/PullToLoad.kt @@ -5,6 +5,7 @@ import androidx.compose.animation.core.FloatExponentialDecaySpec import androidx.compose.animation.core.animate import androidx.compose.animation.core.animateDecay import androidx.compose.foundation.MutatorMutex +import androidx.compose.foundation.layout.offset import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.runtime.Composable import androidx.compose.runtime.SideEffect @@ -28,6 +29,7 @@ import androidx.compose.ui.unit.dp import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.cancel import kotlinx.coroutines.launch +import me.ash.reader.ui.page.home.reading.PullToLoadDefaults.ContentOffsetMultiple import kotlin.math.abs import kotlin.math.sqrt @@ -45,18 +47,18 @@ private const val TAG = "PullRelease" * @param enabled If not enabled, all scroll delta and fling velocity will be ignored. * @param onScroll Used for detecting if the reader is scrolling down */ -class ReaderNestedScrollConnection( +private class ReaderNestedScrollConnection( private val enabled: Boolean, private val onPreScroll: (Float) -> Float, private val onPostScroll: (Float) -> Float, private val onRelease: (Float) -> Unit, - private val onScroll: (Float) -> Unit + private val onScroll: ((Float) -> Unit)? = null ) : NestedScrollConnection { override fun onPreScroll( available: Offset, source: NestedScrollSource ): Offset { - onScroll(available.y) + onScroll?.invoke(available.y) return when { !enabled || available.y == 0f -> Offset.Zero @@ -294,11 +296,31 @@ private fun Float.signOpposites(f: Float): Boolean = /** * Default parameter values for [rememberPullToLoadState]. */ -@ExperimentalMaterialApi object PullToLoadDefaults { /** * If the indicator is below this threshold offset when it is released, the load action * will be triggered. */ val LoadThreshold = 120.dp -} \ No newline at end of file + + const val ContentOffsetMultiple = 80 +} + +fun Modifier.pullToLoad( + state: PullToLoadState, + contentOffsetMultiple: Int = ContentOffsetMultiple, + onScroll: ((Float) -> Unit)? = null, + enabled: Boolean = true +): Modifier = + nestedScroll( + ReaderNestedScrollConnection( + enabled = enabled, + onPreScroll = state::onPullBack, + onPostScroll = state::onPull, + onRelease = state::onRelease, + onScroll = onScroll + ) + ).run { + if (enabled) offset(x = 0.dp, y = (state.offsetFraction * contentOffsetMultiple).dp) + else this + } diff --git a/app/src/main/java/me/ash/reader/ui/page/home/reading/ReadingPage.kt b/app/src/main/java/me/ash/reader/ui/page/home/reading/ReadingPage.kt index e1439b9c8..32e20cd69 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/reading/ReadingPage.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/reading/ReadingPage.kt @@ -7,7 +7,6 @@ import androidx.compose.foundation.LocalOverscrollConfiguration import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyListState import androidx.compose.material.ExperimentalMaterialApi @@ -24,13 +23,12 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.hapticfeedback.HapticFeedbackType -import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalHapticFeedback -import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavHostController import androidx.paging.compose.collectAsLazyPagingItems +import me.ash.reader.infrastructure.preference.LocalPullToSwitchArticle import me.ash.reader.infrastructure.preference.LocalReadingAutoHideToolbar import me.ash.reader.infrastructure.preference.LocalReadingPageTonalElevation import me.ash.reader.ui.ext.collectAsStateValue @@ -50,6 +48,7 @@ fun ReadingPage( readingViewModel: ReadingViewModel = hiltViewModel(), ) { val tonalElevation = LocalReadingPageTonalElevation.current + val isPullToSwitchArticleEnabled = LocalPullToSwitchArticle.current.value val readingUiState = readingViewModel.readingUiState.collectAsStateValue() val readerState = readingViewModel.readerStateStateFlow.collectAsStateValue() val homeUiState = homeViewModel.homeUiState.collectAsStateValue() @@ -165,26 +164,25 @@ fun ReadingPage( saver = LazyListState.Saver ) { LazyListState() } - CompositionLocalProvider(LocalOverscrollConfiguration provides null) { + CompositionLocalProvider( + LocalOverscrollConfiguration provides + if (isPullToSwitchArticleEnabled) null else LocalOverscrollConfiguration.current + ) { Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { Content( modifier = Modifier - .nestedScroll( - ReaderNestedScrollConnection( - enabled = true, - onPreScroll = state::onPullBack, - onPostScroll = state::onPull, - onRelease = state::onRelease, - onScroll = { f -> - if (abs(f) > 2f) - isReaderScrollingDown = f < 0f - }) - ) .padding(paddings) - .offset(x = 0.dp, y = (state.offsetFraction * 80).dp), + .pullToLoad( + state = state, + onScroll = { f -> + if (abs(f) > 2f) + isReaderScrollingDown = f < 0f + }, + enabled = isPullToSwitchArticleEnabled + ), content = content.text ?: "", feedName = feedName, title = title.toString(), diff --git a/app/src/main/java/me/ash/reader/ui/page/settings/color/reading/ReadingStylePage.kt b/app/src/main/java/me/ash/reader/ui/page/settings/color/reading/ReadingStylePage.kt index 662568dc0..951c65803 100644 --- a/app/src/main/java/me/ash/reader/ui/page/settings/color/reading/ReadingStylePage.kt +++ b/app/src/main/java/me/ash/reader/ui/page/settings/color/reading/ReadingStylePage.kt @@ -47,6 +47,8 @@ fun ReadingStylePage( val tonalElevation = LocalReadingPageTonalElevation.current val fonts = LocalReadingFonts.current val autoHideToolbar = LocalReadingAutoHideToolbar.current + val pullToSwitchArticle = LocalPullToSwitchArticle.current + var tonalElevationDialogVisible by remember { mutableStateOf(false) } var fontsDialogVisible by remember { mutableStateOf(false) } @@ -169,6 +171,11 @@ fun ReadingStylePage( enabled = false, onClick = {}, ) {} + SettingItem( + title = stringResource(id = R.string.pull_to_switch_article), + onClick = { pullToSwitchArticle.toggle(context, scope) }) { + RYSwitch(activated = pullToSwitchArticle.value) + } SettingItem( title = stringResource(R.string.tonal_elevation), desc = "${tonalElevation.value}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 991669e34..f00bd83cd 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,8 +9,6 @@ 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 @@ -32,12 +30,14 @@ 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.LocalPullToSwitchArticle 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 +import me.ash.reader.ui.component.base.RYSwitch import me.ash.reader.ui.component.base.RadioDialog import me.ash.reader.ui.component.base.RadioDialogOption import me.ash.reader.ui.component.base.Subtitle @@ -54,6 +54,7 @@ fun InteractionPage( val initialFilter = LocalInitialFilter.current val swipeToStartAction = LocalArticleListSwipeStartAction.current val swipeToEndAction = LocalArticleListSwipeEndAction.current + val pullToSwitchArticle = LocalPullToSwitchArticle.current val openLink = LocalOpenLink.current val openLinkSpecificBrowser = LocalOpenLinkSpecificBrowser.current val scope = rememberCoroutineScope() @@ -124,6 +125,17 @@ fun InteractionPage( }, ) {} + Subtitle( + modifier = Modifier.padding(horizontal = 24.dp), + text = stringResource(R.string.reading_page), + ) + + SettingItem( + title = stringResource(id = R.string.pull_to_switch_article), + onClick = { pullToSwitchArticle.toggle(context, scope) }) { + RYSwitch(activated = pullToSwitchArticle.value) + } + Subtitle( modifier = Modifier.padding(horizontal = 24.dp), text = stringResource(R.string.external_links), diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f66aa1b21..c7534643b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -420,4 +420,5 @@ Toggle read Toggle starred Export + Pull to switch article