From f21a0c9fd864d56fe42c1bff49a8b0564ab7d02c Mon Sep 17 00:00:00 2001 From: Ashinch Date: Sun, 10 Jul 2022 14:43:09 +0800 Subject: [PATCH] Add reading page style settings (#132) * Add reading page style settings * improved german language (#134) * Add Hindi translation Thanks augurer * Add auto hide toolbar and image maximize setting * Add tonal elevation setting in reading page * Add reading dark theme and previews * Add dynamic custom reading theme preview Co-authored-by: helloworldtest123 <36381315+helloworldtest123@users.noreply.github.com> --- README-de.md | 8 + .../model/preference/LanguagesPreference.kt | 8 +- .../data/model/preference/Preference.kt | 27 ++ .../ReadingAutoHideToolbarPreference.kt | 39 +++ .../preference/ReadingDarkThemePreference.kt | 72 ++++ .../preference/ReadingFontsPreference.kt | 65 ++++ ...ReadingImageHorizontalPaddingPreference.kt | 23 ++ .../ReadingImageMaximizePreference.kt | 39 +++ .../ReadingImageRoundedCornersPreference.kt | 23 ++ .../ReadingLetterSpacingPreference.kt | 23 ++ .../ReadingPageTonalElevationPreference.kt | 53 +++ .../ReadingSubheadAlignPreference.kt | 58 ++++ .../ReadingSubheadBoldPreference.kt | 42 +++ .../ReadingSubheadUpperCasePreference.kt | 42 +++ .../preference/ReadingTextAlignPreference.kt | 66 ++++ .../preference/ReadingTextBoldPreference.kt | 42 +++ .../ReadingTextFontSizePreference.kt | 23 ++ .../ReadingTextHorizontalPaddingPreference.kt | 23 ++ .../preference/ReadingThemePreference.kt | 127 +++++++ .../preference/ReadingTitleAlignPreference.kt | 61 ++++ .../preference/ReadingTitleBoldPreference.kt | 42 +++ .../ReadingTitleUpperCasePreference.kt | 42 +++ .../reader/data/model/preference/Settings.kt | 196 +++++++---- .../reader/ui/component/ReadingThemePrev.kt | 168 ++++++++++ .../reader/ui/component/base/RadioDialog.kt | 6 +- .../ui/component/reader/HtmlToComposable.kt | 78 +++-- .../ash/reader/ui/component/reader/Reader.kt | 4 +- .../ash/reader/ui/component/reader/Styles.kt | 159 +++++++-- .../java/me/ash/reader/ui/ext/DataStoreExt.kt | 120 +++++++ .../me/ash/reader/ui/page/common/HomeEntry.kt | 40 ++- .../me/ash/reader/ui/page/common/RouteName.kt | 6 + .../reader/ui/page/home/reading/BottomBar.kt | 10 +- .../reader/ui/page/home/reading/Content.kt | 5 +- .../ash/reader/ui/page/home/reading/Header.kt | 73 ---- .../reader/ui/page/home/reading/Metadata.kt | 100 ++++++ .../ui/page/home/reading/ReadingPage.kt | 12 +- .../ash/reader/ui/page/home/reading/TopBar.kt | 23 +- .../home/reading/drawer/FeedOptionDrawer.kt | 71 ++++ .../reader/ui/page/settings/SettingsPage.kt | 1 + .../page/settings/color/ColorAndStylePage.kt | 8 +- .../ui/page/settings/color/DarkThemePage.kt | 1 + .../color/feeds/FeedsPageStylePage.kt | 4 +- .../settings/color/flow/FlowPageStylePage.kt | 5 +- .../color/reading/ReadingDarkThemePage.kt | 72 ++++ .../color/reading/ReadingImagePage.kt | 158 +++++++++ .../color/reading/ReadingStylePage.kt | 259 +++++++++++++++ .../settings/color/reading/ReadingTextPage.kt | 202 ++++++++++++ .../color/reading/ReadingTitlePage.kt | 196 +++++++++++ .../color/reading/ReadingVideoPage.kt | 102 ++++++ .../color/reading/TitleAndTextPreview.kt | 71 ++++ .../settings/interaction/InteractionPage.kt | 1 + .../page/settings/languages/LanguagesPage.kt | 1 + .../java/me/ash/reader/ui/theme/Shapes.kt | 3 + .../ui/theme/palette/DynamicTonalPalette.kt | 45 +++ app/src/main/res/values-cs-rCZ/strings.xml | 33 +- app/src/main/res/values-de-rDE/strings.xml | 37 ++- app/src/main/res/values-fr-rFR/strings.xml | 33 +- app/src/main/res/values-hi-rIN/strings.xml | 311 ++++++++++++++++++ app/src/main/res/values-it/strings.xml | 33 +- app/src/main/res/values-zh-rCN/strings.xml | 37 ++- app/src/main/res/values/strings.xml | 34 +- .../metadata/android/de-DE/changelogs/12.txt | 17 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 59203 bytes 63 files changed, 3456 insertions(+), 227 deletions(-) create mode 100644 app/src/main/java/me/ash/reader/data/model/preference/ReadingAutoHideToolbarPreference.kt create mode 100644 app/src/main/java/me/ash/reader/data/model/preference/ReadingDarkThemePreference.kt create mode 100644 app/src/main/java/me/ash/reader/data/model/preference/ReadingFontsPreference.kt create mode 100644 app/src/main/java/me/ash/reader/data/model/preference/ReadingImageHorizontalPaddingPreference.kt create mode 100644 app/src/main/java/me/ash/reader/data/model/preference/ReadingImageMaximizePreference.kt create mode 100644 app/src/main/java/me/ash/reader/data/model/preference/ReadingImageRoundedCornersPreference.kt create mode 100644 app/src/main/java/me/ash/reader/data/model/preference/ReadingLetterSpacingPreference.kt create mode 100644 app/src/main/java/me/ash/reader/data/model/preference/ReadingPageTonalElevationPreference.kt create mode 100644 app/src/main/java/me/ash/reader/data/model/preference/ReadingSubheadAlignPreference.kt create mode 100644 app/src/main/java/me/ash/reader/data/model/preference/ReadingSubheadBoldPreference.kt create mode 100644 app/src/main/java/me/ash/reader/data/model/preference/ReadingSubheadUpperCasePreference.kt create mode 100644 app/src/main/java/me/ash/reader/data/model/preference/ReadingTextAlignPreference.kt create mode 100644 app/src/main/java/me/ash/reader/data/model/preference/ReadingTextBoldPreference.kt create mode 100644 app/src/main/java/me/ash/reader/data/model/preference/ReadingTextFontSizePreference.kt create mode 100644 app/src/main/java/me/ash/reader/data/model/preference/ReadingTextHorizontalPaddingPreference.kt create mode 100644 app/src/main/java/me/ash/reader/data/model/preference/ReadingThemePreference.kt create mode 100644 app/src/main/java/me/ash/reader/data/model/preference/ReadingTitleAlignPreference.kt create mode 100644 app/src/main/java/me/ash/reader/data/model/preference/ReadingTitleBoldPreference.kt create mode 100644 app/src/main/java/me/ash/reader/data/model/preference/ReadingTitleUpperCasePreference.kt create mode 100644 app/src/main/java/me/ash/reader/ui/component/ReadingThemePrev.kt delete mode 100644 app/src/main/java/me/ash/reader/ui/page/home/reading/Header.kt create mode 100644 app/src/main/java/me/ash/reader/ui/page/home/reading/Metadata.kt create mode 100644 app/src/main/java/me/ash/reader/ui/page/home/reading/drawer/FeedOptionDrawer.kt create mode 100644 app/src/main/java/me/ash/reader/ui/page/settings/color/reading/ReadingDarkThemePage.kt create mode 100644 app/src/main/java/me/ash/reader/ui/page/settings/color/reading/ReadingImagePage.kt create mode 100644 app/src/main/java/me/ash/reader/ui/page/settings/color/reading/ReadingStylePage.kt create mode 100644 app/src/main/java/me/ash/reader/ui/page/settings/color/reading/ReadingTextPage.kt create mode 100644 app/src/main/java/me/ash/reader/ui/page/settings/color/reading/ReadingTitlePage.kt create mode 100644 app/src/main/java/me/ash/reader/ui/page/settings/color/reading/ReadingVideoPage.kt create mode 100644 app/src/main/java/me/ash/reader/ui/page/settings/color/reading/TitleAndTextPreview.kt create mode 100644 app/src/main/res/values-hi-rIN/strings.xml create mode 100644 fastlane/metadata/android/de-DE/changelogs/12.txt create mode 100644 gradle/wrapper/gradle-wrapper.jar diff --git a/README-de.md b/README-de.md index a60df5b..cb34047 100644 --- a/README-de.md +++ b/README-de.md @@ -66,6 +66,14 @@ Nachfolgend sind die bisher erzielten Fortschritte und die Ziele aufgeführt, an > Bei den oben genannten Funktionen handelt es sich nur um vorläufige Implementierungen, und es können noch unbekannte Probleme auftreten. +## Herunterladen + +[Jetzt bei F-Droid](https://f-droid.org/packages/me.ash.reader/) + +oder hole die APK aus dem [GitHub Releasebereich](https://github.com/Ashinch/ReadYou/releases). + ## Build > Wenn Sie eine Vorschau der Read You App wollen, können Sie die **Vorschau-Version** der APK-Datei in [Telegram] (https://t.me/ReadYouApp) herunterladen. diff --git a/app/src/main/java/me/ash/reader/data/model/preference/LanguagesPreference.kt b/app/src/main/java/me/ash/reader/data/model/preference/LanguagesPreference.kt index 422caf9..eb757e5 100644 --- a/app/src/main/java/me/ash/reader/data/model/preference/LanguagesPreference.kt +++ b/app/src/main/java/me/ash/reader/data/model/preference/LanguagesPreference.kt @@ -20,6 +20,8 @@ sealed class LanguagesPreference(val value: Int) : Preference() { object Czech : LanguagesPreference(5) object Italian : LanguagesPreference(6) + object Hindi : LanguagesPreference(7) + override fun put(context: Context, scope: CoroutineScope) { scope.launch { context.dataStore.put( @@ -39,6 +41,7 @@ sealed class LanguagesPreference(val value: Int) : Preference() { French -> context.getString(R.string.french) Czech -> context.getString(R.string.czech) Italian -> context.getString(R.string.italian) + Hindi -> context.getString(R.string.hindi) } fun getLocale(): Locale = @@ -50,6 +53,7 @@ sealed class LanguagesPreference(val value: Int) : Preference() { French -> Locale("fr", "FR") Czech -> Locale("cs", "CZ") Italian -> Locale("it", "IT") + Hindi -> Locale("hi", "IN") } fun setLocale(context: Context) { @@ -74,7 +78,7 @@ sealed class LanguagesPreference(val value: Int) : Preference() { companion object { val default = UseDeviceLanguages - val values = listOf(UseDeviceLanguages, English, ChineseSimplified, German, French, Czech, Italian) + val values = listOf(UseDeviceLanguages, English, ChineseSimplified, German, French, Czech, Italian, Hindi) fun fromPreferences(preferences: Preferences): LanguagesPreference = when (preferences[DataStoreKeys.Languages.key]) { @@ -85,6 +89,7 @@ sealed class LanguagesPreference(val value: Int) : Preference() { 4 -> French 5 -> Czech 6 -> Italian + 7 -> Hindi else -> default } @@ -97,6 +102,7 @@ sealed class LanguagesPreference(val value: Int) : Preference() { 4 -> French 5 -> Czech 6 -> Italian + 7 -> Hindi else -> default } } diff --git a/app/src/main/java/me/ash/reader/data/model/preference/Preference.kt b/app/src/main/java/me/ash/reader/data/model/preference/Preference.kt index 9acac2a..9eaf6a3 100644 --- a/app/src/main/java/me/ash/reader/data/model/preference/Preference.kt +++ b/app/src/main/java/me/ash/reader/data/model/preference/Preference.kt @@ -11,6 +11,7 @@ sealed class Preference { fun Preferences.toSettings(): Settings { return Settings( + // Version newVersionNumber = NewVersionNumberPreference.fromPreferences(this), skipVersionNumber = SkipVersionNumberPreference.fromPreferences(this), newVersionPublishDate = NewVersionPublishDatePreference.fromPreferences(this), @@ -18,11 +19,13 @@ fun Preferences.toSettings(): Settings { newVersionSize = NewVersionSizePreference.fromPreferences(this), newVersionDownloadUrl = NewVersionDownloadUrlPreference.fromPreferences(this), + // Theme themeIndex = ThemeIndexPreference.fromPreferences(this), customPrimaryColor = CustomPrimaryColorPreference.fromPreferences(this), darkTheme = DarkThemePreference.fromPreferences(this), amoledDarkTheme = AmoledDarkThemePreference.fromPreferences(this), + // Feeds page feedsFilterBarStyle = FeedsFilterBarStylePreference.fromPreferences(this), feedsFilterBarFilled = FeedsFilterBarFilledPreference.fromPreferences(this), feedsFilterBarPadding = FeedsFilterBarPaddingPreference.fromPreferences(this), @@ -31,6 +34,7 @@ fun Preferences.toSettings(): Settings { feedsGroupListExpand = FeedsGroupListExpandPreference.fromPreferences(this), feedsGroupListTonalElevation = FeedsGroupListTonalElevationPreference.fromPreferences(this), + // Flow page flowFilterBarStyle = FlowFilterBarStylePreference.fromPreferences(this), flowFilterBarFilled = FlowFilterBarFilledPreference.fromPreferences(this), flowFilterBarPadding = FlowFilterBarPaddingPreference.fromPreferences(this), @@ -46,9 +50,32 @@ fun Preferences.toSettings(): Settings { ), flowArticleListTonalElevation = FlowArticleListTonalElevationPreference.fromPreferences(this), + // Reading page + readingTheme = ReadingThemePreference.fromPreferences(this), + readingDarkTheme = ReadingDarkThemePreference.fromPreferences(this), + readingPageTonalElevation = ReadingPageTonalElevationPreference.fromPreferences(this), + readingAutoHideToolbar = ReadingAutoHideToolbarPreference.fromPreferences(this), + readingTextFontSize = ReadingTextFontSizePreference.fromPreferences(this), + readingLetterSpacing = ReadingLetterSpacingPreference.fromPreferences(this), + readingTextHorizontalPadding = ReadingTextHorizontalPaddingPreference.fromPreferences(this), + readingTextAlign = ReadingTextAlignPreference.fromPreferences(this), + readingTextBold = ReadingTextBoldPreference.fromPreferences(this), + readingTitleAlign = ReadingTitleAlignPreference.fromPreferences(this), + readingSubheadAlign = ReadingSubheadAlignPreference.fromPreferences(this), + readingFonts = ReadingFontsPreference.fromPreferences(this), + readingTitleBold = ReadingTitleBoldPreference.fromPreferences(this), + readingSubheadBold = ReadingSubheadBoldPreference.fromPreferences(this), + readingTitleUpperCase = ReadingTitleUpperCasePreference.fromPreferences(this), + readingSubheadUpperCase = ReadingSubheadUpperCasePreference.fromPreferences(this), + readingImageHorizontalPadding = ReadingImageHorizontalPaddingPreference.fromPreferences(this), + readingImageRoundedCorners = ReadingImageRoundedCornersPreference.fromPreferences(this), + readingImageMaximize = ReadingImageMaximizePreference.fromPreferences(this), + + // Interaction initialPage = InitialPagePreference.fromPreferences(this), initialFilter = InitialFilterPreference.fromPreferences(this), + // Languages languages = LanguagesPreference.fromPreferences(this), ) } diff --git a/app/src/main/java/me/ash/reader/data/model/preference/ReadingAutoHideToolbarPreference.kt b/app/src/main/java/me/ash/reader/data/model/preference/ReadingAutoHideToolbarPreference.kt new file mode 100644 index 0000000..d918632 --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/model/preference/ReadingAutoHideToolbarPreference.kt @@ -0,0 +1,39 @@ +package me.ash.reader.data.model.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 + +sealed class ReadingAutoHideToolbarPreference(val value: Boolean) : Preference() { + object ON : ReadingAutoHideToolbarPreference(true) + object OFF : ReadingAutoHideToolbarPreference(false) + + override fun put(context: Context, scope: CoroutineScope) { + scope.launch { + context.dataStore.put(DataStoreKeys.ReadingAutoHideToolbar, value) + } + } + + companion object { + + val default = ON + val values = listOf(ON, OFF) + + fun fromPreferences(preferences: Preferences) = + when (preferences[DataStoreKeys.ReadingAutoHideToolbar.key]) { + true -> ON + false -> OFF + else -> default + } + } +} + +operator fun ReadingAutoHideToolbarPreference.not(): ReadingAutoHideToolbarPreference = + when (value) { + true -> ReadingAutoHideToolbarPreference.OFF + false -> ReadingAutoHideToolbarPreference.ON + } diff --git a/app/src/main/java/me/ash/reader/data/model/preference/ReadingDarkThemePreference.kt b/app/src/main/java/me/ash/reader/data/model/preference/ReadingDarkThemePreference.kt new file mode 100644 index 0000000..71dee4d --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/model/preference/ReadingDarkThemePreference.kt @@ -0,0 +1,72 @@ +package me.ash.reader.data.model.preference + +import android.content.Context +import androidx.compose.runtime.Composable +import androidx.compose.runtime.ReadOnlyComposable +import androidx.compose.runtime.Stable +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 + +sealed class ReadingDarkThemePreference(val value: Int) : Preference() { + object UseAppTheme : ReadingDarkThemePreference(0) + object ON : ReadingDarkThemePreference(1) + object OFF : ReadingDarkThemePreference(2) + + override fun put(context: Context, scope: CoroutineScope) { + scope.launch { + context.dataStore.put( + DataStoreKeys.ReadingDarkTheme, + value + ) + } + } + + fun toDesc(context: Context): String = + when (this) { + UseAppTheme -> context.getString(R.string.use_app_theme) + ON -> context.getString(R.string.on) + OFF -> context.getString(R.string.off) + } + + @Composable + @ReadOnlyComposable + fun isDarkTheme(): Boolean = when (this) { + UseAppTheme -> LocalDarkTheme.current.isDarkTheme() + ON -> true + OFF -> false + } + + companion object { + + val default = UseAppTheme + val values = listOf(UseAppTheme, ON, OFF) + + fun fromPreferences(preferences: Preferences) = + when (preferences[DataStoreKeys.ReadingDarkTheme.key]) { + 0 -> UseAppTheme + 1 -> ON + 2 -> OFF + else -> default + } + } +} + +@Stable +@Composable +@ReadOnlyComposable +operator fun ReadingDarkThemePreference.not(): ReadingDarkThemePreference = + when (this) { + ReadingDarkThemePreference.UseAppTheme -> if (LocalDarkTheme.current.isDarkTheme()) { + ReadingDarkThemePreference.OFF + } else { + ReadingDarkThemePreference.ON + } + + ReadingDarkThemePreference.ON -> ReadingDarkThemePreference.OFF + ReadingDarkThemePreference.OFF -> ReadingDarkThemePreference.ON + } diff --git a/app/src/main/java/me/ash/reader/data/model/preference/ReadingFontsPreference.kt b/app/src/main/java/me/ash/reader/data/model/preference/ReadingFontsPreference.kt new file mode 100644 index 0000000..b6b2dd1 --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/model/preference/ReadingFontsPreference.kt @@ -0,0 +1,65 @@ +package me.ash.reader.data.model.preference + +import android.content.Context +import androidx.compose.ui.text.font.FontFamily +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 +import me.ash.reader.ui.theme.googleSansDisplay +import me.ash.reader.ui.theme.googleSansText + +sealed class ReadingFontsPreference(val value: Int) : Preference() { + object GoogleSans : ReadingFontsPreference(0) + object Serif : ReadingFontsPreference(1) + object SansSerif : ReadingFontsPreference(2) + object Monospace : ReadingFontsPreference(3) + object Cursive : ReadingFontsPreference(4) + object External : ReadingFontsPreference(5) + + override fun put(context: Context, scope: CoroutineScope) { + scope.launch { + context.dataStore.put(DataStoreKeys.ReadingFonts, value) + } + } + + fun toDesc(context: Context): String = + when (this) { + GoogleSans -> "Google Sans" + Serif -> "Serif" + SansSerif -> "Sans-Serif" + Monospace -> "Monospace" + Cursive -> "Cursive" + External -> context.getString(R.string.external_fonts) + } + + fun asFontFamily(isDisplay: Boolean = false): FontFamily = + when (this) { + GoogleSans -> if (isDisplay) googleSansDisplay else googleSansText + Serif -> FontFamily.Serif + SansSerif -> FontFamily.SansSerif + Monospace -> FontFamily.Monospace + Cursive -> FontFamily.Cursive + External -> FontFamily.Default + } + + companion object { + + val default = GoogleSans + val values = listOf(GoogleSans, Serif, SansSerif, Monospace, Cursive, External) + + fun fromPreferences(preferences: Preferences): ReadingFontsPreference = + when (preferences[DataStoreKeys.ReadingFonts.key]) { + 0 -> GoogleSans + 1 -> Serif + 2 -> SansSerif + 3 -> Monospace + 4 -> Cursive + 5 -> External + else -> default + } + } +} diff --git a/app/src/main/java/me/ash/reader/data/model/preference/ReadingImageHorizontalPaddingPreference.kt b/app/src/main/java/me/ash/reader/data/model/preference/ReadingImageHorizontalPaddingPreference.kt new file mode 100644 index 0000000..40eeaef --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/model/preference/ReadingImageHorizontalPaddingPreference.kt @@ -0,0 +1,23 @@ +package me.ash.reader.data.model.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 + +object ReadingImageHorizontalPaddingPreference { + + const val default = 24 + + fun put(context: Context, scope: CoroutineScope, value: Int) { + scope.launch { + context.dataStore.put(DataStoreKeys.ReadingImageHorizontalPadding, value) + } + } + + fun fromPreferences(preferences: Preferences) = + preferences[DataStoreKeys.ReadingImageHorizontalPadding.key] ?: default +} diff --git a/app/src/main/java/me/ash/reader/data/model/preference/ReadingImageMaximizePreference.kt b/app/src/main/java/me/ash/reader/data/model/preference/ReadingImageMaximizePreference.kt new file mode 100644 index 0000000..2a37938 --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/model/preference/ReadingImageMaximizePreference.kt @@ -0,0 +1,39 @@ +package me.ash.reader.data.model.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 + +sealed class ReadingImageMaximizePreference(val value: Boolean) : Preference() { + object ON : ReadingImageMaximizePreference(true) + object OFF : ReadingImageMaximizePreference(false) + + override fun put(context: Context, scope: CoroutineScope) { + scope.launch { + context.dataStore.put(DataStoreKeys.ReadingImageMaximize, value) + } + } + + companion object { + + val default = ON + val values = listOf(ON, OFF) + + fun fromPreferences(preferences: Preferences) = + when (preferences[DataStoreKeys.ReadingImageMaximize.key]) { + true -> ON + false -> OFF + else -> default + } + } +} + +operator fun ReadingImageMaximizePreference.not(): ReadingImageMaximizePreference = + when (value) { + true -> ReadingImageMaximizePreference.OFF + false -> ReadingImageMaximizePreference.ON + } diff --git a/app/src/main/java/me/ash/reader/data/model/preference/ReadingImageRoundedCornersPreference.kt b/app/src/main/java/me/ash/reader/data/model/preference/ReadingImageRoundedCornersPreference.kt new file mode 100644 index 0000000..0b2de79 --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/model/preference/ReadingImageRoundedCornersPreference.kt @@ -0,0 +1,23 @@ +package me.ash.reader.data.model.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 + +object ReadingImageRoundedCornersPreference { + + const val default = 32 + + fun put(context: Context, scope: CoroutineScope, value: Int) { + scope.launch { + context.dataStore.put(DataStoreKeys.ReadingImageRoundedCorners, value) + } + } + + fun fromPreferences(preferences: Preferences) = + preferences[DataStoreKeys.ReadingImageRoundedCorners.key] ?: default +} diff --git a/app/src/main/java/me/ash/reader/data/model/preference/ReadingLetterSpacingPreference.kt b/app/src/main/java/me/ash/reader/data/model/preference/ReadingLetterSpacingPreference.kt new file mode 100644 index 0000000..a81bb3e --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/model/preference/ReadingLetterSpacingPreference.kt @@ -0,0 +1,23 @@ +package me.ash.reader.data.model.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 + +object ReadingLetterSpacingPreference { + + const val default = 0.5 + + fun put(context: Context, scope: CoroutineScope, value: Double) { + scope.launch { + context.dataStore.put(DataStoreKeys.ReadingLetterSpacing, value) + } + } + + fun fromPreferences(preferences: Preferences) = + preferences[DataStoreKeys.ReadingLetterSpacing.key] ?: default +} diff --git a/app/src/main/java/me/ash/reader/data/model/preference/ReadingPageTonalElevationPreference.kt b/app/src/main/java/me/ash/reader/data/model/preference/ReadingPageTonalElevationPreference.kt new file mode 100644 index 0000000..b288d4c --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/model/preference/ReadingPageTonalElevationPreference.kt @@ -0,0 +1,53 @@ +package me.ash.reader.data.model.preference + +import android.content.Context +import androidx.datastore.preferences.core.Preferences +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import me.ash.reader.data.constant.ElevationTokens +import me.ash.reader.ui.ext.DataStoreKeys +import me.ash.reader.ui.ext.dataStore +import me.ash.reader.ui.ext.put + +sealed class ReadingPageTonalElevationPreference(val value: Int) : Preference() { + object Level0 : ReadingPageTonalElevationPreference(ElevationTokens.Level0) + object Level1 : ReadingPageTonalElevationPreference(ElevationTokens.Level1) + object Level2 : ReadingPageTonalElevationPreference(ElevationTokens.Level2) + object Level3 : ReadingPageTonalElevationPreference(ElevationTokens.Level3) + object Level4 : ReadingPageTonalElevationPreference(ElevationTokens.Level4) + object Level5 : ReadingPageTonalElevationPreference(ElevationTokens.Level5) + + override fun put(context: Context, scope: CoroutineScope) { + scope.launch { + context.dataStore.put(DataStoreKeys.ReadingPageTonalElevation, value) + } + } + + fun toDesc(context: Context): String = + when (this) { + Level0 -> "Level 0 (${ElevationTokens.Level0}dp)" + Level1 -> "Level 1 (${ElevationTokens.Level1}dp)" + Level2 -> "Level 2 (${ElevationTokens.Level2}dp)" + Level3 -> "Level 3 (${ElevationTokens.Level3}dp)" + Level4 -> "Level 4 (${ElevationTokens.Level4}dp)" + Level5 -> "Level 5 (${ElevationTokens.Level5}dp)" + } + + companion object { + + val default = Level0 + val values = listOf(Level0, Level1, Level2, Level3, Level4, Level5) + + fun fromPreferences(preferences: Preferences) = + when (preferences[DataStoreKeys.ReadingPageTonalElevation.key]) { + ElevationTokens.Level0 -> Level0 + ElevationTokens.Level1 -> Level1 + ElevationTokens.Level2 -> Level2 + ElevationTokens.Level3 -> Level3 + ElevationTokens.Level4 -> Level4 + ElevationTokens.Level5 -> Level5 + else -> default + } + } +} + diff --git a/app/src/main/java/me/ash/reader/data/model/preference/ReadingSubheadAlignPreference.kt b/app/src/main/java/me/ash/reader/data/model/preference/ReadingSubheadAlignPreference.kt new file mode 100644 index 0000000..35aefe6 --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/model/preference/ReadingSubheadAlignPreference.kt @@ -0,0 +1,58 @@ +package me.ash.reader.data.model.preference + +import android.content.Context +import androidx.compose.ui.text.style.TextAlign +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 + +sealed class ReadingSubheadAlignPreference(val value: Int) : Preference() { + object Left : ReadingSubheadAlignPreference(0) + object Right : ReadingSubheadAlignPreference(1) + object Center : ReadingSubheadAlignPreference(2) + object Justify : ReadingSubheadAlignPreference(3) + + override fun put(context: Context, scope: CoroutineScope) { + scope.launch { + context.dataStore.put( + DataStoreKeys.ReadingSubheadAlign, + value + ) + } + } + + fun toDesc(context: Context): String = + when (this) { + Left -> context.getString(R.string.align_left) + Right -> context.getString(R.string.align_right) + Center -> context.getString(R.string.center_text) + Justify -> context.getString(R.string.justify) + } + + fun toTextAlign(): TextAlign = + when (this) { + Left -> TextAlign.Start + Right -> TextAlign.End + Center -> TextAlign.Center + Justify -> TextAlign.Justify + } + + companion object { + + val default = Left + val values = listOf(Left, Right, Center, Justify) + + fun fromPreferences(preferences: Preferences): ReadingSubheadAlignPreference = + when (preferences[DataStoreKeys.ReadingSubheadAlign.key]) { + 0 -> Left + 1 -> Right + 2 -> Center + 3 -> Justify + else -> default + } + } +} diff --git a/app/src/main/java/me/ash/reader/data/model/preference/ReadingSubheadBoldPreference.kt b/app/src/main/java/me/ash/reader/data/model/preference/ReadingSubheadBoldPreference.kt new file mode 100644 index 0000000..1d79acd --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/model/preference/ReadingSubheadBoldPreference.kt @@ -0,0 +1,42 @@ +package me.ash.reader.data.model.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 + +sealed class ReadingSubheadBoldPreference(val value: Boolean) : Preference() { + object ON : ReadingSubheadBoldPreference(true) + object OFF : ReadingSubheadBoldPreference(false) + + override fun put(context: Context, scope: CoroutineScope) { + scope.launch { + context.dataStore.put( + DataStoreKeys.ReadingSubheadBold, + value + ) + } + } + + companion object { + + val default = OFF + val values = listOf(ON, OFF) + + fun fromPreferences(preferences: Preferences) = + when (preferences[DataStoreKeys.ReadingSubheadBold.key]) { + true -> ON + false -> OFF + else -> default + } + } +} + +operator fun ReadingSubheadBoldPreference.not(): ReadingSubheadBoldPreference = + when (value) { + true -> ReadingSubheadBoldPreference.OFF + false -> ReadingSubheadBoldPreference.ON + } diff --git a/app/src/main/java/me/ash/reader/data/model/preference/ReadingSubheadUpperCasePreference.kt b/app/src/main/java/me/ash/reader/data/model/preference/ReadingSubheadUpperCasePreference.kt new file mode 100644 index 0000000..a574dd4 --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/model/preference/ReadingSubheadUpperCasePreference.kt @@ -0,0 +1,42 @@ +package me.ash.reader.data.model.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 + +sealed class ReadingSubheadUpperCasePreference(val value: Boolean) : Preference() { + object ON : ReadingSubheadUpperCasePreference(true) + object OFF : ReadingSubheadUpperCasePreference(false) + + override fun put(context: Context, scope: CoroutineScope) { + scope.launch { + context.dataStore.put( + DataStoreKeys.ReadingSubheadUpperCase, + value + ) + } + } + + companion object { + + val default = OFF + val values = listOf(ON, OFF) + + fun fromPreferences(preferences: Preferences) = + when (preferences[DataStoreKeys.ReadingSubheadUpperCase.key]) { + true -> ON + false -> OFF + else -> default + } + } +} + +operator fun ReadingSubheadUpperCasePreference.not(): ReadingSubheadUpperCasePreference = + when (value) { + true -> ReadingSubheadUpperCasePreference.OFF + false -> ReadingSubheadUpperCasePreference.ON + } diff --git a/app/src/main/java/me/ash/reader/data/model/preference/ReadingTextAlignPreference.kt b/app/src/main/java/me/ash/reader/data/model/preference/ReadingTextAlignPreference.kt new file mode 100644 index 0000000..b541b8e --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/model/preference/ReadingTextAlignPreference.kt @@ -0,0 +1,66 @@ +package me.ash.reader.data.model.preference + +import android.content.Context +import androidx.compose.ui.Alignment +import androidx.compose.ui.text.style.TextAlign +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 + +sealed class ReadingTextAlignPreference(val value: Int) : Preference() { + object Left : ReadingTextAlignPreference(0) + object Right : ReadingTextAlignPreference(1) + object Center : ReadingTextAlignPreference(2) + object Justify : ReadingTextAlignPreference(3) + + override fun put(context: Context, scope: CoroutineScope) { + scope.launch { + context.dataStore.put( + DataStoreKeys.ReadingTextAlign, + value + ) + } + } + + fun toDesc(context: Context): String = + when (this) { + Left -> context.getString(R.string.align_left) + Right -> context.getString(R.string.align_right) + Center -> context.getString(R.string.center_text) + Justify -> context.getString(R.string.justify) + } + + fun toTextAlign(): TextAlign = + when (this) { + Left -> TextAlign.Start + Right -> TextAlign.End + Center -> TextAlign.Center + Justify -> TextAlign.Justify + } + fun toAlignment(): Alignment.Horizontal = + when (this) { + Left -> Alignment.Start + Right -> Alignment.End + Center -> Alignment.CenterHorizontally + Justify -> Alignment.Start + } + + companion object { + + val default = Left + val values = listOf(Left, Right, Center, Justify) + + fun fromPreferences(preferences: Preferences): ReadingTextAlignPreference = + when (preferences[DataStoreKeys.ReadingTextAlign.key]) { + 0 -> Left + 1 -> Right + 2 -> Center + 3 -> Justify + else -> default + } + } +} diff --git a/app/src/main/java/me/ash/reader/data/model/preference/ReadingTextBoldPreference.kt b/app/src/main/java/me/ash/reader/data/model/preference/ReadingTextBoldPreference.kt new file mode 100644 index 0000000..b8d1103 --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/model/preference/ReadingTextBoldPreference.kt @@ -0,0 +1,42 @@ +package me.ash.reader.data.model.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 + +sealed class ReadingTextBoldPreference(val value: Boolean) : Preference() { + object ON : ReadingTextBoldPreference(true) + object OFF : ReadingTextBoldPreference(false) + + override fun put(context: Context, scope: CoroutineScope) { + scope.launch { + context.dataStore.put( + DataStoreKeys.ReadingTextBold, + value + ) + } + } + + companion object { + + val default = OFF + val values = listOf(ON, OFF) + + fun fromPreferences(preferences: Preferences) = + when (preferences[DataStoreKeys.ReadingTextBold.key]) { + true -> ON + false -> OFF + else -> default + } + } +} + +operator fun ReadingTextBoldPreference.not(): ReadingTextBoldPreference = + when (value) { + true -> ReadingTextBoldPreference.OFF + false -> ReadingTextBoldPreference.ON + } diff --git a/app/src/main/java/me/ash/reader/data/model/preference/ReadingTextFontSizePreference.kt b/app/src/main/java/me/ash/reader/data/model/preference/ReadingTextFontSizePreference.kt new file mode 100644 index 0000000..85ff1c9 --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/model/preference/ReadingTextFontSizePreference.kt @@ -0,0 +1,23 @@ +package me.ash.reader.data.model.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 + +object ReadingTextFontSizePreference { + + const val default = 17 + + fun put(context: Context, scope: CoroutineScope, value: Int) { + scope.launch { + context.dataStore.put(DataStoreKeys.ReadingTextFontSize, value) + } + } + + fun fromPreferences(preferences: Preferences) = + preferences[DataStoreKeys.ReadingTextFontSize.key] ?: default +} diff --git a/app/src/main/java/me/ash/reader/data/model/preference/ReadingTextHorizontalPaddingPreference.kt b/app/src/main/java/me/ash/reader/data/model/preference/ReadingTextHorizontalPaddingPreference.kt new file mode 100644 index 0000000..2c5524f --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/model/preference/ReadingTextHorizontalPaddingPreference.kt @@ -0,0 +1,23 @@ +package me.ash.reader.data.model.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 + +object ReadingTextHorizontalPaddingPreference { + + const val default = 24 + + fun put(context: Context, scope: CoroutineScope, value: Int) { + scope.launch { + context.dataStore.put(DataStoreKeys.ReadingTextHorizontalPadding, value) + } + } + + fun fromPreferences(preferences: Preferences) = + preferences[DataStoreKeys.ReadingTextHorizontalPadding.key] ?: default +} diff --git a/app/src/main/java/me/ash/reader/data/model/preference/ReadingThemePreference.kt b/app/src/main/java/me/ash/reader/data/model/preference/ReadingThemePreference.kt new file mode 100644 index 0000000..19be8c6 --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/model/preference/ReadingThemePreference.kt @@ -0,0 +1,127 @@ +package me.ash.reader.data.model.preference + +import android.content.Context +import androidx.compose.runtime.Immutable +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 + +@Immutable +sealed class ReadingThemePreference(val value: Int) : Preference() { + + object MaterialYou : ReadingThemePreference(0) + object Reeder : ReadingThemePreference(1) + object Paper : ReadingThemePreference(2) + object Custom : ReadingThemePreference(3) + + override fun put(context: Context, scope: CoroutineScope) { + scope.launch { + context.dataStore.put(DataStoreKeys.ReadingTheme, value) + } + } + + fun toDesc(context: Context): String = + when (this) { + MaterialYou -> "Material You" + Reeder -> "Reeder" + Paper -> "Paper" + Custom -> "Custom" + } + + fun applyTheme(context: Context, scope: CoroutineScope) { + when (this) { + MaterialYou -> { + ReadingTitleBoldPreference.default.put(context, scope) + ReadingTitleUpperCasePreference.default.put(context, scope) + ReadingTitleAlignPreference.default.put(context, scope) + ReadingSubheadBoldPreference.default.put(context, scope) + ReadingSubheadUpperCasePreference.default.put(context, scope) + ReadingSubheadAlignPreference.default.put(context, scope) + ReadingTextBoldPreference.default.put(context, scope) + ReadingTextHorizontalPaddingPreference.put(context, scope, + ReadingTextHorizontalPaddingPreference.default) + ReadingTextAlignPreference.default.put(context, scope) + ReadingLetterSpacingPreference.put(context, scope, ReadingLetterSpacingPreference.default) + ReadingTextFontSizePreference.put(context, scope, ReadingTextFontSizePreference.default) + ReadingImageRoundedCornersPreference.put(context, scope, ReadingImageRoundedCornersPreference.default) + ReadingImageHorizontalPaddingPreference.put(context, scope, + ReadingImageHorizontalPaddingPreference.default) + ReadingImageMaximizePreference.default.put(context, scope) + } + + Reeder -> { + ReadingTitleBoldPreference.ON.put(context, scope) + ReadingTitleUpperCasePreference.default.put(context, scope) + ReadingTitleAlignPreference.default.put(context, scope) + ReadingSubheadBoldPreference.ON.put(context, scope) + ReadingSubheadUpperCasePreference.default.put(context, scope) + ReadingSubheadAlignPreference.default.put(context, scope) + ReadingTextBoldPreference.default.put(context, scope) + ReadingTextHorizontalPaddingPreference.put(context, scope, + ReadingTextHorizontalPaddingPreference.default) + ReadingTextAlignPreference.default.put(context, scope) + ReadingLetterSpacingPreference.put(context, scope, ReadingLetterSpacingPreference.default) + ReadingTextFontSizePreference.put(context, scope, 18) + ReadingImageRoundedCornersPreference.put(context, scope, 0) + ReadingImageHorizontalPaddingPreference.put(context, scope, 0) + ReadingImageMaximizePreference.default.put(context, scope) + } + + Paper -> { + ReadingTitleBoldPreference.ON.put(context, scope) + ReadingTitleUpperCasePreference.ON.put(context, scope) + ReadingTitleAlignPreference.Center.put(context, scope) + ReadingSubheadBoldPreference.ON.put(context, scope) + ReadingSubheadUpperCasePreference.ON.put(context, scope) + ReadingSubheadAlignPreference.Center.put(context, scope) + ReadingTextBoldPreference.default.put(context, scope) + ReadingTextHorizontalPaddingPreference.put(context, scope, + ReadingTextHorizontalPaddingPreference.default) + ReadingTextAlignPreference.Center.put(context, scope) + ReadingLetterSpacingPreference.put(context, scope, ReadingLetterSpacingPreference.default) + ReadingTextFontSizePreference.put(context, scope, 20) + ReadingImageRoundedCornersPreference.put(context, scope, 0) + ReadingImageHorizontalPaddingPreference.put(context, scope, + ReadingImageHorizontalPaddingPreference.default) + ReadingImageMaximizePreference.default.put(context, scope) + } + + Custom -> { + ReadingTitleBoldPreference.default.put(context, scope) + ReadingTitleUpperCasePreference.default.put(context, scope) + ReadingTitleAlignPreference.default.put(context, scope) + ReadingSubheadBoldPreference.default.put(context, scope) + ReadingSubheadUpperCasePreference.default.put(context, scope) + ReadingSubheadAlignPreference.default.put(context, scope) + ReadingTextBoldPreference.default.put(context, scope) + ReadingTextHorizontalPaddingPreference.put(context, scope, + ReadingTextHorizontalPaddingPreference.default) + ReadingTextAlignPreference.default.put(context, scope) + ReadingLetterSpacingPreference.put(context, scope, ReadingLetterSpacingPreference.default) + ReadingTextFontSizePreference.put(context, scope, ReadingTextFontSizePreference.default) + ReadingImageRoundedCornersPreference.put(context, scope, ReadingImageRoundedCornersPreference.default) + ReadingImageHorizontalPaddingPreference.put(context, scope, + ReadingImageHorizontalPaddingPreference.default) + ReadingImageMaximizePreference.default.put(context, scope) + } + } + } + + companion object { + + val default = MaterialYou + val values = listOf(MaterialYou, Reeder, Paper, Custom) + + fun fromPreferences(preferences: Preferences): ReadingThemePreference = + when (preferences[DataStoreKeys.ReadingTheme.key]) { + 0 -> MaterialYou + 1 -> Reeder + 2 -> Paper + 3 -> Custom + else -> default + } + } +} diff --git a/app/src/main/java/me/ash/reader/data/model/preference/ReadingTitleAlignPreference.kt b/app/src/main/java/me/ash/reader/data/model/preference/ReadingTitleAlignPreference.kt new file mode 100644 index 0000000..a38f2dc --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/model/preference/ReadingTitleAlignPreference.kt @@ -0,0 +1,61 @@ +package me.ash.reader.data.model.preference + +import android.content.Context +import androidx.compose.runtime.Stable +import androidx.compose.ui.text.style.TextAlign +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 + +sealed class ReadingTitleAlignPreference(val value: Int) : Preference() { + object Left : ReadingTitleAlignPreference(0) + object Right : ReadingTitleAlignPreference(1) + object Center : ReadingTitleAlignPreference(2) + object Justify : ReadingTitleAlignPreference(3) + + override fun put(context: Context, scope: CoroutineScope) { + scope.launch { + context.dataStore.put( + DataStoreKeys.ReadingTitleAlign, + value + ) + } + } + + @Stable + fun toDesc(context: Context): String = + when (this) { + Left -> context.getString(R.string.align_left) + Right -> context.getString(R.string.align_right) + Center -> context.getString(R.string.center_text) + Justify -> context.getString(R.string.justify) + } + + @Stable + fun toTextAlign(): TextAlign = + when (this) { + Left -> TextAlign.Start + Right -> TextAlign.End + Center -> TextAlign.Center + Justify -> TextAlign.Justify + } + + companion object { + + val default = Left + val values = listOf(Left, Right, Center, Justify) + + fun fromPreferences(preferences: Preferences): ReadingTitleAlignPreference = + when (preferences[DataStoreKeys.ReadingTitleAlign.key]) { + 0 -> Left + 1 -> Right + 2 -> Center + 3 -> Justify + else -> default + } + } +} diff --git a/app/src/main/java/me/ash/reader/data/model/preference/ReadingTitleBoldPreference.kt b/app/src/main/java/me/ash/reader/data/model/preference/ReadingTitleBoldPreference.kt new file mode 100644 index 0000000..599daa8 --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/model/preference/ReadingTitleBoldPreference.kt @@ -0,0 +1,42 @@ +package me.ash.reader.data.model.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 + +sealed class ReadingTitleBoldPreference(val value: Boolean) : Preference() { + object ON : ReadingTitleBoldPreference(true) + object OFF : ReadingTitleBoldPreference(false) + + override fun put(context: Context, scope: CoroutineScope) { + scope.launch { + context.dataStore.put( + DataStoreKeys.ReadingTitleBold, + value + ) + } + } + + companion object { + + val default = OFF + val values = listOf(ON, OFF) + + fun fromPreferences(preferences: Preferences) = + when (preferences[DataStoreKeys.ReadingTitleBold.key]) { + true -> ON + false -> OFF + else -> default + } + } +} + +operator fun ReadingTitleBoldPreference.not(): ReadingTitleBoldPreference = + when (value) { + true -> ReadingTitleBoldPreference.OFF + false -> ReadingTitleBoldPreference.ON + } diff --git a/app/src/main/java/me/ash/reader/data/model/preference/ReadingTitleUpperCasePreference.kt b/app/src/main/java/me/ash/reader/data/model/preference/ReadingTitleUpperCasePreference.kt new file mode 100644 index 0000000..e2f66c7 --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/model/preference/ReadingTitleUpperCasePreference.kt @@ -0,0 +1,42 @@ +package me.ash.reader.data.model.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 + +sealed class ReadingTitleUpperCasePreference(val value: Boolean) : Preference() { + object ON : ReadingTitleUpperCasePreference(true) + object OFF : ReadingTitleUpperCasePreference(false) + + override fun put(context: Context, scope: CoroutineScope) { + scope.launch { + context.dataStore.put( + DataStoreKeys.ReadingTitleUpperCase, + value + ) + } + } + + companion object { + + val default = OFF + val values = listOf(ON, OFF) + + fun fromPreferences(preferences: Preferences) = + when (preferences[DataStoreKeys.ReadingTitleUpperCase.key]) { + true -> ON + false -> OFF + else -> default + } + } +} + +operator fun ReadingTitleUpperCasePreference.not(): ReadingTitleUpperCasePreference = + when (value) { + true -> ReadingTitleUpperCasePreference.OFF + false -> ReadingTitleUpperCasePreference.ON + } diff --git a/app/src/main/java/me/ash/reader/data/model/preference/Settings.kt b/app/src/main/java/me/ash/reader/data/model/preference/Settings.kt index 7dd6cec..b8305a7 100644 --- a/app/src/main/java/me/ash/reader/data/model/preference/Settings.kt +++ b/app/src/main/java/me/ash/reader/data/model/preference/Settings.kt @@ -12,6 +12,7 @@ import me.ash.reader.ui.ext.collectAsStateValue import me.ash.reader.ui.ext.dataStore data class Settings( + // Version val newVersionNumber: Version = NewVersionNumberPreference.default, val skipVersionNumber: Version = SkipVersionNumberPreference.default, val newVersionPublishDate: String = NewVersionPublishDatePreference.default, @@ -19,11 +20,13 @@ data class Settings( val newVersionSize: String = NewVersionSizePreference.default, val newVersionDownloadUrl: String = NewVersionDownloadUrlPreference.default, + // Theme val themeIndex: Int = ThemeIndexPreference.default, val customPrimaryColor: String = CustomPrimaryColorPreference.default, val darkTheme: DarkThemePreference = DarkThemePreference.default, val amoledDarkTheme: AmoledDarkThemePreference = AmoledDarkThemePreference.default, + // Feeds page val feedsFilterBarStyle: FeedsFilterBarStylePreference = FeedsFilterBarStylePreference.default, val feedsFilterBarFilled: FeedsFilterBarFilledPreference = FeedsFilterBarFilledPreference.default, val feedsFilterBarPadding: Int = FeedsFilterBarPaddingPreference.default, @@ -32,6 +35,7 @@ data class Settings( val feedsGroupListExpand: FeedsGroupListExpandPreference = FeedsGroupListExpandPreference.default, val feedsGroupListTonalElevation: FeedsGroupListTonalElevationPreference = FeedsGroupListTonalElevationPreference.default, + // Flow page val flowFilterBarStyle: FlowFilterBarStylePreference = FlowFilterBarStylePreference.default, val flowFilterBarFilled: FlowFilterBarFilledPreference = FlowFilterBarFilledPreference.default, val flowFilterBarPadding: Int = FlowFilterBarPaddingPreference.default, @@ -45,67 +49,36 @@ data class Settings( val flowArticleListDateStickyHeader: FlowArticleListDateStickyHeaderPreference = FlowArticleListDateStickyHeaderPreference.default, val flowArticleListTonalElevation: FlowArticleListTonalElevationPreference = FlowArticleListTonalElevationPreference.default, + // Reading page + val readingTheme: ReadingThemePreference = ReadingThemePreference.default, + val readingDarkTheme: ReadingDarkThemePreference = ReadingDarkThemePreference.default, + val readingPageTonalElevation: ReadingPageTonalElevationPreference = ReadingPageTonalElevationPreference.default, + val readingAutoHideToolbar: ReadingAutoHideToolbarPreference = ReadingAutoHideToolbarPreference.default, + val readingTextFontSize: Int = ReadingTextFontSizePreference.default, + val readingLetterSpacing: Double = ReadingLetterSpacingPreference.default, + val readingTextHorizontalPadding: Int = ReadingTextHorizontalPaddingPreference.default, + val readingTextAlign: ReadingTextAlignPreference = ReadingTextAlignPreference.default, + val readingTextBold: ReadingTextBoldPreference = ReadingTextBoldPreference.default, + val readingTitleAlign: ReadingTitleAlignPreference = ReadingTitleAlignPreference.default, + val readingSubheadAlign: ReadingSubheadAlignPreference = ReadingSubheadAlignPreference.default, + val readingFonts: ReadingFontsPreference = ReadingFontsPreference.default, + val readingTitleBold: ReadingTitleBoldPreference = ReadingTitleBoldPreference.default, + val readingSubheadBold: ReadingSubheadBoldPreference = ReadingSubheadBoldPreference.default, + val readingTitleUpperCase: ReadingTitleUpperCasePreference = ReadingTitleUpperCasePreference.default, + val readingSubheadUpperCase: ReadingSubheadUpperCasePreference = ReadingSubheadUpperCasePreference.default, + val readingImageHorizontalPadding: Int = ReadingImageHorizontalPaddingPreference.default, + val readingImageRoundedCorners: Int = ReadingImageRoundedCornersPreference.default, + val readingImageMaximize: ReadingImageMaximizePreference = ReadingImageMaximizePreference.default, + + // Interaction val initialPage: InitialPagePreference = InitialPagePreference.default, val initialFilter: InitialFilterPreference = InitialFilterPreference.default, + // Languages val languages: LanguagesPreference = LanguagesPreference.default, ) -@Composable -fun SettingsProvider( - content: @Composable () -> Unit, -) { - val context = LocalContext.current - val settings = remember { - context.dataStore.data.map { - Log.i("RLog", "AppTheme: ${it}") - it.toSettings() - } - }.collectAsStateValue(initial = Settings()) - - CompositionLocalProvider( - LocalNewVersionNumber provides settings.newVersionNumber, - LocalSkipVersionNumber provides settings.skipVersionNumber, - LocalNewVersionPublishDate provides settings.newVersionPublishDate, - LocalNewVersionLog provides settings.newVersionLog, - LocalNewVersionSize provides settings.newVersionSize, - LocalNewVersionDownloadUrl provides settings.newVersionDownloadUrl, - - LocalThemeIndex provides settings.themeIndex, - LocalCustomPrimaryColor provides settings.customPrimaryColor, - LocalDarkTheme provides settings.darkTheme, - LocalAmoledDarkTheme provides settings.amoledDarkTheme, - - LocalFeedsTopBarTonalElevation provides settings.feedsTopBarTonalElevation, - LocalFeedsGroupListExpand provides settings.feedsGroupListExpand, - LocalFeedsGroupListTonalElevation provides settings.feedsGroupListTonalElevation, - LocalFeedsFilterBarStyle provides settings.feedsFilterBarStyle, - LocalFeedsFilterBarFilled provides settings.feedsFilterBarFilled, - LocalFeedsFilterBarPadding provides settings.feedsFilterBarPadding, - LocalFeedsFilterBarTonalElevation provides settings.feedsFilterBarTonalElevation, - - LocalFlowTopBarTonalElevation provides settings.flowTopBarTonalElevation, - LocalFlowArticleListFeedIcon provides settings.flowArticleListFeedIcon, - LocalFlowArticleListFeedName provides settings.flowArticleListFeedName, - LocalFlowArticleListImage provides settings.flowArticleListImage, - LocalFlowArticleListDesc provides settings.flowArticleListDesc, - LocalFlowArticleListTime provides settings.flowArticleListTime, - LocalFlowArticleListDateStickyHeader provides settings.flowArticleListDateStickyHeader, - LocalFlowArticleListTonalElevation provides settings.flowArticleListTonalElevation, - LocalFlowFilterBarStyle provides settings.flowFilterBarStyle, - LocalFlowFilterBarFilled provides settings.flowFilterBarFilled, - LocalFlowFilterBarPadding provides settings.flowFilterBarPadding, - LocalFlowFilterBarTonalElevation provides settings.flowFilterBarTonalElevation, - - LocalInitialPage provides settings.initialPage, - LocalInitialFilter provides settings.initialFilter, - - LocalLanguages provides settings.languages, - ) { - content() - } -} - +// Version val LocalNewVersionNumber = compositionLocalOf { NewVersionNumberPreference.default } val LocalSkipVersionNumber = compositionLocalOf { SkipVersionNumberPreference.default } val LocalNewVersionPublishDate = compositionLocalOf { NewVersionPublishDatePreference.default } @@ -113,6 +86,7 @@ val LocalNewVersionLog = compositionLocalOf { NewVersionLogPreference.default } val LocalNewVersionSize = compositionLocalOf { NewVersionSizePreference.default } val LocalNewVersionDownloadUrl = compositionLocalOf { NewVersionDownloadUrlPreference.default } +// Theme val LocalThemeIndex = compositionLocalOf { ThemeIndexPreference.default } val LocalCustomPrimaryColor = @@ -122,6 +96,7 @@ val LocalDarkTheme = val LocalAmoledDarkTheme = compositionLocalOf { AmoledDarkThemePreference.default } +// Feeds page val LocalFeedsFilterBarStyle = compositionLocalOf { FeedsFilterBarStylePreference.default } val LocalFeedsFilterBarFilled = @@ -137,6 +112,7 @@ val LocalFeedsGroupListExpand = val LocalFeedsGroupListTonalElevation = compositionLocalOf { FeedsGroupListTonalElevationPreference.default } +// Flow page val LocalFlowFilterBarStyle = compositionLocalOf { FlowFilterBarStylePreference.default } val LocalFlowFilterBarFilled = @@ -162,9 +138,119 @@ val LocalFlowArticleListDateStickyHeader = val LocalFlowArticleListTonalElevation = compositionLocalOf { FlowArticleListTonalElevationPreference.default } +// Reading page +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 LocalReadingSubheadAlign = + compositionLocalOf { ReadingSubheadAlignPreference.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 LocalReadingImageMaximize = compositionLocalOf { ReadingImageMaximizePreference.default } + +// Interaction val LocalInitialPage = compositionLocalOf { InitialPagePreference.default } val LocalInitialFilter = compositionLocalOf { InitialFilterPreference.default } +// Languages val LocalLanguages = compositionLocalOf { LanguagesPreference.default } + +@Composable +fun SettingsProvider( + content: @Composable () -> Unit, +) { + val context = LocalContext.current + val settings = remember { + context.dataStore.data.map { + Log.i("RLog", "AppTheme: ${it}") + it.toSettings() + } + }.collectAsStateValue(initial = Settings()) + + CompositionLocalProvider( + // Version + LocalNewVersionNumber provides settings.newVersionNumber, + LocalSkipVersionNumber provides settings.skipVersionNumber, + LocalNewVersionPublishDate provides settings.newVersionPublishDate, + LocalNewVersionLog provides settings.newVersionLog, + LocalNewVersionSize provides settings.newVersionSize, + LocalNewVersionDownloadUrl provides settings.newVersionDownloadUrl, + + // Theme + LocalThemeIndex provides settings.themeIndex, + LocalCustomPrimaryColor provides settings.customPrimaryColor, + LocalDarkTheme provides settings.darkTheme, + LocalAmoledDarkTheme provides settings.amoledDarkTheme, + + // Feeds page + LocalFeedsTopBarTonalElevation provides settings.feedsTopBarTonalElevation, + LocalFeedsGroupListExpand provides settings.feedsGroupListExpand, + LocalFeedsGroupListTonalElevation provides settings.feedsGroupListTonalElevation, + LocalFeedsFilterBarStyle provides settings.feedsFilterBarStyle, + LocalFeedsFilterBarFilled provides settings.feedsFilterBarFilled, + LocalFeedsFilterBarPadding provides settings.feedsFilterBarPadding, + LocalFeedsFilterBarTonalElevation provides settings.feedsFilterBarTonalElevation, + + // Flow page + LocalFlowTopBarTonalElevation provides settings.flowTopBarTonalElevation, + LocalFlowArticleListFeedIcon provides settings.flowArticleListFeedIcon, + LocalFlowArticleListFeedName provides settings.flowArticleListFeedName, + LocalFlowArticleListImage provides settings.flowArticleListImage, + LocalFlowArticleListDesc provides settings.flowArticleListDesc, + LocalFlowArticleListTime provides settings.flowArticleListTime, + LocalFlowArticleListDateStickyHeader provides settings.flowArticleListDateStickyHeader, + LocalFlowArticleListTonalElevation provides settings.flowArticleListTonalElevation, + LocalFlowFilterBarStyle provides settings.flowFilterBarStyle, + LocalFlowFilterBarFilled provides settings.flowFilterBarFilled, + LocalFlowFilterBarPadding provides settings.flowFilterBarPadding, + LocalFlowFilterBarTonalElevation provides settings.flowFilterBarTonalElevation, + + // Reading page + LocalReadingTheme provides settings.readingTheme, + LocalReadingDarkTheme provides settings.readingDarkTheme, + LocalReadingPageTonalElevation provides settings.readingPageTonalElevation, + LocalReadingAutoHideToolbar provides settings.readingAutoHideToolbar, + LocalReadingTextFontSize provides settings.readingTextFontSize, + LocalReadingLetterSpacing provides settings.readingLetterSpacing, + LocalReadingTextHorizontalPadding provides settings.readingTextHorizontalPadding, + LocalReadingTextAlign provides settings.readingTextAlign, + LocalReadingTextBold provides settings.readingTextBold, + LocalReadingTitleAlign provides settings.readingTitleAlign, + LocalReadingSubheadAlign provides settings.readingSubheadAlign, + LocalReadingFonts provides settings.readingFonts, + LocalReadingTitleBold provides settings.readingTitleBold, + LocalReadingSubheadBold provides settings.readingSubheadBold, + LocalReadingTitleUpperCase provides settings.readingTitleUpperCase, + LocalReadingSubheadUpperCase provides settings.readingSubheadUpperCase, + LocalReadingImageHorizontalPadding provides settings.readingImageHorizontalPadding, + LocalReadingImageRoundedCorners provides settings.readingImageRoundedCorners, + LocalReadingImageMaximize provides settings.readingImageMaximize, + + // Interaction + LocalInitialPage provides settings.initialPage, + LocalInitialFilter provides settings.initialFilter, + + // Languages + LocalLanguages provides settings.languages, + ) { + content() + } +} + diff --git a/app/src/main/java/me/ash/reader/ui/component/ReadingThemePrev.kt b/app/src/main/java/me/ash/reader/ui/component/ReadingThemePrev.kt new file mode 100644 index 0000000..9df4db4 --- /dev/null +++ b/app/src/main/java/me/ash/reader/ui/component/ReadingThemePrev.kt @@ -0,0 +1,168 @@ +package me.ash.reader.ui.component + +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import me.ash.reader.data.model.preference.LocalReadingImageHorizontalPadding +import me.ash.reader.data.model.preference.LocalReadingImageRoundedCorners +import me.ash.reader.data.model.preference.LocalReadingTextAlign +import me.ash.reader.data.model.preference.ReadingThemePreference +import me.ash.reader.ui.theme.Shape24 +import me.ash.reader.ui.theme.palette.onDark +import me.ash.reader.ui.theme.palette.onLight + +@Composable +fun ReadingThemePrev( + selected: ReadingThemePreference = ReadingThemePreference.MaterialYou, + theme: ReadingThemePreference = ReadingThemePreference.MaterialYou, + onClick: () -> Unit = {}, +) { + val context = LocalContext.current + val imageRoundedCorners = LocalReadingImageRoundedCorners.current + val roundedCorners by remember { mutableStateOf(RoundedCornerShape((imageRoundedCorners / 2).dp)) } + + Column( + modifier = Modifier + .width(150.dp) + .clip(Shape24) + .background(MaterialTheme.colorScheme.inverseOnSurface + onLight MaterialTheme.colorScheme.surface + ) + .border( + width = animateDpAsState(if (selected == theme) 4.dp else (-1).dp).value, + color = MaterialTheme.colorScheme.primary, + shape = Shape24 + ) + .clickable(onClick = onClick), + horizontalAlignment = when (theme) { + ReadingThemePreference.MaterialYou -> Alignment.Start + ReadingThemePreference.Reeder -> Alignment.Start + ReadingThemePreference.Paper -> Alignment.CenterHorizontally + ReadingThemePreference.Custom -> LocalReadingTextAlign.current.toAlignment() + } + ) { + Spacer(modifier = Modifier.height(22.dp)) + // Header + Text( + modifier = Modifier.padding(horizontal = 12.dp), + text = theme.toDesc(context), + style = MaterialTheme.typography.titleSmall, + ) + Spacer(modifier = Modifier.height(2.dp)) + // Metadata + Box(modifier = Modifier + .padding(horizontal = 12.dp) + .size(width = 32.dp, height = 4.dp) + .clip(CircleShape) + .background(MaterialTheme.colorScheme.tertiaryContainer) + ) + Spacer(modifier = Modifier.height(16.dp)) + // Paragraph + Box(modifier = Modifier + .padding(horizontal = 12.dp) + .fillMaxWidth() + .height(12.dp) + .clip(CircleShape) + .background(MaterialTheme.colorScheme.surfaceVariant) + ) + Spacer(modifier = Modifier.height(4.dp)) + Row(modifier = Modifier + .padding(horizontal = 12.dp) + .width(114.dp) + .height(12.dp) + ) { + Box(modifier = Modifier + .weight(1f) + .fillMaxSize() + .clip(CircleShape) + .background(MaterialTheme.colorScheme.surfaceVariant) + ) + Box(modifier = Modifier + .padding(start = 4.dp) + .weight(2f) + .fillMaxSize() + .clip(CircleShape) + .background(MaterialTheme.colorScheme.surfaceVariant) + ) + } + Spacer(modifier = Modifier.height(8.dp)) + // Image + Box(modifier = Modifier + .padding(horizontal = when (theme) { + ReadingThemePreference.MaterialYou -> 12.dp + ReadingThemePreference.Reeder -> 0.dp + ReadingThemePreference.Paper -> 12.dp + ReadingThemePreference.Custom -> (LocalReadingImageHorizontalPadding.current / 2).dp + }) + .fillMaxWidth() + .height(46.dp) + .clip(when (theme) { + ReadingThemePreference.MaterialYou -> MaterialTheme.shapes.medium + ReadingThemePreference.Reeder -> RectangleShape + ReadingThemePreference.Paper -> RectangleShape + ReadingThemePreference.Custom -> roundedCorners + }) + .background(MaterialTheme.colorScheme.primaryContainer onDark MaterialTheme.colorScheme.secondaryContainer) + ) + Spacer(modifier = Modifier.height(8.dp)) + // Footer + Box(modifier = Modifier + .padding(horizontal = 12.dp) + .width(100.dp) + .height(12.dp) + .clip(CircleShape) + .background(MaterialTheme.colorScheme.surfaceVariant) + ) + Spacer(modifier = Modifier.height(14.dp)) + } +} + +@Preview +@Composable +private fun ReadYouPreview() { + ReadingThemePrev( + selected = ReadingThemePreference.MaterialYou, + theme = ReadingThemePreference.MaterialYou, + ) +} + +@Preview +@Composable +private fun ReederPreview() { + ReadingThemePrev( + theme = ReadingThemePreference.Reeder, + ) +} + +@Preview +@Composable +private fun PaperPreview() { + ReadingThemePrev( + theme = ReadingThemePreference.Paper, + ) +} + +@Preview +@Composable +private fun CustomPreview() { + ReadingThemePrev( + theme = ReadingThemePreference.Custom, + ) +} diff --git a/app/src/main/java/me/ash/reader/ui/component/base/RadioDialog.kt b/app/src/main/java/me/ash/reader/ui/component/base/RadioDialog.kt index 8363208..535d7e0 100644 --- a/app/src/main/java/me/ash/reader/ui/component/base/RadioDialog.kt +++ b/app/src/main/java/me/ash/reader/ui/component/base/RadioDialog.kt @@ -16,6 +16,7 @@ import androidx.compose.runtime.Immutable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.style.BaselineShift import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp @@ -63,7 +64,7 @@ fun RadioDialog( text = option.text, style = MaterialTheme.typography.bodyLarge.copy( baselineShift = BaselineShift.None - ), + ).merge(other = option.style), color = MaterialTheme.colorScheme.onSurface, ) } @@ -78,6 +79,7 @@ fun RadioDialog( @Immutable data class RadioDialogOption( val text: String = "", + val style: TextStyle? = null, val selected: Boolean = false, val onClick: () -> Unit = {}, -) \ No newline at end of file +) diff --git a/app/src/main/java/me/ash/reader/ui/component/reader/HtmlToComposable.kt b/app/src/main/java/me/ash/reader/ui/component/reader/HtmlToComposable.kt index e9c172b..a5552fc 100644 --- a/app/src/main/java/me/ash/reader/ui/component/reader/HtmlToComposable.kt +++ b/app/src/main/java/me/ash/reader/ui/component/reader/HtmlToComposable.kt @@ -33,6 +33,7 @@ import androidx.compose.foundation.text.selection.DisableSelection import androidx.compose.material.Text import androidx.compose.material3.Surface import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.RectangleShape @@ -51,6 +52,7 @@ import coil.size.Precision import coil.size.Size import coil.size.pxOrElse import me.ash.reader.R +import me.ash.reader.data.model.preference.LocalReadingImageMaximize import me.ash.reader.ui.component.base.RYAsyncImage import org.jsoup.Jsoup import org.jsoup.helper.StringUtil @@ -63,6 +65,7 @@ import kotlin.math.roundToInt fun LazyListScope.htmlFormattedText( inputStream: InputStream, + subheadUpperCase: Boolean = false, baseUrl: String, @DrawableRes imagePlaceholder: Int, onLinkClick: (String) -> Unit, @@ -72,6 +75,7 @@ fun LazyListScope.htmlFormattedText( ?.let { body -> formatBody( element = body, + subheadUpperCase = subheadUpperCase, imagePlaceholder = imagePlaceholder, onLinkClick = onLinkClick, baseUrl = baseUrl, @@ -81,6 +85,7 @@ fun LazyListScope.htmlFormattedText( private fun LazyListScope.formatBody( element: Element, + subheadUpperCase: Boolean = false, @DrawableRes imagePlaceholder: Int, onLinkClick: (String) -> Unit, baseUrl: String, @@ -98,7 +103,7 @@ private fun LazyListScope.formatBody( text = paragraph, style = bodyStyle(), modifier = Modifier - .padding(horizontal = PADDING_HORIZONTAL.dp) + .padding(horizontal = textHorizontalPadding().dp) .width(MAX_CONTENT_WIDTH.dp) ) { offset -> paragraph.getStringAnnotations("URL", offset, offset) @@ -112,7 +117,7 @@ private fun LazyListScope.formatBody( text = paragraph, style = bodyStyle(), modifier = Modifier - .padding(horizontal = PADDING_HORIZONTAL.dp) + .padding(horizontal = textHorizontalPadding().dp) .width(MAX_CONTENT_WIDTH.dp) ) } @@ -121,6 +126,7 @@ private fun LazyListScope.formatBody( composer.appendTextChildren( element.childNodes(), + subheadUpperCase = subheadUpperCase, lazyListScope = this, imagePlaceholder = imagePlaceholder, onLinkClick = onLinkClick, @@ -144,7 +150,7 @@ private fun LazyListScope.formatCodeBlock( color = codeBlockBackground(), shape = RoundedCornerShape(8.dp), modifier = Modifier - .padding(horizontal = PADDING_HORIZONTAL.dp), + .padding(horizontal = textHorizontalPadding().dp), ) { Box( modifier = Modifier @@ -179,6 +185,7 @@ private fun LazyListScope.formatCodeBlock( private fun TextComposer.appendTextChildren( nodes: List, preFormatted: Boolean = false, + subheadUpperCase: Boolean = false, lazyListScope: LazyListScope, @DrawableRes imagePlaceholder: Int, onLinkClick: (String) -> Unit, @@ -238,9 +245,9 @@ private fun TextComposer.appendTextChildren( "h1" -> { withParagraph { withComposableStyle( - style = { h5Style().toSpanStyle() } + style = { h1Style().toSpanStyle() } ) { - append("\n${element.text()}") + append("\n${if (subheadUpperCase) element.text().uppercase() else element.text()}") } } } @@ -248,9 +255,9 @@ private fun TextComposer.appendTextChildren( "h2" -> { withParagraph { withComposableStyle( - style = { h5Style().toSpanStyle() } + style = { h2Style().toSpanStyle() } ) { - append("\n${element.text()}") + append("\n${if (subheadUpperCase) element.text().uppercase() else element.text()}") } } } @@ -258,9 +265,9 @@ private fun TextComposer.appendTextChildren( "h3" -> { withParagraph { withComposableStyle( - style = { h5Style().toSpanStyle() } + style = { h3Style().toSpanStyle() } ) { - append("\n${element.text()}") + append("\n${if (subheadUpperCase) element.text().uppercase() else element.text()}") } } } @@ -268,9 +275,9 @@ private fun TextComposer.appendTextChildren( "h4" -> { withParagraph { withComposableStyle( - style = { h5Style().toSpanStyle() } + style = { h4Style().toSpanStyle() } ) { - append("\n${element.text()}") + append("\n${if (subheadUpperCase) element.text().uppercase() else element.text()}") } } } @@ -280,7 +287,7 @@ private fun TextComposer.appendTextChildren( withComposableStyle( style = { h5Style().toSpanStyle() } ) { - append("\n${element.text()}") + append("\n${if (subheadUpperCase) element.text().uppercase() else element.text()}") } } } @@ -288,15 +295,17 @@ private fun TextComposer.appendTextChildren( "h6" -> { withParagraph { withComposableStyle( - style = { h5Style().toSpanStyle() } + style = { h6Style().toSpanStyle() } ) { - append("\n${element.text()}") + append("\n${if (subheadUpperCase) element.text().uppercase() else element.text()}") } } } "strong", "b" -> { - withStyle(SpanStyle(fontWeight = FontWeight.Bold)) { + withComposableStyle( + style = { boldStyle().toSpanStyle() } + ) { appendTextChildren( element.childNodes(), lazyListScope = lazyListScope, @@ -419,8 +428,11 @@ private fun TextComposer.appendTextChildren( "blockquote" -> { withParagraph { - withComposableStyle( - style = { blockQuoteStyle() } + withStyle( + SpanStyle( + fontStyle = FontStyle.Italic, + fontWeight = FontWeight.Light, + ) ) { appendTextChildren( element.childNodes(), @@ -458,10 +470,10 @@ private fun TextComposer.appendTextChildren( // val scale = remember { mutableStateOf(1f) } Column( modifier = Modifier -// .padding(horizontal = PADDING_HORIZONTAL.dp) +// .padding(horizontal = horizontalPadding().dp) .width(MAX_CONTENT_WIDTH.dp) ) { - Spacer(modifier = Modifier.height(PADDING_HORIZONTAL.dp)) + Spacer(modifier = Modifier.height(textHorizontalPadding().dp)) DisableSelection { BoxWithConstraints( modifier = Modifier @@ -487,9 +499,10 @@ private fun TextComposer.appendTextChildren( val imageSize = maxImageSize() RYAsyncImage( modifier = Modifier + .align(Alignment.Center) .fillMaxWidth() - .padding(horizontal = PADDING_HORIZONTAL.dp) - .clip(IMAGE_SHAPE) + .padding(horizontal = imageHorizontalPadding().dp) + .clip(imageShape()) .clickable { }, data = imageCandidates.getBestImageForMaxSize( pixelDensity = pixelDensity(), @@ -498,24 +511,24 @@ private fun TextComposer.appendTextChildren( contentDescription = alt, size = imageSize, precision = Precision.INEXACT, - contentScale = ContentScale.FillWidth, + contentScale = if (LocalReadingImageMaximize.current.value) ContentScale.FillWidth else ContentScale.Inside, ) } } if (alt.isNotBlank()) { - Spacer(modifier = Modifier.height(PADDING_HORIZONTAL.dp / 2)) + Spacer(modifier = Modifier.height(textHorizontalPadding().dp / 2)) Text( modifier = Modifier .fillMaxWidth() - .padding(horizontal = PADDING_HORIZONTAL.dp), + .padding(horizontal = textHorizontalPadding().dp), text = alt, style = captionStyle(), ) } - Spacer(modifier = Modifier.height(PADDING_HORIZONTAL.dp)) + Spacer(modifier = Modifier.height(textHorizontalPadding().dp)) } } } @@ -528,7 +541,7 @@ private fun TextComposer.appendTextChildren( .forEach { listItem -> withParagraph { // no break space - append("• ") + append(" • ") appendTextChildren( listItem.childNodes(), lazyListScope = lazyListScope, @@ -612,7 +625,7 @@ private fun TextComposer.appendTextChildren( lazyListScope.item { Column( modifier = Modifier - .padding(horizontal = PADDING_HORIZONTAL.dp) + .padding(horizontal = textHorizontalPadding().dp) .width(MAX_CONTENT_WIDTH.dp) ) { DisableSelection { @@ -622,8 +635,8 @@ private fun TextComposer.appendTextChildren( RYAsyncImage( modifier = Modifier .fillMaxWidth() - .padding(horizontal = PADDING_HORIZONTAL.dp) - .clip(IMAGE_SHAPE) + .padding(horizontal = imageHorizontalPadding().dp) + .clip(imageShape()) .clickable { onLinkClick(video.link) }, @@ -636,17 +649,17 @@ private fun TextComposer.appendTextChildren( } } - Spacer(modifier = Modifier.height(PADDING_HORIZONTAL.dp / 2)) + Spacer(modifier = Modifier.height(textHorizontalPadding().dp / 2)) Text( modifier = Modifier .fillMaxWidth() - .padding(horizontal = PADDING_HORIZONTAL.dp), + .padding(horizontal = textHorizontalPadding().dp), text = stringResource(R.string.touch_to_play_video), style = captionStyle(), ) - Spacer(modifier = Modifier.height(PADDING_HORIZONTAL.dp)) + Spacer(modifier = Modifier.height(textHorizontalPadding().dp)) } } } @@ -661,6 +674,7 @@ private fun TextComposer.appendTextChildren( appendTextChildren( nodes = element.childNodes(), preFormatted = preFormatted, + subheadUpperCase = subheadUpperCase, lazyListScope = lazyListScope, imagePlaceholder = imagePlaceholder, onLinkClick = onLinkClick, diff --git a/app/src/main/java/me/ash/reader/ui/component/reader/Reader.kt b/app/src/main/java/me/ash/reader/ui/component/reader/Reader.kt index 464369b..19155ed 100644 --- a/app/src/main/java/me/ash/reader/ui/component/reader/Reader.kt +++ b/app/src/main/java/me/ash/reader/ui/component/reader/Reader.kt @@ -30,12 +30,14 @@ import me.ash.reader.R @Suppress("FunctionName") fun LazyListScope.Reader( context: Context, + subheadUpperCase: Boolean = false, link: String, content: String, ) { Log.i("RLog", "Reader: ") htmlFormattedText( inputStream = content.byteInputStream(), + subheadUpperCase = subheadUpperCase, baseUrl = link, imagePlaceholder = R.drawable.ic_launcher_foreground, onLinkClick = { @@ -47,4 +49,4 @@ fun LazyListScope.Reader( ) } ) -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/ui/component/reader/Styles.kt b/app/src/main/java/me/ash/reader/ui/component/reader/Styles.kt index 84f3832..6699b0d 100644 --- a/app/src/main/java/me/ash/reader/ui/component/reader/Styles.kt +++ b/app/src/main/java/me/ash/reader/ui/component/reader/Styles.kt @@ -23,6 +23,8 @@ package me.ash.reader.ui.component.reader import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable +import androidx.compose.runtime.ReadOnlyComposable +import androidx.compose.runtime.Stable import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.TextStyle @@ -31,79 +33,162 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import me.ash.reader.data.model.preference.* import me.ash.reader.ui.ext.alphaLN -const val PADDING_HORIZONTAL = 24.0 const val MAX_CONTENT_WIDTH = 840.0 -val IMAGE_SHAPE = RoundedCornerShape(32.dp) +@Stable @Composable -fun bodyForeground(): Color = +@ReadOnlyComposable +fun imageHorizontalPadding(): Int = + LocalReadingImageHorizontalPadding.current + +@Stable +@Composable +@ReadOnlyComposable +fun imageShape(): RoundedCornerShape = + RoundedCornerShape(LocalReadingImageRoundedCorners.current.dp) + +@Stable +@Composable +@ReadOnlyComposable +fun onSurfaceColor(): Color = + MaterialTheme.colorScheme.onSurface + +@Stable +@Composable +@ReadOnlyComposable +fun onSurfaceVariantColor(): Color = MaterialTheme.colorScheme.onSurfaceVariant +@Stable @Composable +@ReadOnlyComposable +fun textHorizontalPadding(): Int = + LocalReadingTextHorizontalPadding.current + +@Stable +@Composable +@ReadOnlyComposable +fun bodyForeground(): Color = onSurfaceVariantColor() + +@Stable +@Composable +@ReadOnlyComposable fun bodyStyle(): TextStyle = - MaterialTheme.typography.bodyLarge.copy( + TextStyle( + fontFamily = LocalReadingFonts.current.asFontFamily(), + fontWeight = if (LocalReadingTextBold.current.value) FontWeight.SemiBold else FontWeight.Normal, + fontSize = LocalReadingTextFontSize.current.sp, + letterSpacing = LocalReadingLetterSpacing.current.sp, color = bodyForeground(), - textAlign = TextAlign.Start, + textAlign = LocalReadingTextAlign.current.toTextAlign(), ) +@Stable @Composable +@ReadOnlyComposable fun h1Style(): TextStyle = - MaterialTheme.typography.displayMedium.copy( - color = bodyForeground(), - textAlign = TextAlign.Start, + TextStyle( + fontFamily = LocalReadingFonts.current.asFontFamily(isDisplay = true), + fontWeight = if (LocalReadingSubheadBold.current.value) FontWeight.SemiBold else FontWeight.Normal, + fontSize = 28.sp, + letterSpacing = 0.sp, + color = onSurfaceColor(), + textAlign = LocalReadingSubheadAlign.current.toTextAlign(), ) +@Stable @Composable +@ReadOnlyComposable fun h2Style(): TextStyle = - MaterialTheme.typography.displaySmall.copy( - color = bodyForeground(), - textAlign = TextAlign.Start, + TextStyle( + fontFamily = LocalReadingFonts.current.asFontFamily(isDisplay = true), + fontWeight = if (LocalReadingSubheadBold.current.value) FontWeight.SemiBold else FontWeight.Normal, + fontSize = 28.sp, + letterSpacing = 0.sp, + color = onSurfaceColor(), + textAlign = LocalReadingSubheadAlign.current.toTextAlign(), ) +@Stable @Composable +@ReadOnlyComposable fun h3Style(): TextStyle = - MaterialTheme.typography.headlineLarge.copy( - color = bodyForeground(), - textAlign = TextAlign.Start, + TextStyle( + fontFamily = LocalReadingFonts.current.asFontFamily(isDisplay = true), + fontWeight = if (LocalReadingSubheadBold.current.value) FontWeight.SemiBold else FontWeight.Normal, + fontSize = 19.sp, + letterSpacing = 0.sp, + color = onSurfaceColor(), + textAlign = LocalReadingSubheadAlign.current.toTextAlign(), ) +@Stable @Composable +@ReadOnlyComposable fun h4Style(): TextStyle = - MaterialTheme.typography.headlineMedium.copy( - color = bodyForeground(), - textAlign = TextAlign.Start, + TextStyle( + fontFamily = LocalReadingFonts.current.asFontFamily(isDisplay = true), + fontWeight = if (LocalReadingSubheadBold.current.value) FontWeight.SemiBold else FontWeight.Normal, + fontSize = 17.sp, + letterSpacing = 0.sp, + color = onSurfaceColor(), + textAlign = LocalReadingSubheadAlign.current.toTextAlign(), ) +@Stable @Composable +@ReadOnlyComposable fun h5Style(): TextStyle = - MaterialTheme.typography.headlineSmall.copy( - color = bodyForeground(), - textAlign = TextAlign.Start, + TextStyle( + fontFamily = LocalReadingFonts.current.asFontFamily(isDisplay = true), + fontWeight = if (LocalReadingSubheadBold.current.value) FontWeight.SemiBold else FontWeight.Normal, + fontSize = 17.sp, + letterSpacing = 0.sp, + color = onSurfaceColor(), + textAlign = LocalReadingSubheadAlign.current.toTextAlign(), ) +@Stable @Composable +@ReadOnlyComposable fun h6Style(): TextStyle = - MaterialTheme.typography.titleLarge.copy( - color = bodyForeground(), - textAlign = TextAlign.Start, + TextStyle( + fontFamily = LocalReadingFonts.current.asFontFamily(isDisplay = true), + fontWeight = if (LocalReadingSubheadBold.current.value) FontWeight.SemiBold else FontWeight.Normal, + fontSize = 17.sp, + letterSpacing = 0.sp, + color = onSurfaceColor(), + textAlign = LocalReadingSubheadAlign.current.toTextAlign(), ) +@Stable @Composable +@ReadOnlyComposable fun captionStyle(): TextStyle = - MaterialTheme.typography.bodySmall.copy( - color = bodyForeground().copy(alpha = 0.6f), - textAlign = TextAlign.Center, + MaterialTheme.typography.bodySmall.merge( + TextStyle( + fontFamily = LocalReadingFonts.current.asFontFamily(), + color = bodyForeground().copy(alpha = 0.6f), + textAlign = TextAlign.Center, + ) ) +@Stable @Composable +@ReadOnlyComposable fun linkTextStyle(): TextStyle = TextStyle( - color = MaterialTheme.colorScheme.secondary, - textDecoration = TextDecoration.Underline + fontFamily = LocalReadingFonts.current.asFontFamily(), + fontSize = LocalReadingTextFontSize.current.sp, + color = MaterialTheme.colorScheme.primary, + textDecoration = TextDecoration.Underline, ) +@Stable @Composable fun codeBlockStyle(): TextStyle = MaterialTheme.typography.titleSmall.merge( @@ -113,21 +198,27 @@ fun codeBlockStyle(): TextStyle = ) ) +@Stable @Composable fun codeBlockBackground(): Color = MaterialTheme.colorScheme.secondary.copy(alpha = (0.dp).alphaLN(weight = 3.2f)) +@Stable @Composable -fun blockQuoteStyle(): SpanStyle = - MaterialTheme.typography.titleSmall.toSpanStyle().merge( +fun boldStyle(): TextStyle = + bodyStyle().merge( SpanStyle( - fontWeight = FontWeight.Light + fontWeight = FontWeight.SemiBold, + color = onSurfaceColor(), ) ) +@Stable @Composable fun codeInlineStyle(): SpanStyle = - MaterialTheme.typography.titleSmall.toSpanStyle().copy( - color = bodyForeground(), - fontFamily = FontFamily.Monospace, + MaterialTheme.typography.titleSmall.toSpanStyle().merge( + SpanStyle( + color = bodyForeground(), + fontFamily = FontFamily.Monospace, + ) ) 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 55cd3e1..c62c0b3 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 @@ -69,6 +69,7 @@ sealed class DataStoreKeys { abstract val key: Preferences.Key + // Version object IsFirstLaunch : DataStoreKeys() { override val key: Preferences.Key @@ -147,6 +148,7 @@ sealed class DataStoreKeys { get() = booleanPreferencesKey("amoledDarkTheme") } + // Feeds page object FeedsFilterBarStyle : DataStoreKeys() { override val key: Preferences.Key @@ -189,6 +191,7 @@ sealed class DataStoreKeys { get() = intPreferencesKey("feedsGroupListTonalElevation") } + // Flow page object FlowFilterBarStyle : DataStoreKeys() { override val key: Preferences.Key @@ -261,6 +264,122 @@ sealed class DataStoreKeys { get() = intPreferencesKey("flowArticleListTonalElevation") } + // Reading page + object ReadingDarkTheme : DataStoreKeys() { + + override val key: Preferences.Key + get() = intPreferencesKey("readingDarkTheme") + } + + object ReadingPageTonalElevation : DataStoreKeys() { + + override val key: Preferences.Key + get() = intPreferencesKey("ReadingPageTonalElevation") + } + + object ReadingTextFontSize : DataStoreKeys() { + + override val key: Preferences.Key + get() = intPreferencesKey("readingTextFontSize") + } + + object ReadingLetterSpacing : DataStoreKeys() { + + override val key: Preferences.Key + get() = doublePreferencesKey("readingLetterSpacing") + } + + object ReadingTextHorizontalPadding : DataStoreKeys() { + + override val key: Preferences.Key + get() = intPreferencesKey("readingTextHorizontalPadding") + } + + object ReadingTextBold : DataStoreKeys() { + + override val key: Preferences.Key + get() = booleanPreferencesKey("readingTextBold") + } + + object ReadingTextAlign : DataStoreKeys() { + + override val key: Preferences.Key + get() = intPreferencesKey("readingTextAlign") + } + + object ReadingTitleAlign : DataStoreKeys() { + + override val key: Preferences.Key + get() = intPreferencesKey("readingTitleAlign") + } + + object ReadingSubheadAlign : DataStoreKeys() { + + override val key: Preferences.Key + get() = intPreferencesKey("readingSubheadAlign") + } + + object ReadingTheme : DataStoreKeys() { + + override val key: Preferences.Key + get() = intPreferencesKey("readingTheme") + } + + object ReadingFonts : DataStoreKeys() { + + override val key: Preferences.Key + get() = intPreferencesKey("readingFonts") + } + + object ReadingAutoHideToolbar : DataStoreKeys() { + + override val key: Preferences.Key + get() = booleanPreferencesKey("readingAutoHideToolbar") + } + + object ReadingTitleBold : DataStoreKeys() { + + override val key: Preferences.Key + get() = booleanPreferencesKey("readingTitleBold") + } + + object ReadingSubheadBold : DataStoreKeys() { + + override val key: Preferences.Key + get() = booleanPreferencesKey("ReadingSubheadBold") + } + + object ReadingTitleUpperCase : DataStoreKeys() { + + override val key: Preferences.Key + get() = booleanPreferencesKey("readingTitleUpperCase") + } + + object ReadingSubheadUpperCase : DataStoreKeys() { + + override val key: Preferences.Key + get() = booleanPreferencesKey("ReadingSubheadUpperCase") + } + + object ReadingImageMaximize : DataStoreKeys() { + + override val key: Preferences.Key + get() = booleanPreferencesKey("readingImageMaximize") + } + + object ReadingImageHorizontalPadding : DataStoreKeys() { + + override val key: Preferences.Key + get() = intPreferencesKey("readingImageHorizontalPadding") + } + + object ReadingImageRoundedCorners : DataStoreKeys() { + + override val key: Preferences.Key + get() = intPreferencesKey("readingImageRoundedCorners") + } + + // Interaction object InitialPage : DataStoreKeys() { override val key: Preferences.Key @@ -273,6 +392,7 @@ sealed class DataStoreKeys { get() = intPreferencesKey("initialFilter") } + // Languages object Languages : DataStoreKeys() { override val key: Preferences.Key diff --git a/app/src/main/java/me/ash/reader/ui/page/common/HomeEntry.kt b/app/src/main/java/me/ash/reader/ui/page/common/HomeEntry.kt index 4f2da16..3808ad4 100644 --- a/app/src/main/java/me/ash/reader/ui/page/common/HomeEntry.kt +++ b/app/src/main/java/me/ash/reader/ui/page/common/HomeEntry.kt @@ -1,5 +1,6 @@ package me.ash.reader.ui.page.common +import android.util.Log import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.foundation.background import androidx.compose.material3.MaterialTheme @@ -12,8 +13,11 @@ import androidx.hilt.navigation.compose.hiltViewModel import com.google.accompanist.navigation.animation.AnimatedNavHost import com.google.accompanist.navigation.animation.rememberAnimatedNavController import com.google.accompanist.systemuicontroller.rememberSystemUiController +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.collectLatest import me.ash.reader.data.model.general.Filter import me.ash.reader.data.model.preference.LocalDarkTheme +import me.ash.reader.data.model.preference.LocalReadingDarkTheme import me.ash.reader.ui.ext.* import me.ash.reader.ui.page.home.HomeViewModel import me.ash.reader.ui.page.home.feeds.FeedsPage @@ -24,6 +28,7 @@ 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.feeds.FeedsPageStylePage import me.ash.reader.ui.page.settings.color.flow.FlowPageStylePage +import me.ash.reader.ui.page.settings.color.reading.* import me.ash.reader.ui.page.settings.interaction.InteractionPage import me.ash.reader.ui.page.settings.languages.LanguagesPage import me.ash.reader.ui.page.settings.tips.TipsAndSupportPage @@ -36,6 +41,7 @@ fun HomeEntry( homeViewModel: HomeViewModel = hiltViewModel(), ) { val context = LocalContext.current + var isReadingPage by rememberSaveable { mutableStateOf(false) } val filterUiState = homeViewModel.filterUiState.collectAsStateValue() val navController = rememberAnimatedNavController() @@ -47,6 +53,11 @@ fun HomeEntry( } LaunchedEffect(Unit) { + navController.currentBackStackEntryFlow.collectLatest { + Log.i("RLog", "isReadingPage: ${navController.currentDestination?.route}") + delay(310L) + isReadingPage = navController.currentDestination?.route == "${RouteName.READING}/{articleId}" + } when (context.initialPage) { 1 -> { navController.navigate(RouteName.FLOW) { @@ -80,9 +91,16 @@ fun HomeEntry( } } - val useDarkTheme = LocalDarkTheme.current.isDarkTheme() + val useDarkTheme = if (isReadingPage) { + LocalReadingDarkTheme.current.isDarkTheme() + } else { + LocalDarkTheme.current.isDarkTheme() + } - AppTheme(useDarkTheme = useDarkTheme) { + AppTheme( + useDarkTheme = if (isReadingPage) LocalReadingDarkTheme.current.isDarkTheme() + else LocalDarkTheme.current.isDarkTheme() + ) { rememberSystemUiController().run { setStatusBarColor(Color.Transparent, !useDarkTheme) @@ -132,6 +150,24 @@ fun HomeEntry( animatedComposable(route = RouteName.FLOW_PAGE_STYLE) { FlowPageStylePage(navController) } + animatedComposable(route = RouteName.READING_PAGE_STYLE) { + ReadingStylePage(navController) + } + animatedComposable(route = RouteName.READING_DARK_THEME) { + ReadingDarkThemePage(navController) + } + animatedComposable(route = RouteName.READING_PAGE_TITLE) { + ReadingTitlePage(navController) + } + animatedComposable(route = RouteName.READING_PAGE_TEXT) { + ReadingTextPage(navController) + } + animatedComposable(route = RouteName.READING_PAGE_IMAGE) { + ReadingImagePage(navController) + } + animatedComposable(route = RouteName.READING_PAGE_VIDEO) { + ReadingVideoPage(navController) + } // Interaction animatedComposable(route = RouteName.INTERACTION) { diff --git a/app/src/main/java/me/ash/reader/ui/page/common/RouteName.kt b/app/src/main/java/me/ash/reader/ui/page/common/RouteName.kt index 1d7be0c..d735c23 100644 --- a/app/src/main/java/me/ash/reader/ui/page/common/RouteName.kt +++ b/app/src/main/java/me/ash/reader/ui/page/common/RouteName.kt @@ -18,6 +18,12 @@ object RouteName { const val DARK_THEME = "dark_theme" const val FEEDS_PAGE_STYLE = "feeds_page_style" const val FLOW_PAGE_STYLE = "flow_page_style" + const val READING_PAGE_STYLE = "reading_page_style" + const val READING_DARK_THEME = "reading_dark_theme" + const val READING_PAGE_TITLE = "reading_page_title" + const val READING_PAGE_TEXT = "reading_page_text" + const val READING_PAGE_IMAGE = "reading_page_image" + const val READING_PAGE_VIDEO = "reading_page_video" // Interaction const val INTERACTION = "interaction" diff --git a/app/src/main/java/me/ash/reader/ui/page/home/reading/BottomBar.kt b/app/src/main/java/me/ash/reader/ui/page/home/reading/BottomBar.kt index 0dbead5..e7534e7 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/reading/BottomBar.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/reading/BottomBar.kt @@ -22,6 +22,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex import me.ash.reader.R +import me.ash.reader.data.model.preference.LocalReadingPageTonalElevation import me.ash.reader.ui.component.base.CanBeDisabledIconButton @Composable @@ -34,6 +35,8 @@ fun BottomBar( onStarred: (isStarred: Boolean) -> Unit = {}, onFullContent: (isFullContent: Boolean) -> Unit = {}, ) { + val tonalElevation = LocalReadingPageTonalElevation.current + Box( modifier = Modifier .fillMaxSize() @@ -43,7 +46,10 @@ fun BottomBar( RYExtensibleVisibility(visible = isShow) { val view = LocalView.current - Surface(modifier = Modifier.navigationBarsPadding()) { + Surface( + modifier = Modifier.navigationBarsPadding(), + tonalElevation = tonalElevation.value.dp, + ) { // TODO: Component styles await refactoring Row( modifier = Modifier @@ -128,4 +134,4 @@ fun BottomBar( } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/ui/page/home/reading/Content.kt b/app/src/main/java/me/ash/reader/ui/page/home/reading/Content.kt index b1883ad..c27f509 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/reading/Content.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/reading/Content.kt @@ -13,6 +13,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp +import me.ash.reader.data.model.preference.LocalReadingSubheadUpperCase import me.ash.reader.ui.component.reader.Reader import me.ash.reader.ui.ext.drawVerticalScrollbar import java.util.* @@ -30,6 +31,7 @@ fun Content( isShowToolBar: Boolean, ) { val context = LocalContext.current + val subheadUpperCase = LocalReadingSubheadUpperCase.current SelectionContainer { LazyColumn( @@ -56,7 +58,7 @@ fun Content( .padding(horizontal = 12.dp) ) { DisableSelection { - Header( + Metadata( feedName = feedName, title = title, author = author, @@ -88,6 +90,7 @@ fun Content( if (!isLoading) { Reader( context = context, + subheadUpperCase = subheadUpperCase.value, link = link ?: "", content = content ) diff --git a/app/src/main/java/me/ash/reader/ui/page/home/reading/Header.kt b/app/src/main/java/me/ash/reader/ui/page/home/reading/Header.kt deleted file mode 100644 index 281ecf1..0000000 --- a/app/src/main/java/me/ash/reader/ui/page/home/reading/Header.kt +++ /dev/null @@ -1,73 +0,0 @@ -package me.ash.reader.ui.page.home.reading - -import androidx.compose.foundation.layout.* -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.alpha -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp -import me.ash.reader.ui.ext.formatAsString -import me.ash.reader.ui.ext.openURL -import me.ash.reader.ui.ext.roundClick -import java.util.* - -@Composable -fun Header( - feedName: String, - title: String, - author: String? = null, - link: String? = null, - publishedDate: Date, -) { - val context = LocalContext.current - val dateString = remember(publishedDate) { - publishedDate.formatAsString(context, atHourMinute = true) - } - - Column( - modifier = Modifier - .fillMaxWidth() - .roundClick { - context.openURL(link) - } - .padding(12.dp) - ) { - Text( - modifier = Modifier.alpha(0.7f), - text = dateString, - color = MaterialTheme.colorScheme.outline, - style = MaterialTheme.typography.labelMedium, - textAlign = TextAlign.Start, - ) - Spacer(modifier = Modifier.height(4.dp)) - Text( - text = title, - color = MaterialTheme.colorScheme.onSurface, - style = MaterialTheme.typography.headlineLarge, - textAlign = TextAlign.Start, - ) - Spacer(modifier = Modifier.height(4.dp)) - author?.let { - if (it.isNotEmpty()) { - Text( - modifier = Modifier.alpha(0.7f), - text = it, - color = MaterialTheme.colorScheme.outline, - style = MaterialTheme.typography.labelMedium, - textAlign = TextAlign.Start, - ) - } - } - Text( - modifier = Modifier.alpha(0.7f), - text = feedName, - color = MaterialTheme.colorScheme.outline, - style = MaterialTheme.typography.labelMedium, - textAlign = TextAlign.Start, - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/ui/page/home/reading/Metadata.kt b/app/src/main/java/me/ash/reader/ui/page/home/reading/Metadata.kt new file mode 100644 index 0000000..8c5a7ec --- /dev/null +++ b/app/src/main/java/me/ash/reader/ui/page/home/reading/Metadata.kt @@ -0,0 +1,100 @@ +package me.ash.reader.ui.page.home.reading + +import androidx.compose.foundation.layout.* +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import me.ash.reader.data.model.preference.LocalReadingFonts +import me.ash.reader.data.model.preference.LocalReadingTitleAlign +import me.ash.reader.data.model.preference.LocalReadingTitleBold +import me.ash.reader.data.model.preference.LocalReadingTitleUpperCase +import me.ash.reader.ui.ext.formatAsString +import me.ash.reader.ui.ext.openURL +import me.ash.reader.ui.ext.roundClick +import java.util.* + +@Composable +fun Metadata( + feedName: String, + title: String, + author: String? = null, + link: String? = null, + publishedDate: Date, +) { + val context = LocalContext.current + val titleBold = LocalReadingTitleBold.current + val titleUpperCase = LocalReadingTitleUpperCase.current + val titleAlign = LocalReadingTitleAlign.current + val dateString = remember(publishedDate) { + publishedDate.formatAsString(context, atHourMinute = true) + } + + val titleUpperCaseString by remember { derivedStateOf { title.uppercase() } } + + Column( + modifier = Modifier + .fillMaxWidth() + .roundClick { + context.openURL(link) + } + .padding(12.dp) + ) { + Text( + modifier = Modifier + .alpha(0.7f) + .fillMaxWidth(), + text = dateString, + color = MaterialTheme.colorScheme.outline, + style = MaterialTheme.typography.labelMedium.copy( + fontFamily = LocalReadingFonts.current.asFontFamily(), + ), + textAlign = titleAlign.toTextAlign(), + ) + Spacer(modifier = Modifier.height(4.dp)) + Text( + modifier = Modifier.fillMaxWidth(), + text = if (titleUpperCase.value) titleUpperCaseString else title, + color = MaterialTheme.colorScheme.onSurface, + style = MaterialTheme.typography.headlineLarge.copy( + fontFamily = LocalReadingFonts.current.asFontFamily(isDisplay = true), + fontWeight = if (titleBold.value) FontWeight.SemiBold else FontWeight.Normal, + ), + textAlign = titleAlign.toTextAlign(), + ) + Spacer(modifier = Modifier.height(4.dp)) + author?.let { + if (it.isNotEmpty()) { + Text( + modifier = Modifier + .alpha(0.7f) + .fillMaxWidth(), + text = it, + color = MaterialTheme.colorScheme.outline, + style = MaterialTheme.typography.labelMedium.copy( + fontFamily = LocalReadingFonts.current.asFontFamily(), + ), + textAlign = titleAlign.toTextAlign(), + ) + } + } + Text( + modifier = Modifier + .alpha(0.7f) + .fillMaxWidth(), + text = feedName, + color = MaterialTheme.colorScheme.outline, + style = MaterialTheme.typography.labelMedium.copy( + fontFamily = LocalReadingFonts.current.asFontFamily(), + ), + textAlign = titleAlign.toTextAlign(), + ) + } +} 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 d198734..d6e9c3a 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 @@ -6,8 +6,11 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavHostController +import me.ash.reader.data.model.preference.LocalReadingAutoHideToolbar +import me.ash.reader.data.model.preference.LocalReadingPageTonalElevation import me.ash.reader.ui.component.base.RYScaffold import me.ash.reader.ui.ext.collectAsStateValue import me.ash.reader.ui.ext.isScrollDown @@ -17,9 +20,13 @@ fun ReadingPage( navController: NavHostController, readingViewModel: ReadingViewModel = hiltViewModel(), ) { + val tonalElevation = LocalReadingPageTonalElevation.current val readingUiState = readingViewModel.readingUiState.collectAsStateValue() - val isShowToolBar = + val isShowToolBar = if (LocalReadingAutoHideToolbar.current.value) { readingUiState.articleWithFeed != null && !readingUiState.listState.isScrollDown() + } else { + true + } LaunchedEffect(Unit) { navController.currentBackStackEntryFlow.collect { @@ -39,12 +46,15 @@ fun ReadingPage( } RYScaffold( + topBarTonalElevation = tonalElevation.value.dp, + containerTonalElevation = tonalElevation.value.dp, content = { Log.i("RLog", "TopBar: recomposition") Box(modifier = Modifier.fillMaxSize()) { // Top Bar TopBar( + navController = navController, isShow = isShowToolBar, title = readingUiState.articleWithFeed?.article?.title, link = readingUiState.articleWithFeed?.article?.link, diff --git a/app/src/main/java/me/ash/reader/ui/page/home/reading/TopBar.kt b/app/src/main/java/me/ash/reader/ui/page/home/reading/TopBar.kt index 4d88925..a87dbf0 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/reading/TopBar.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/reading/TopBar.kt @@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Palette import androidx.compose.material.icons.outlined.Share import androidx.compose.material.icons.rounded.Close import androidx.compose.material3.MaterialTheme @@ -18,18 +19,24 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex +import androidx.navigation.NavHostController import me.ash.reader.R +import me.ash.reader.data.model.preference.LocalReadingPageTonalElevation import me.ash.reader.ui.component.base.FeedbackIconButton import me.ash.reader.ui.ext.share +import me.ash.reader.ui.ext.surfaceColorAtElevation +import me.ash.reader.ui.page.common.RouteName @Composable fun TopBar( + navController: NavHostController, isShow: Boolean, title: String? = "", link: String? = "", onClose: () -> Unit = {}, ) { val context = LocalContext.current + val tonalElevation = LocalReadingPageTonalElevation.current Box( modifier = Modifier @@ -41,7 +48,9 @@ fun TopBar( SmallTopAppBar( modifier = Modifier.statusBarsPadding(), colors = TopAppBarDefaults.smallTopAppBarColors( - containerColor = MaterialTheme.colorScheme.surface, + containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation( + elevation = tonalElevation.value.dp + ), ), title = {}, navigationIcon = { @@ -54,6 +63,16 @@ fun TopBar( } }, actions = { + FeedbackIconButton( + modifier = Modifier.size(22.dp), + imageVector = Icons.Outlined.Palette, + contentDescription = stringResource(R.string.style), + tint = MaterialTheme.colorScheme.onSurface + ) { + navController.navigate(RouteName.READING_PAGE_STYLE) { + launchSingleTop = true + } + } FeedbackIconButton( modifier = Modifier.size(20.dp), imageVector = Icons.Outlined.Share, @@ -69,4 +88,4 @@ fun TopBar( ) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/me/ash/reader/ui/page/home/reading/drawer/FeedOptionDrawer.kt b/app/src/main/java/me/ash/reader/ui/page/home/reading/drawer/FeedOptionDrawer.kt new file mode 100644 index 0000000..a4b3470 --- /dev/null +++ b/app/src/main/java/me/ash/reader/ui/page/home/reading/drawer/FeedOptionDrawer.kt @@ -0,0 +1,71 @@ +package me.ash.reader.ui.page.home.reading.drawer + +import androidx.activity.compose.BackHandler +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material3.Tab +import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.hilt.navigation.compose.hiltViewModel +import kotlinx.coroutines.launch +import me.ash.reader.R +import me.ash.reader.ui.component.base.BottomDrawer +import me.ash.reader.ui.ext.collectAsStateValue +import me.ash.reader.ui.page.home.feeds.drawer.feed.FeedOptionViewModel + +@OptIn(ExperimentalMaterialApi::class) +@Composable +fun StyleOptionDrawer( + feedOptionViewModel: FeedOptionViewModel = hiltViewModel(), + content: @Composable () -> Unit = {}, +) { + val context = LocalContext.current + val view = LocalView.current + val scope = rememberCoroutineScope() + val feedOptionUiState = feedOptionViewModel.feedOptionUiState.collectAsStateValue() + val feed = feedOptionUiState.feed + val toastString = stringResource(R.string.rename_toast, feedOptionUiState.newName) + + BackHandler(feedOptionUiState.drawerState.isVisible) { + scope.launch { + feedOptionUiState.drawerState.hide() + } + } + + BottomDrawer( + drawerState = feedOptionUiState.drawerState, + sheetContent = { + Info() + } + ) { + content() + } +} + +@Composable +fun Info() { + Column(modifier = Modifier.navigationBarsPadding()) { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Tab(selected = true, onClick = { /*TODO*/ }) + } + } +} + +@Preview +@Composable +fun Prev() { + Tab(selected = true, onClick = { /*TODO*/ }) +} diff --git a/app/src/main/java/me/ash/reader/ui/page/settings/SettingsPage.kt b/app/src/main/java/me/ash/reader/ui/page/settings/SettingsPage.kt index 5f5ab3d..461d83f 100644 --- a/app/src/main/java/me/ash/reader/ui/page/settings/SettingsPage.kt +++ b/app/src/main/java/me/ash/reader/ui/page/settings/SettingsPage.kt @@ -140,6 +140,7 @@ fun SettingsPage( } } item { + Spacer(modifier = Modifier.height(24.dp)) Spacer(modifier = Modifier.windowInsetsBottomHeight(WindowInsets.navigationBars)) } } diff --git a/app/src/main/java/me/ash/reader/ui/page/settings/color/ColorAndStylePage.kt b/app/src/main/java/me/ash/reader/ui/page/settings/color/ColorAndStylePage.kt index bede78c..c498ac4 100644 --- a/app/src/main/java/me/ash/reader/ui/page/settings/color/ColorAndStylePage.kt +++ b/app/src/main/java/me/ash/reader/ui/page/settings/color/ColorAndStylePage.kt @@ -181,11 +181,15 @@ fun ColorAndStylePage( ) {} SettingItem( title = stringResource(R.string.reading_page), - enable = false, - onClick = {}, + onClick = { + navController.navigate(RouteName.READING_PAGE_STYLE) { + launchSingleTop = true + } + }, ) {} } item { + Spacer(modifier = Modifier.height(24.dp)) Spacer(modifier = Modifier.windowInsetsBottomHeight(WindowInsets.navigationBars)) } } diff --git a/app/src/main/java/me/ash/reader/ui/page/settings/color/DarkThemePage.kt b/app/src/main/java/me/ash/reader/ui/page/settings/color/DarkThemePage.kt index c88c07e..e558edf 100644 --- a/app/src/main/java/me/ash/reader/ui/page/settings/color/DarkThemePage.kt +++ b/app/src/main/java/me/ash/reader/ui/page/settings/color/DarkThemePage.kt @@ -78,6 +78,7 @@ fun DarkThemePage( } } item { + Spacer(modifier = Modifier.height(24.dp)) Spacer(modifier = Modifier.windowInsetsBottomHeight(WindowInsets.navigationBars)) } } diff --git a/app/src/main/java/me/ash/reader/ui/page/settings/color/feeds/FeedsPageStylePage.kt b/app/src/main/java/me/ash/reader/ui/page/settings/color/feeds/FeedsPageStylePage.kt index b0ec6cb..73e24cf 100644 --- a/app/src/main/java/me/ash/reader/ui/page/settings/color/feeds/FeedsPageStylePage.kt +++ b/app/src/main/java/me/ash/reader/ui/page/settings/color/feeds/FeedsPageStylePage.kt @@ -158,7 +158,7 @@ fun FeedsPageStylePage( } } SettingItem( - title = stringResource(R.string.padding_on_both_ends), + title = stringResource(R.string.horizontal_padding), desc = "${filterBarPadding}dp", onClick = { filterBarPaddingValue = filterBarPadding @@ -197,7 +197,7 @@ fun FeedsPageStylePage( TextFieldDialog( visible = filterBarPaddingDialogVisible, - title = stringResource(R.string.padding_on_both_ends), + title = stringResource(R.string.horizontal_padding), value = (filterBarPaddingValue ?: "").toString(), placeholder = stringResource(R.string.value), onValueChange = { diff --git a/app/src/main/java/me/ash/reader/ui/page/settings/color/flow/FlowPageStylePage.kt b/app/src/main/java/me/ash/reader/ui/page/settings/color/flow/FlowPageStylePage.kt index 434f921..c942a1f 100644 --- a/app/src/main/java/me/ash/reader/ui/page/settings/color/flow/FlowPageStylePage.kt +++ b/app/src/main/java/me/ash/reader/ui/page/settings/color/flow/FlowPageStylePage.kt @@ -218,7 +218,7 @@ fun FlowPageStylePage( } } SettingItem( - title = stringResource(R.string.padding_on_both_ends), + title = stringResource(R.string.horizontal_padding), desc = "${filterBarPadding}dp", onClick = { filterBarPaddingValue = filterBarPadding @@ -234,6 +234,7 @@ fun FlowPageStylePage( ) {} } item { + Spacer(modifier = Modifier.height(24.dp)) Spacer(modifier = Modifier.windowInsetsBottomHeight(WindowInsets.navigationBars)) } } @@ -257,7 +258,7 @@ fun FlowPageStylePage( TextFieldDialog( visible = filterBarPaddingDialogVisible, - title = stringResource(R.string.padding_on_both_ends), + title = stringResource(R.string.horizontal_padding), value = (filterBarPaddingValue ?: "").toString(), placeholder = stringResource(R.string.value), onValueChange = { diff --git a/app/src/main/java/me/ash/reader/ui/page/settings/color/reading/ReadingDarkThemePage.kt b/app/src/main/java/me/ash/reader/ui/page/settings/color/reading/ReadingDarkThemePage.kt new file mode 100644 index 0000000..dffbbc5 --- /dev/null +++ b/app/src/main/java/me/ash/reader/ui/page/settings/color/reading/ReadingDarkThemePage.kt @@ -0,0 +1,72 @@ +package me.ash.reader.ui.page.settings.color.reading + +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.ArrowBack +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.RadioButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.navigation.NavHostController +import me.ash.reader.R +import me.ash.reader.data.model.preference.LocalReadingDarkTheme +import me.ash.reader.data.model.preference.ReadingDarkThemePreference +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.page.settings.SettingItem +import me.ash.reader.ui.theme.palette.onLight + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ReadingDarkThemePage( + navController: NavHostController, +) { + val context = LocalContext.current + val darkTheme = LocalReadingDarkTheme.current + val scope = rememberCoroutineScope() + + RYScaffold( + containerColor = MaterialTheme.colorScheme.surface onLight MaterialTheme.colorScheme.inverseOnSurface, + navigationIcon = { + FeedbackIconButton( + imageVector = Icons.Rounded.ArrowBack, + contentDescription = stringResource(R.string.back), + tint = MaterialTheme.colorScheme.onSurface + ) { + navController.popBackStack() + } + }, + content = { + LazyColumn { + item { + DisplayText(text = stringResource(R.string.dark_theme), desc = "") + } + item { + ReadingDarkThemePreference.values.map { + SettingItem( + title = it.toDesc(context), + onClick = { + it.put(context, scope) + }, + ) { + RadioButton(selected = it == darkTheme, onClick = { + it.put(context, scope) + }) + } + } + } + item { + Spacer(modifier = Modifier.height(24.dp)) + Spacer(modifier = Modifier.windowInsetsBottomHeight(WindowInsets.navigationBars)) + } + } + } + ) +} diff --git a/app/src/main/java/me/ash/reader/ui/page/settings/color/reading/ReadingImagePage.kt b/app/src/main/java/me/ash/reader/ui/page/settings/color/reading/ReadingImagePage.kt new file mode 100644 index 0000000..a259019 --- /dev/null +++ b/app/src/main/java/me/ash/reader/ui/page/settings/color/reading/ReadingImagePage.kt @@ -0,0 +1,158 @@ +package me.ash.reader.ui.page.settings.color.reading + +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.ArrowBack +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.navigation.NavHostController +import me.ash.reader.R +import me.ash.reader.data.model.preference.* +import me.ash.reader.ui.component.base.* +import me.ash.reader.ui.page.settings.SettingItem +import me.ash.reader.ui.theme.palette.onLight + +@Composable +fun ReadingImagePage( + navController: NavHostController, +) { + val context = LocalContext.current + val scope = rememberCoroutineScope() + + val readingTheme = LocalReadingTheme.current + val roundedCorners = LocalReadingImageRoundedCorners.current + val horizontalPadding = LocalReadingImageHorizontalPadding.current + val maximize = LocalReadingImageMaximize.current + + var roundedCornersDialogVisible by remember { mutableStateOf(false) } + var horizontalPaddingDialogVisible by remember { mutableStateOf(false) } + + var roundedCornersValue: Int? by remember { mutableStateOf(roundedCorners) } + var horizontalPaddingValue: Int? by remember { mutableStateOf(horizontalPadding) } + + RYScaffold( + containerColor = MaterialTheme.colorScheme.surface onLight MaterialTheme.colorScheme.inverseOnSurface, + navigationIcon = { + FeedbackIconButton( + imageVector = Icons.Rounded.ArrowBack, + contentDescription = stringResource(R.string.back), + tint = MaterialTheme.colorScheme.onSurface + ) { + navController.popBackStack() + } + }, + content = { + LazyColumn { + item { + DisplayText(text = stringResource(R.string.images), desc = "") + } + + // Preview + item { +// Row( +// modifier = Modifier +// .fillMaxWidth() +// .padding(horizontal = 24.dp) +// .clip(RoundedCornerShape(24.dp)) +// .background( +// MaterialTheme.colorScheme.inverseOnSurface +// onLight MaterialTheme.colorScheme.surface.copy(0.7f) +// ) +// .clickable { }, +// horizontalArrangement = Arrangement.Center, +// verticalAlignment = Alignment.CenterVertically +// ) { +// RYAsyncImage( +// modifier = Modifier +// .fillMaxSize() +// .padding(24.dp) +// .padding(imageHorizontalPadding().dp) +// .clip(imageShape()), +// data = "https://images.unsplash.com/photo-1544716278-ca5e3f4abd8c?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1yZWxhdGVkfDJ8fHxlbnwwfHx8fA%3D%3D&auto=format&fit=crop&w=800&q=60", +// contentDescription = stringResource(R.string.images), +// contentScale = ContentScale.Inside, +// ) +// } + Spacer(modifier = Modifier.height(24.dp)) + } + + + // Images + item { + Subtitle( + modifier = Modifier.padding(horizontal = 24.dp), + text = stringResource(R.string.images) + ) + SettingItem( + title = stringResource(R.string.rounded_corners), + desc = "${roundedCorners}dp", + onClick = { roundedCornersDialogVisible = true }, + ) {} + SettingItem( + title = stringResource(R.string.horizontal_padding), + desc = "${horizontalPadding}dp", + onClick = { horizontalPaddingDialogVisible = true }, + ) {} + SettingItem( + title = stringResource(R.string.maximize), + onClick = { + (!maximize).put(context, scope) + ReadingThemePreference.Custom.put(context, scope) + }, + ) { + RYSwitch(activated = maximize.value) { + (!maximize).put(context, scope) + ReadingThemePreference.Custom.put(context, scope) + } + } + } + + item { + Spacer(modifier = Modifier.height(24.dp)) + Spacer(modifier = Modifier.windowInsetsBottomHeight(WindowInsets.navigationBars)) + } + } + } + ) + + TextFieldDialog( + visible = roundedCornersDialogVisible, + title = stringResource(R.string.rounded_corners), + value = (roundedCornersValue ?: "").toString(), + placeholder = stringResource(R.string.value), + onValueChange = { + roundedCornersValue = it.filter { it.isDigit() }.toIntOrNull() + }, + onDismissRequest = { + roundedCornersDialogVisible = false + }, + onConfirm = { + ReadingImageRoundedCornersPreference.put(context, scope, roundedCornersValue ?: 0) + ReadingThemePreference.Custom.put(context, scope) + roundedCornersDialogVisible = false + } + ) + + TextFieldDialog( + visible = horizontalPaddingDialogVisible, + title = stringResource(R.string.horizontal_padding), + value = (horizontalPaddingValue ?: "").toString(), + placeholder = stringResource(R.string.value), + onValueChange = { + horizontalPaddingValue = it.filter { it.isDigit() }.toIntOrNull() + }, + onDismissRequest = { + horizontalPaddingDialogVisible = false + }, + onConfirm = { + ReadingImageHorizontalPaddingPreference.put(context, scope, horizontalPaddingValue ?: 0) + ReadingThemePreference.Custom.put(context, scope) + horizontalPaddingDialogVisible = false + } + ) +} 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 new file mode 100644 index 0000000..e3fb5f7 --- /dev/null +++ b/app/src/main/java/me/ash/reader/ui/page/settings/color/reading/ReadingStylePage.kt @@ -0,0 +1,259 @@ +package me.ash.reader.ui.page.settings.color.reading + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.horizontalScroll +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Image +import androidx.compose.material.icons.outlined.Movie +import androidx.compose.material.icons.rounded.ArrowBack +import androidx.compose.material.icons.rounded.Segment +import androidx.compose.material.icons.rounded.Title +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.unit.dp +import androidx.navigation.NavHostController +import me.ash.reader.R +import me.ash.reader.data.model.preference.* +import me.ash.reader.ui.component.ReadingThemePrev +import me.ash.reader.ui.component.base.* +import me.ash.reader.ui.page.common.RouteName +import me.ash.reader.ui.page.settings.SettingItem +import me.ash.reader.ui.theme.palette.onLight + +@Composable +fun ReadingStylePage( + navController: NavHostController, +) { + val context = LocalContext.current + val scope = rememberCoroutineScope() + + val readingTheme = LocalReadingTheme.current + val darkTheme = LocalReadingDarkTheme.current + val darkThemeNot = !darkTheme + val tonalElevation = LocalReadingPageTonalElevation.current + val fonts = LocalReadingFonts.current + val autoHideToolbar = LocalReadingAutoHideToolbar.current + + var tonalElevationDialogVisible by remember { mutableStateOf(false) } + var fontsDialogVisible by remember { mutableStateOf(false) } + + RYScaffold( + containerColor = MaterialTheme.colorScheme.surface onLight MaterialTheme.colorScheme.inverseOnSurface, + navigationIcon = { + FeedbackIconButton( + imageVector = Icons.Rounded.ArrowBack, + contentDescription = stringResource(R.string.back), + tint = MaterialTheme.colorScheme.onSurface + ) { + navController.popBackStack() + } + }, + content = { + LazyColumn { + item { + DisplayText(text = stringResource(R.string.reading_page), desc = "") + } + + // Preview + item { + Row(modifier = Modifier.horizontalScroll(rememberScrollState()) + ) { + Spacer(modifier = Modifier.width(24.dp)) + ReadingThemePreference.values.map { + if (readingTheme == ReadingThemePreference.Custom || it != ReadingThemePreference.Custom) { + ReadingThemePrev(selected = readingTheme, theme = it) { + it.put(context, scope) + it.applyTheme(context, scope) + } + } else { + Spacer(modifier = Modifier.width(150.dp)) + } + Spacer(modifier = Modifier.width(8.dp)) + } + Spacer(modifier = Modifier.width((24 - 8).dp)) + } + + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 24.dp) + .clip(RoundedCornerShape(24.dp)) + .background( + MaterialTheme.colorScheme.inverseOnSurface + onLight MaterialTheme.colorScheme.surface.copy(0.7f) + ) + .clickable { }, + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically + ) { + + } + Spacer(modifier = Modifier.height(24.dp)) + } + + // General + item { + Subtitle( + modifier = Modifier.padding(horizontal = 24.dp), + text = stringResource(R.string.general) + ) + SettingItem( + title = stringResource(R.string.reading_fonts), + desc = fonts.toDesc(context), + onClick = { fontsDialogVisible = true }, + ) {} + SettingItem( + title = stringResource(R.string.dark_reading_theme), + desc = darkTheme.toDesc(context), + separatedActions = true, + onClick = { + navController.navigate(RouteName.READING_DARK_THEME) { + launchSingleTop = true + } + }, + ) { + RYSwitch( + activated = darkTheme.isDarkTheme() + ) { + darkThemeNot.put(context, scope) + } + } + SettingItem( + title = stringResource(R.string.bionic_reading), + separatedActions = true, + enable = false, + onClick = { +// (!articleListDesc).put(context, scope) + }, + ) { + RYSwitch( + activated = false, + enable = false, + ) { +// (!articleListDesc).put(context, scope) + } + } + SettingItem( + title = stringResource(R.string.auto_hide_toolbars), + onClick = { + (!autoHideToolbar).put(context, scope) + }, + ) { + RYSwitch(activated = autoHideToolbar.value) { + (!autoHideToolbar).put(context, scope) + } + } + SettingItem( + title = stringResource(R.string.rearrange_buttons), + enable = false, + onClick = {}, + ) {} + SettingItem( + title = stringResource(R.string.tonal_elevation), + desc = "${tonalElevation.value}dp", + onClick = { + tonalElevationDialogVisible = true + }, + ) {} + Spacer(modifier = Modifier.height(24.dp)) + } + + // Advanced + item { + Subtitle( + modifier = Modifier.padding(horizontal = 24.dp), + text = stringResource(R.string.advanced) + ) + SettingItem( + title = stringResource(R.string.title), + desc = stringResource(R.string.title_desc), + icon = Icons.Rounded.Title, + onClick = { + navController.navigate(RouteName.READING_PAGE_TITLE) { + launchSingleTop = true + } + }, + ) {} + SettingItem( + title = stringResource(R.string.text), + desc = stringResource(R.string.text_desc), + icon = Icons.Rounded.Segment, + onClick = { + navController.navigate(RouteName.READING_PAGE_TEXT) { + launchSingleTop = true + } + }, + ) {} + SettingItem( + title = stringResource(R.string.images), + desc = stringResource(R.string.images_desc), + icon = Icons.Outlined.Image, + onClick = { + navController.navigate(RouteName.READING_PAGE_IMAGE) { + launchSingleTop = true + } + }, + ) {} + SettingItem( + title = stringResource(R.string.videos), + desc = stringResource(R.string.videos_desc), + icon = Icons.Outlined.Movie, + enable = false, + onClick = { +// navController.navigate(RouteName.READING_PAGE_VIDEO) { +// launchSingleTop = true +// } + }, + ) {} + } + + item { + Spacer(modifier = Modifier.height(24.dp)) + Spacer(modifier = Modifier.windowInsetsBottomHeight(WindowInsets.navigationBars)) + } + } + } + ) + + RadioDialog( + visible = tonalElevationDialogVisible, + title = stringResource(R.string.tonal_elevation), + options = ReadingPageTonalElevationPreference.values.map { + RadioDialogOption( + text = it.toDesc(context), + selected = it == tonalElevation, + ) { + it.put(context, scope) + } + } + ) { + tonalElevationDialogVisible = false + } + + RadioDialog( + visible = fontsDialogVisible, + title = stringResource(R.string.reading_fonts), + options = ReadingFontsPreference.values.map { + RadioDialogOption( + text = it.toDesc(context), + style = TextStyle(fontFamily = it.asFontFamily()), + selected = it == fonts, + ) { + it.put(context, scope) + } + } + ) { + fontsDialogVisible = false + } +} diff --git a/app/src/main/java/me/ash/reader/ui/page/settings/color/reading/ReadingTextPage.kt b/app/src/main/java/me/ash/reader/ui/page/settings/color/reading/ReadingTextPage.kt new file mode 100644 index 0000000..aeb4d63 --- /dev/null +++ b/app/src/main/java/me/ash/reader/ui/page/settings/color/reading/ReadingTextPage.kt @@ -0,0 +1,202 @@ +package me.ash.reader.ui.page.settings.color.reading + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.ArrowBack +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.navigation.NavHostController +import me.ash.reader.R +import me.ash.reader.data.model.preference.* +import me.ash.reader.ui.component.base.* +import me.ash.reader.ui.page.settings.SettingItem +import me.ash.reader.ui.theme.palette.onLight + +@Composable +fun ReadingTextPage( + navController: NavHostController, +) { + val context = LocalContext.current + val scope = rememberCoroutineScope() + + val readingTheme = LocalReadingTheme.current + val fontSize = LocalReadingTextFontSize.current + val letterSpacing = LocalReadingLetterSpacing.current + val horizontalPadding = LocalReadingTextHorizontalPadding.current + val align = LocalReadingTextAlign.current + val bold = LocalReadingTextBold.current + + var fontSizeDialogVisible by remember { mutableStateOf(false) } + var letterSpacingDialogVisible by remember { mutableStateOf(false) } + var horizontalPaddingDialogVisible by remember { mutableStateOf(false) } + var alignDialogVisible by remember { mutableStateOf(false) } + + var fontSizeValue: Int? by remember { mutableStateOf(fontSize) } + var letterSpacingValue: String? by remember { mutableStateOf(letterSpacing.toString()) } + var horizontalPaddingValue: Int? by remember { mutableStateOf(horizontalPadding) } + + RYScaffold( + containerColor = MaterialTheme.colorScheme.surface onLight MaterialTheme.colorScheme.inverseOnSurface, + navigationIcon = { + FeedbackIconButton( + imageVector = Icons.Rounded.ArrowBack, + contentDescription = stringResource(R.string.back), + tint = MaterialTheme.colorScheme.onSurface + ) { + navController.popBackStack() + } + }, + content = { + LazyColumn { + item { + DisplayText(text = stringResource(R.string.text), desc = "") + } + + // Preview + item { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 24.dp) + .clip(RoundedCornerShape(24.dp)) + .background( + MaterialTheme.colorScheme.inverseOnSurface + onLight MaterialTheme.colorScheme.surface.copy(0.7f) + ) + .clickable { }, + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically + ) { + TitleAndTextPreview() + } + Spacer(modifier = Modifier.height(24.dp)) + } + + // Text + item { + Subtitle( + modifier = Modifier.padding(horizontal = 24.dp), + text = stringResource(R.string.text) + ) + SettingItem( + title = stringResource(R.string.font_size), + desc = "${fontSize}sp", + onClick = { fontSizeDialogVisible = true }, + ) {} + SettingItem( + title = stringResource(R.string.bold), + onClick = { + (!bold).put(context, scope) + ReadingThemePreference.Custom.put(context, scope) + }, + ) { + RYSwitch(activated = bold.value) { + (!bold).put(context, scope) + ReadingThemePreference.Custom.put(context, scope) + } + } + SettingItem( + title = stringResource(R.string.letter_spacing), + desc = "${letterSpacing}sp", + onClick = { letterSpacingDialogVisible = true }, + ) {} + SettingItem( + title = stringResource(R.string.horizontal_padding), + desc = "${horizontalPadding}dp", + onClick = { horizontalPaddingDialogVisible = true }, + ) {} + SettingItem( + title = stringResource(R.string.alignment), + desc = align.toDesc(context), + onClick = { alignDialogVisible = true }, + ) {} + } + + item { + Spacer(modifier = Modifier.height(24.dp)) + Spacer(modifier = Modifier.windowInsetsBottomHeight(WindowInsets.navigationBars)) + } + } + } + ) + + TextFieldDialog( + visible = fontSizeDialogVisible, + title = stringResource(R.string.font_size), + value = (fontSizeValue ?: "").toString(), + placeholder = stringResource(R.string.value), + onValueChange = { + fontSizeValue = it.filter { it.isDigit() }.toIntOrNull() + }, + onDismissRequest = { + fontSizeDialogVisible = false + }, + onConfirm = { + ReadingTextFontSizePreference.put(context, scope, fontSizeValue ?: 0) + ReadingThemePreference.Custom.put(context, scope) + fontSizeDialogVisible = false + } + ) + + TextFieldDialog( + visible = letterSpacingDialogVisible, + title = stringResource(R.string.letter_spacing), + value = (letterSpacingValue ?: "").toString(), + placeholder = stringResource(R.string.value), + onValueChange = { + letterSpacingValue = it + }, + onDismissRequest = { + letterSpacingDialogVisible = false + }, + onConfirm = { + ReadingLetterSpacingPreference.put(context, scope, letterSpacingValue?.toDoubleOrNull() ?: 0.0) + ReadingThemePreference.Custom.put(context, scope) + letterSpacingDialogVisible = false + } + ) + + TextFieldDialog( + visible = horizontalPaddingDialogVisible, + title = stringResource(R.string.horizontal_padding), + value = (horizontalPaddingValue ?: "").toString(), + placeholder = stringResource(R.string.value), + onValueChange = { + horizontalPaddingValue = it.filter { it.isDigit() }.toIntOrNull() + }, + onDismissRequest = { + horizontalPaddingDialogVisible = false + }, + onConfirm = { + ReadingTextHorizontalPaddingPreference.put(context, scope, horizontalPaddingValue ?: 0) + ReadingThemePreference.Custom.put(context, scope) + horizontalPaddingDialogVisible = false + } + ) + + RadioDialog( + visible = alignDialogVisible, + title = stringResource(R.string.alignment), + options = ReadingTextAlignPreference.values.map { + RadioDialogOption( + text = it.toDesc(context), + selected = it == align, + ) { + it.put(context, scope) + ReadingThemePreference.Custom.put(context, scope) + } + } + ) { + alignDialogVisible = false + } +} diff --git a/app/src/main/java/me/ash/reader/ui/page/settings/color/reading/ReadingTitlePage.kt b/app/src/main/java/me/ash/reader/ui/page/settings/color/reading/ReadingTitlePage.kt new file mode 100644 index 0000000..6be85a1 --- /dev/null +++ b/app/src/main/java/me/ash/reader/ui/page/settings/color/reading/ReadingTitlePage.kt @@ -0,0 +1,196 @@ +package me.ash.reader.ui.page.settings.color.reading + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.ArrowBack +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.navigation.NavHostController +import me.ash.reader.R +import me.ash.reader.data.model.preference.* +import me.ash.reader.ui.component.base.* +import me.ash.reader.ui.page.settings.SettingItem +import me.ash.reader.ui.theme.palette.onLight + +@Composable +fun ReadingTitlePage( + navController: NavHostController, +) { + val context = LocalContext.current + val scope = rememberCoroutineScope() + + val titleBold = LocalReadingTitleBold.current + val subtitleBold = LocalReadingSubheadBold.current + val titleAlign = LocalReadingTitleAlign.current + val subtitleAlign = LocalReadingSubheadAlign.current + val titleUpperCase = LocalReadingTitleUpperCase.current + val subheadUpperCase = LocalReadingSubheadUpperCase.current + + var titleAlignDialogVisible by remember { mutableStateOf(false) } + var subtitleAlignDialogVisible by remember { mutableStateOf(false) } + + RYScaffold( + containerColor = MaterialTheme.colorScheme.surface onLight MaterialTheme.colorScheme.inverseOnSurface, + navigationIcon = { + FeedbackIconButton( + imageVector = Icons.Rounded.ArrowBack, + contentDescription = stringResource(R.string.back), + tint = MaterialTheme.colorScheme.onSurface + ) { + navController.popBackStack() + } + }, + content = { + LazyColumn { + item { + DisplayText(text = stringResource(R.string.title), desc = "") + } + + // Preview + item { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 24.dp) + .clip(RoundedCornerShape(24.dp)) + .background( + MaterialTheme.colorScheme.inverseOnSurface + onLight MaterialTheme.colorScheme.surface.copy(0.7f) + ) + .clickable { }, + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically + ) { + TitleAndTextPreview() + } + Spacer(modifier = Modifier.height(24.dp)) + } + + // Title + item { + Subtitle( + modifier = Modifier.padding(horizontal = 24.dp), + text = stringResource(R.string.title) + ) + SettingItem( + title = stringResource(R.string.bold), + onClick = { + (!titleBold).put(context, scope) + ReadingThemePreference.Custom.put(context, scope) + }, + ) { + RYSwitch(activated = titleBold.value) { + (!titleBold).put(context, scope) + ReadingThemePreference.Custom.put(context, scope) + } + } + SettingItem( + title = stringResource(R.string.upper_case), + onClick = { + (!titleUpperCase).put(context, scope) + ReadingThemePreference.Custom.put(context, scope) + }, + ) { + RYSwitch(activated = titleUpperCase.value) { + (!titleUpperCase).put(context, scope) + ReadingThemePreference.Custom.put(context, scope) + } + } + SettingItem( + title = stringResource(R.string.alignment), + desc = titleAlign.toDesc(context), + onClick = { titleAlignDialogVisible = true }, + ) {} + Spacer(modifier = Modifier.height(24.dp)) + } + + // Subhead + item { + Subtitle( + modifier = Modifier.padding(horizontal = 24.dp), + text = stringResource(R.string.subhead) + ) + SettingItem( + title = stringResource(R.string.bold), + onClick = { + (!subtitleBold).put(context, scope) + ReadingThemePreference.Custom.put(context, scope) + }, + ) { + RYSwitch(activated = subtitleBold.value) { + (!subtitleBold).put(context, scope) + ReadingThemePreference.Custom.put(context, scope) + } + } + SettingItem( + title = stringResource(R.string.upper_case), + onClick = { + (!subheadUpperCase).put(context, scope) + ReadingThemePreference.Custom.put(context, scope) + }, + ) { + RYSwitch(activated = subheadUpperCase.value) { + (!subheadUpperCase).put(context, scope) + ReadingThemePreference.Custom.put(context, scope) + } + } + SettingItem( + title = stringResource(R.string.alignment), + desc = subtitleAlign.toDesc(context), + enable = false, + onClick = { +// subtitleAlignDialogVisible = true + }, + ) {} + } + + item { + Spacer(modifier = Modifier.height(24.dp)) + Spacer(modifier = Modifier.windowInsetsBottomHeight(WindowInsets.navigationBars)) + } + } + } + ) + + RadioDialog( + visible = titleAlignDialogVisible, + title = stringResource(R.string.alignment), + options = ReadingTitleAlignPreference.values.map { + RadioDialogOption( + text = it.toDesc(context), + selected = it == titleAlign, + ) { + it.put(context, scope) + ReadingThemePreference.Custom.put(context, scope) + } + } + ) { + titleAlignDialogVisible = false + } + + RadioDialog( + visible = subtitleAlignDialogVisible, + title = stringResource(R.string.alignment), + options = ReadingSubheadAlignPreference.values.map { + RadioDialogOption( + text = it.toDesc(context), + selected = it == subtitleAlign, + ) { + it.put(context, scope) + ReadingThemePreference.Custom.put(context, scope) + } + } + ) { + subtitleAlignDialogVisible = false + } +} diff --git a/app/src/main/java/me/ash/reader/ui/page/settings/color/reading/ReadingVideoPage.kt b/app/src/main/java/me/ash/reader/ui/page/settings/color/reading/ReadingVideoPage.kt new file mode 100644 index 0000000..cf34fa3 --- /dev/null +++ b/app/src/main/java/me/ash/reader/ui/page/settings/color/reading/ReadingVideoPage.kt @@ -0,0 +1,102 @@ +package me.ash.reader.ui.page.settings.color.reading + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.horizontalScroll +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.ArrowBack +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.navigation.NavHostController +import me.ash.reader.R +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.Subtitle +import me.ash.reader.ui.page.settings.SettingItem +import me.ash.reader.ui.theme.palette.onLight + +@Composable +fun ReadingVideoPage( + navController: NavHostController, +) { + val context = LocalContext.current + val scope = rememberCoroutineScope() + + RYScaffold( + containerColor = MaterialTheme.colorScheme.surface onLight MaterialTheme.colorScheme.inverseOnSurface, + navigationIcon = { + FeedbackIconButton( + imageVector = Icons.Rounded.ArrowBack, + contentDescription = stringResource(R.string.back), + tint = MaterialTheme.colorScheme.onSurface + ) { + navController.popBackStack() + } + }, + content = { + LazyColumn { + item { + DisplayText(text = stringResource(R.string.videos), desc = "") + } + + // Preview + item { + Row(modifier = Modifier.horizontalScroll(rememberScrollState()) + ) { + } + + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 24.dp) + .clip(RoundedCornerShape(24.dp)) + .background( + MaterialTheme.colorScheme.inverseOnSurface + onLight MaterialTheme.colorScheme.surface.copy(0.7f) + ) + .clickable { }, + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically + ) { + + } + Spacer(modifier = Modifier.height(24.dp)) + } + + // Videos + item { + Subtitle( + modifier = Modifier.padding(horizontal = 24.dp), + text = stringResource(R.string.videos) + ) + SettingItem( + title = stringResource(R.string.rounded_corners), + onClick = {}, + ) {} + SettingItem( + title = stringResource(R.string.horizontal_padding), + desc = "dp", + onClick = {}, + ) {} + } + + item { + Spacer(modifier = Modifier.height(24.dp)) + Spacer(modifier = Modifier.windowInsetsBottomHeight(WindowInsets.navigationBars)) + } + } + } + ) +} diff --git a/app/src/main/java/me/ash/reader/ui/page/settings/color/reading/TitleAndTextPreview.kt b/app/src/main/java/me/ash/reader/ui/page/settings/color/reading/TitleAndTextPreview.kt new file mode 100644 index 0000000..f02f2d8 --- /dev/null +++ b/app/src/main/java/me/ash/reader/ui/page/settings/color/reading/TitleAndTextPreview.kt @@ -0,0 +1,71 @@ +package me.ash.reader.ui.page.settings.color.reading + +import androidx.compose.foundation.layout.* +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import me.ash.reader.R +import me.ash.reader.data.model.preference.* +import me.ash.reader.ui.component.reader.bodyStyle +import me.ash.reader.ui.component.reader.h3Style +import me.ash.reader.ui.component.reader.textHorizontalPadding + +@Composable +fun TitleAndTextPreview() { + val context = LocalContext.current + val titleBold = LocalReadingTitleBold.current + val subtitleBold = LocalReadingSubheadBold.current + val titleUpperCase = LocalReadingTitleUpperCase.current + val subheadUpperCase = LocalReadingSubheadUpperCase.current + val titleAlign = LocalReadingTitleAlign.current + val subtitleAlign = LocalReadingSubheadAlign.current + + + val titleUpperCaseString by remember { + derivedStateOf { + context.getString(R.string.title).uppercase() + } + } + + val subheadUpperCaseString by remember { + derivedStateOf { + context.getString(R.string.subhead).uppercase() + } + } + Column(modifier = Modifier.padding(24.dp)) { + Text( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = textHorizontalPadding().dp), + text = if (titleUpperCase.value) titleUpperCaseString else stringResource(id = R.string.title), + color = MaterialTheme.colorScheme.onSurface, + style = MaterialTheme.typography.headlineLarge.copy( + fontFamily = LocalReadingFonts.current.asFontFamily(isDisplay = true), + fontWeight = if (titleBold.value) FontWeight.SemiBold else FontWeight.Normal, + ), + textAlign = titleAlign.toTextAlign(), + ) + Spacer(modifier = Modifier.height(20.dp)) + Text( + text = if (subheadUpperCase.value) subheadUpperCaseString else stringResource(id = R.string.subhead), + style = h3Style().copy(textAlign = bodyStyle().textAlign), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = textHorizontalPadding().dp) + ) + Spacer(modifier = Modifier.height(20.dp)) + Text( + text = stringResource(id = R.string.preview_article_desc), + style = bodyStyle(), + modifier = Modifier.padding(horizontal = textHorizontalPadding().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 df6f89f..026e245 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 @@ -69,6 +69,7 @@ fun InteractionPage( ) {} } item { + Spacer(modifier = Modifier.height(24.dp)) Spacer(modifier = Modifier.windowInsetsBottomHeight(WindowInsets.navigationBars)) } } diff --git a/app/src/main/java/me/ash/reader/ui/page/settings/languages/LanguagesPage.kt b/app/src/main/java/me/ash/reader/ui/page/settings/languages/LanguagesPage.kt index e71a3e1..f64929a 100644 --- a/app/src/main/java/me/ash/reader/ui/page/settings/languages/LanguagesPage.kt +++ b/app/src/main/java/me/ash/reader/ui/page/settings/languages/LanguagesPage.kt @@ -88,6 +88,7 @@ fun LanguagesPage( } } item { + Spacer(modifier = Modifier.height(24.dp)) Spacer(modifier = Modifier.windowInsetsBottomHeight(WindowInsets.navigationBars)) } } diff --git a/app/src/main/java/me/ash/reader/ui/theme/Shapes.kt b/app/src/main/java/me/ash/reader/ui/theme/Shapes.kt index 9905264..54f6b22 100644 --- a/app/src/main/java/me/ash/reader/ui/theme/Shapes.kt +++ b/app/src/main/java/me/ash/reader/ui/theme/Shapes.kt @@ -16,6 +16,9 @@ val Shapes = Shapes( @Stable val Shape20 = RoundedCornerShape(20.0.dp) +@Stable +val Shape24 = RoundedCornerShape(24.0.dp) + @Stable val Shape32 = RoundedCornerShape(32.0.dp) diff --git a/app/src/main/java/me/ash/reader/ui/theme/palette/DynamicTonalPalette.kt b/app/src/main/java/me/ash/reader/ui/theme/palette/DynamicTonalPalette.kt index 3dbc416..74685c3 100644 --- a/app/src/main/java/me/ash/reader/ui/theme/palette/DynamicTonalPalette.kt +++ b/app/src/main/java/me/ash/reader/ui/theme/palette/DynamicTonalPalette.kt @@ -5,6 +5,7 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.darkColorScheme import androidx.compose.material3.lightColorScheme import androidx.compose.runtime.Composable +import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.runtime.Stable import androidx.compose.ui.graphics.Color import me.ash.reader.data.model.preference.LocalAmoledDarkTheme @@ -80,7 +81,9 @@ infix fun Color.onLight(lightColor: Color): Color = infix fun Color.onDark(darkColor: Color): Color = if (LocalDarkTheme.current.isDarkTheme()) darkColor else this +@Stable @Composable +@ReadOnlyComposable infix fun Color.alwaysLight(isAlways: Boolean): Color { val colorScheme = MaterialTheme.colorScheme return if (isAlways && LocalDarkTheme.current.isDarkTheme()) { @@ -120,6 +123,48 @@ infix fun Color.alwaysLight(isAlways: Boolean): Color { } } +@Stable +@Composable +@ReadOnlyComposable +infix fun Color.alwaysDark(isAlways: Boolean): Color { + val colorScheme = MaterialTheme.colorScheme + return if (isAlways && !LocalDarkTheme.current.isDarkTheme()) { + when (this) { + colorScheme.primary -> colorScheme.onPrimary + colorScheme.secondary -> colorScheme.onSecondary + colorScheme.tertiary -> colorScheme.onTertiary + colorScheme.background -> colorScheme.onBackground + colorScheme.error -> colorScheme.onError + colorScheme.surface -> colorScheme.onSurface + colorScheme.surfaceVariant -> colorScheme.onSurfaceVariant + colorScheme.error -> colorScheme.onError + colorScheme.primaryContainer -> colorScheme.onPrimaryContainer + colorScheme.secondaryContainer -> colorScheme.onSecondaryContainer + colorScheme.tertiaryContainer -> colorScheme.onTertiaryContainer + colorScheme.errorContainer -> colorScheme.onErrorContainer + colorScheme.inverseSurface -> colorScheme.inverseOnSurface + + colorScheme.onPrimary -> colorScheme.primary + colorScheme.onSecondary -> colorScheme.secondary + colorScheme.onTertiary -> colorScheme.tertiary + colorScheme.onBackground -> colorScheme.background + colorScheme.onError -> colorScheme.error + colorScheme.onSurface -> colorScheme.surface + colorScheme.onSurfaceVariant -> colorScheme.surfaceVariant + colorScheme.onError -> colorScheme.error + colorScheme.onPrimaryContainer -> colorScheme.primaryContainer + colorScheme.onSecondaryContainer -> colorScheme.secondaryContainer + colorScheme.onTertiaryContainer -> colorScheme.tertiaryContainer + colorScheme.onErrorContainer -> colorScheme.errorContainer + colorScheme.inverseOnSurface -> colorScheme.inverseSurface + + else -> Color.Unspecified + } + } else { + this + } +} + fun String.checkColorHex(): String? { var s = this.trim() if (s.length > 6) { diff --git a/app/src/main/res/values-cs-rCZ/strings.xml b/app/src/main/res/values-cs-rCZ/strings.xml index 44a5b15..d040b59 100644 --- a/app/src/main/res/values-cs-rCZ/strings.xml +++ b/app/src/main/res/values-cs-rCZ/strings.xml @@ -239,7 +239,7 @@ Ostatní Tmavé téma AMOLED Zvýraznění tónů - Písma + Přečtení písma Základní písma Stránka se zdroji Stránka Flow @@ -263,7 +263,7 @@ Blázen je osmým a závěrečným dílem série Pán záhad, jehož autorem je sépie, která se ráda potápí. Reddit value - Odsazení na obou koncích + Horizontální polstrování Čas zveřejnění článku Popis článku Obrázky článku @@ -286,4 +286,33 @@ Zvýšení tónu seznamu skupin je dostupné pouze ve světlém režimu. Sdílet Klepněte pro přehráníé videa + Text + Font size + Letter spacing + Line spacing + Alignment + General + Auto hide toolbars + Rearrange buttons + Bionic Reading + Images + Rounded corners + Videos + Align left + Align right + Center text + Justify + External fonts + Title + Bold + Upper case + Subhead + Use app theme + Advanced + Dark reading theme + Bold, upper case, alignment + Font size, letter spacing, alignment + Rounded corners, horizontal padding + Rounded corners, horizontal padding + Maximize diff --git a/app/src/main/res/values-de-rDE/strings.xml b/app/src/main/res/values-de-rDE/strings.xml index 4a280e8..b0956bb 100644 --- a/app/src/main/res/values-de-rDE/strings.xml +++ b/app/src/main/res/values-de-rDE/strings.xml @@ -98,7 +98,7 @@ Interaktion Beim Start, haptisches Feedback Sprachen - English, Deutsch, mehr + Deutsch, Englisch, andere Hilf uns beim Übersetzen Gerätesprache verwenden Tipps & Support @@ -233,7 +233,7 @@ Sonstiges AMOLED Dunkles Design Farbliches Hervorheben - Schriftarten + Leseschriftarten Grundschriftarten Feeds Seite Flow Seite @@ -257,7 +257,7 @@ The Fool ist der achte und letzte Band der Lord of the Mysteries Reihe, geschrieben von Cuttlefish That Loves Diving. Reddit Wert - Rand an beiden Enden + Horizontaler Rand Artikel Veröffentlichungszeit Artikel Beschreibungen Artikel Bilder @@ -278,6 +278,35 @@ Das farbliche Hervorheben der oberen Leiste ist nur beim Scrollen verfügbar. Das farbliche Hervorheben der Artikelliste ist nur für das helle Design verfügbar. Das farbliche Hervorheben der Gruppenliste ist nur für das helle Design verfügbar. - Aktie + Teilen Berühren, um Video abzuspielen + Text + Schriftgröße + Zeichenabstand + Zeilenabstand + Ausrichtung + Allgemein + Leisten automatisch ausblenden + Schaltflächen umpositionieren + Bionic Reading + Bilder + Abgerundete Ecken + Videos + Linksbündig + Rechtsbündig + Zentriert + Blocksatz + Externe Schriftart + Titel + Fettgedruckt + Großbuchstaben + Untertitel + Appdesign verwenden + Erweitert + Dunkles Lesedesign + Fettgedruckt, Großbuchstaben, Ausrichtung + Schriftgröße, Zeichenabstand, Ausrichtung + Abgerundete Ecken, Horizontaler Rand + Abgerundete Ecken, Horizontaler Rand + Maximieren diff --git a/app/src/main/res/values-fr-rFR/strings.xml b/app/src/main/res/values-fr-rFR/strings.xml index 914ef0a..cbe624d 100644 --- a/app/src/main/res/values-fr-rFR/strings.xml +++ b/app/src/main/res/values-fr-rFR/strings.xml @@ -236,7 +236,7 @@ Autres options Thème sombre AMOLED Intensité - Polices + Polices de lecture Polices de base Page des flux Page des articles @@ -260,7 +260,7 @@ Le Fou est le huitième et dernier volume de la série Le Seigneur des Mystères écrite par Cuttlefish That Loves Diving. Reddit Valeur - Espacement des deux côtés + Rembourrage Horizontal Heure de publication des articles Descriptions des articles Images des articles @@ -283,4 +283,33 @@ L\'intensité de la liste des groupes est uniquement disponible pour le thème clair. Partager Appuyer pour lancer la lecture + Text + Font size + Letter spacing + Line spacing + Alignment + General + Auto hide toolbars + Rearrange buttons + Bionic Reading + Images + Rounded corners + Videos + Align left + Align right + Center text + Justify + External fonts + Title + Bold + Upper case + Subhead + Use app theme + Advanced + Dark reading theme + Bold, upper case, alignment + Font size, letter spacing, alignment + Rounded corners, horizontal padding + Rounded corners, horizontal padding + Maximize diff --git a/app/src/main/res/values-hi-rIN/strings.xml b/app/src/main/res/values-hi-rIN/strings.xml new file mode 100644 index 0000000..60958e1 --- /dev/null +++ b/app/src/main/res/values-hi-rIN/strings.xml @@ -0,0 +1,311 @@ + + सभी + + %1$d संग्रहित पाठ + %1$d संग्रहित पाठ + + अपठित पाठ + + %1$d अपठित पाठ + %1$d अपठित पाठ + + चिन्हित + + %1$d चिन्हित पाठ + %1$d चिन्हित पाठ + + फीड + सिंक्रनाइज़ किए जा रहे… + लोड हो रहा है… + समेटे + विस्तृत करें + पुष्टि करें + रद्द करें + अनुमति + अस्वीकार + सामान्य विकल्प + अज्ञात + पीछे + गो टू + सेटिंग्स + रिफ्रेश + खोजो + खोज रहे है … + सदस्यता लें + सदस्यता ले ली है + क्लिअर + पेस्ट + यु अरे एल अथवा फ़ीड्स + ओ पी एम् एल से आयात करें + प्रीसेट + चयनित + नोटिफिकेशन की अनुमति दें + \"%1$s\" ग्रुप के सभी फ़ीड्स को नोटीफिकेशन की स्वीकृति दें + \"%1$s\" ग्रुप के सभी नोटीफिकेशन स्वीकृत है + \"%1$s\" ग्रुप के सभी नोटीफिकेशन अस्वीकृत है + पूरा पाठ दिखाएँ + \"%1$s\" ग्रुप के सभी पाठ पूरे दिखाएँ + \"%1$s\" ग्रुप के सभी पाठ पूरे दिखाएँ + \"%1$s\" ग्रुप के सभी पाठ पूरे ना दिखाएँ + पाठ हटाएँ + \"%1$s\" फीड के सभी संग्रहित पाठो को हटाएँ + \"%1$s\"फीड के सभी संग्रहित पाठ हटा दिए है + \"%1$s\" फीड के सभी संग्रहित पाठ हटाएँ . + \"%1$s\" ग्रुप के सभी संग्रहित पाठो को हटाएँ. + ग्रुप में जोड़ें + ग्रुप में स्तानान्तरित करें + \"%1$s\" ग्रुप के सभी पाठो को \"%2$s\" में स्थानांतरित करें . + \"%1$s\" ग्रुप में स्थानांतरण पूरा हुआ + नाम बदलें + यु आर एल बदलें + नया नाम \"%1$s\" + नया ग्रुप बनाएँ + नाम + खोलें %1$s + विकल्प + मिटाएं + मिट गया \"%1$s\" + सदस्यता छोडें + \"%1$s\" से सदस्यता छोडें और सभी संग्रहित पाठों को मिटाएं + ग्रुप को मिटाएँ + \"%1$s\" ग्रुप के सभी फ़ीड्स अथवा संग्रहित पाठों को मिटाएँ + निम्नलिखित विकल्प ग्रुप के सभी फ़ीडस् पर लागू होंगे + आज + कल + तिथि %1$s समय %2$s + %1$s के लिए \"%2$s\ में खोजें " + पाठों में %1$s खोजें + पढ़ा हुआ चिन्हित करें + सभी को पढ़ा हुआ चिन्हित करें + अपठित के रूप में चिह्नित करें + तारांकित के रूप में चिह्नित करें + अतारांकित के रूप में चिह्नित करें + 1 दिन से अधिक पढ़ें के रूप में चिह्नित करें + 3 दिन से अधिक पढ़ें के रूप में चिह्नित करें + 7 दिन से अधिक पढ़ें के रूप में चिह्नित करें + 1 दिन + 3 दिन + 7 दिन + बंद करें + नया अपडेट प्राप्त करें + %1$s वर्ज़न उपलब्ध है + कोडिंग चल रही है.... + जल्द आ रहा है... + खाते + लोकल, नया आर एस एस + रंग अथवा शैली + थीम , रंग शैली , लिपि आकार + Interaction + शुरू होने पे , हैप्टिक फीडबैक + भाषाएँ + अंग्रेजी , चाइनीज, और.. + अनुवाद में मदद करें + डिवाइस भाषा का उपयोग करें + सुझाव और सहायता + परिचय, ओपन सोर्स लाइसेंस + स्वागत है + आगे बढ़ने के लिए Read Youकी सेवा शर्तें और गोपनीयता नीति को पढ़े और स्वीकार करें + यहाँ <i><u>सेवा शर्तें और गोपनीयता नीति पढ़े </u></i> + + सेवा की शर्तें + + <h5> + + गोपनीयता नीति + + </h5> + <br/> + <p> + + मैं आपकी गोपनीयता को बहुत गंभीरता से लेता हूं + + </p> + <br/> + <p> + <b>Read You</b> + + कोई उपयोगकर्ता डेटा एकत्र नहीं करता है, और सभी संवेदनशील जानकारी (पासवर्ड और अन्य खाता जानकारी) + आपके डिवाइस पर स्थानीय एप्लिकेशन डेटाबेस में सुरक्षित रूप से संग्रहीत है| + + </p> + <br/> + <p> + <b>Read You</b> + + आपको सेवा प्रदान करने के लिए निम्नलिखित अनुमतियों का उपयोग किया जाएगा। + + </p> + <br/> + <p> + + - एक्सेस नेटवर्क अनुमति (आपके द्वारा निर्देशित ऑनलाइन सामग्री तक पहुँचने के लिए) + + </p> + <p> + + - नेटवर्क स्थिति की अनुमति (यह जानने करने के लिए कि क्या डिवाइस में वर्तमान में नेटवर्क की स्थिति उपलब्ध है) + + </p> + <p> + + -पृष्ठभूमिक सेवा अनुमति (नियमित रूप से अपने पसंदीदार पाठों को स्वचालित रूप से नियमित सिंक करने के लिए आधार) + + </p> + <br/> + <br/> + <h5> + + तृतीय पक्ष सेवाएं + + </h5> + <br/> + <p> + + यह नीति आपके द्वारा <b>Read You</b>के साथ उपयोग की जाने वाली तृतीय-पक्ष सेवाओं पर लागू नहीं होती है. आप उन वेबसाइटों की गोपनीयता नीतियों की समीक्षा + करने हेतु स्वतंत्र है| + + </p> + <br/> + <br/> + <h5> + + अस्वीकरण + + </h5> + <br/> + <p> + <b>Read You</b> + + केवल एक सामग्री संग्रह उपकरण है।. आपके द्वारा <b>Read You</b> का उपयोग आपके देश/राष्ट्र और क्षेत्र के नियम ऐवं कानून के अंतर्गत है + और आपके कार्यों से उत्पन्न होने वाले किसी भी दायित्व का वहन आपके द्वारा ही किया जाएगा | + + </p> + <br/> + <br/> + <h5> + + ओपन सोर्स लाइसेंस + + </h5> + <br/> + <p> + <b>Read You</b> + + GNU GPL 3.0 Open Source License[1] के अंतर्गत एक ओपन सौरसे प्रोजेक्ट है, जो आपको <b>Read You</b> के सोर्स कोड को उपयोग, + प्रासंगिक सन्दर्भ और मुफ्त में संशोधित करेने के अनुमति देता है, परन्तु संशोधित और व्युत्पन्न कोड को क्लोज्ड-सोर्स वाणिज्यिक सॉफ्टवेयर के रूप में वितरित और बेचा जाने की अनुमति नहीं देता । + पूर्ण जानकारी हेतु GNU GPL 3.0 Open Source License[2]. देखें। + + </p> + <br/> + <br/> + <h5> + + शेषसंग्रह + + </h5> + <br/> + <p> + + - [1] https://github.com/Ashinch/ReadYou + + </p> + <p> + + - [2] https://www.gnu.org/licenses/gpl-3.0.html + + </p> + + स्वीकृत करें + वॉलपेपर रंग + कोई पैलेट नहीं + केवल Android 8.1+ हेतु + मूल रंग + प्राथमिक रंग + जैसे #666666 या 666666 + रूप + शैली + डार्क थीम + डिवाइस थीम का उपयोग करें + ओन + ऑफ + अन्य + अमोलेड डार्क थीम + टोनल एलिवेशन + रीडिंग फोंट + बेसिक फोंट + फ़ीड्स पेज + फ्लो पेज + रीडिंग पेज + प्रायोजक + Open source licenses + https://api.github.com/repos/Ashinch/ReadYou/releases/latest + बदलाव सूचि + अपडेट + इस वर्जन को छोडें + अपडेट के लिए जाँच कर रहा है + यह लेटेस्ट अपडेट है + अपडेट की जाँच नहीं कर पाए + अपडेट डाउनलोड नहीं कर पाए + अनुरोध दर सीमित है + सहायता + चालू होने पर + प्रारंभिक पृष्ठ + प्रारंभिक फ़िल्टर + "रेत-समाधि" उपन्यास अंततः समाप्त हो गई + रेत-समाधि गीतांजलि श्री की लिखी हुए उत्तम श्रेष्ट अथवा पहली हिंदी उपन्यास है जिसे अंतर्राष्ट्रीय बुकर प्रिज़े से सम्मानित किया गया है + + रेडइट + अंक + दोनों सिरों पर पैडिंग + पाठ प्रकाशन दिनांक + पाठ विवरण + पाठ चित्र कलाएँ + फीड के नाम + फीड फविकों + प्रकाशन तिथि के लिए स्टिकी हेडर (प्रयोगात्मक) + पाठों की सूचि + ग्रुप की सूचि + हमेशा विस्तृत रखें + टॉप + \"पढ़ा हुआ चिन्हित करें " \बटन का स्थान + टॉप बार + चयनित आइकन भरें + फ़िल्टर बार + चिह्न + चिह्न ऐवं लेबल + चिह्न ऐवं लेबल (केवल चयनित) + यह टूल एलिवेशन केवल स्क्रॉल करते समय उपलब्ध है + यह टोनल एलिवेशन केवल लाइट थीम में उपलब्ध है + यह टोनल एलिवेशन केवल लाइट थीम में उपलब्ध है + साँझा करें + विडियो चलने हेतु टच करें + पाठा + फोंट साइज + लैटर स्पेसिंग + लाइन स्पेसिंग + संरेखण + सामान्य + टूलबार स्वतः छुपाएँ + बटन पुनर्व्यवस्थित करें + बैओनिक पाठन + चित्र + गोल कोने + चलचित्र + बाये संरेखित करें + दाएँ संरेखित करें + पाठ मध्य संरेखित करें + संयोजिक करें + बाहरी फोंट + शीर्षक + बोल्ड + अपरकेस + उप शीर्षक + एप की थीम लगाएं + एडवांस + डार्क रीडर थीम + बोल्ड, उपरेखा, संरेखित + फोंट साईज, पंक्ति और अनुछेद रिक्त , संरेख + गोल कोण ,क्षैतिज पेडिंग + गोल कोण ,क्षैतिज पेडिंग + बड़ा करें + diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 4a641b2..46dafe4 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -233,7 +233,7 @@ Altro Tema scuro AMOLED Elevazione tonale - Font + Font di lettura Font di base Pagina dei feed Pagina dei flow @@ -257,7 +257,7 @@ Lo Sciocco è l\'ottavo e ultimo volume della serie Il Signore dei Misteri, scritta da Seppia Che Ama Le Immersioni. Reddit valore - Padding su entrambe le estremità + Imbottitura orizzontale Tempo di pubblicazione dell\'articolo Descrizione dell\'articolo Immagini dell\'articolo @@ -280,4 +280,33 @@ Questa elevazione tonale è disponibile solo con il tema chiaro. Condividi Tocca per riprodurre il video + Text + Font size + Letter spacing + Line spacing + Alignment + General + Auto hide toolbars + Rearrange buttons + Bionic Reading + Images + Rounded corners + Videos + Align left + Align right + Center text + Justify + External fonts + Title + Bold + Upper case + Subhead + Use app theme + Advanced + Dark reading theme + Bold, upper case, alignment + Font size, letter spacing, alignment + Rounded corners, horizontal padding + Rounded corners, horizontal padding + Maximize diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 6f46a71..6798640 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -224,7 +224,7 @@ AMOLED 深色主题 其他 色调海拔 - 字体 + 阅读字体 基本字体 订阅源页面 信息流页面 @@ -245,10 +245,12 @@ 起始页面 起始过滤条件 呜呜呜,黎明之剑完结了 - 是宴席,就有结束的时候,但这本书真的陪了我好久啊,好舍不得,而且主线结束了坑却没填完,不知道啥时候才有番外啊 + + 是宴席,就有结束的时候,但这本书真的陪了我好久啊,好舍不得,而且主线结束了坑却没填完,不知道啥时候才有番外啊 + 漩涡书院 - 两端边距 + 水平填充 文章发布时间 文章描述 文章插图 @@ -271,4 +273,33 @@ 分组列表的色调海拔仅在亮色主题时可用。 分享 轻触播放视频 + 文本 + 字体大小 + 字符间距 + 行间距 + 对齐 + 通用 + 自动隐藏工具条 + 重新排列按钮 + 仿生阅读 + 图片 + 圆角 + 视频 + 左对齐 + 右对齐 + 居中对齐 + 两端对齐 + 外部字体 + 标题 + 加粗 + 大写字母 + 子标题 + 跟随应用设置 + 高级 + 深色阅读主题 + 加粗、大写字母、对齐 + 字体大小、字符间距、对齐 + 圆角、水平边距 + 圆角、水平边距 + 最大化 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 63d30db..dab1e2f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -111,6 +111,7 @@ français Čeština italiano + हिंदी Tips & support About, open source licenses Welcome @@ -244,7 +245,7 @@ Other AMOLED dark theme Tonal elevation - Fonts + Reading fonts Basic fonts Feeds page Flow page @@ -272,7 +273,7 @@ Reddit value - Padding on both ends + Horizontal padding Article publication time Article descriptions Article images @@ -295,4 +296,33 @@ This tonal elevation is only available in the light theme. Share Touch to play video + Text + Font size + Letter spacing + Line spacing + Alignment + General + Auto hide toolbars + Rearrange buttons + Bionic Reading + Images + Rounded corners + Videos + Align left + Align right + Center text + Justify + External fonts + Title + Bold + Upper case + Subhead + Use app theme + Advanced + Dark reading theme + Bold, upper case, alignment + Font size, letter spacing, alignment + Rounded corners, horizontal padding + Rounded corners, horizontal padding + Maximize diff --git a/fastlane/metadata/android/de-DE/changelogs/12.txt b/fastlane/metadata/android/de-DE/changelogs/12.txt new file mode 100644 index 0000000..f692785 --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/12.txt @@ -0,0 +1,17 @@ +## 0.8.1 + +1. Französische Sprache hinzugefügt (Dank an DodoLeDev). + +2. Tschechische Sprache hinzugefügt (Dank an Fjuro). + +3. Einige übersetzte Texte geändert (Dank an comradekingu). + +4. Behebung des Problems, das nach dem Import einer OPML-Datei aus Version 0.8.0 nicht synchronisiert werden kann. + +5. Behebung eines Absturzes beim Laden sehr großer Bilder (50 MB+) (Dank an Feeder). + +6. Behebung des Problems, dass einige Feeds nicht abonniert werden konnten (Dank an kzaemrio). + +7. Optimierung im Falle von zu vielen Feeds (100+). + +8. Einige Leistungsoptimierungen und Detailänderungen. \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..e708b1c023ec8b20f512888fe07c5bd3ff77bb8f GIT binary patch literal 59203 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

K7UpUK$>l@ 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 E2O(~oYXATM literal 0 HcmV?d00001