From 9bea2e8e8b571da90208a028a3b5cce32ae91fb7 Mon Sep 17 00:00:00 2001 From: Ash Date: Sun, 1 May 2022 06:28:52 +0800 Subject: [PATCH] Add FlowPage style settings --- .../java/me/ash/reader/data/entity/Filter.kt | 16 +- .../preference/ArticleListDatePreference.kt | 45 +++ .../preference/ArticleListDescPreference.kt | 45 +++ .../ArticleListFeedIconPreference.kt | 45 +++ .../ArticleListFeedNamePreference.kt | 45 +++ .../preference/ArticleListImagePreference.kt | 45 +++ .../ArticleListTonalElevationPreference.kt | 57 +++ .../preference/FilterBarFilledPreference.kt | 45 +++ .../preference/FilterBarPaddingPreference.kt | 29 ++ .../preference/FilterBarStylePreference.kt | 48 +++ .../FilterBarTonalElevationPreference.kt | 57 +++ .../ash/reader/data/preference/Preference.kt | 8 + .../java/me/ash/reader/ui/ext/DataStoreExt.kt | 51 +++ .../java/me/ash/reader/ui/ext/StateFlowExt.kt | 9 +- .../me/ash/reader/ui/page/common/HomeEntry.kt | 15 + .../me/ash/reader/ui/page/common/RouteName.kt | 12 + .../me/ash/reader/ui/page/home/FilterBar.kt | 103 ++++-- .../reader/ui/page/home/feeds/FeedsPage.kt | 5 +- .../reader/ui/page/home/flow/ArticleItem.kt | 124 ++++--- .../reader/ui/page/home/flow/ArticleList.kt | 4 +- .../ash/reader/ui/page/home/flow/FlowPage.kt | 33 +- .../reader/ui/page/home/flow/StickyHeader.kt | 10 +- .../ui/page/settings/color/ColorAndStyle.kt | 8 +- .../settings/color/flow/feedsPageStyle.kt | 346 ++++++++++++++++++ app/src/main/res/values-zh-rCN/strings.xml | 2 +- app/src/main/res/values/strings.xml | 2 +- 26 files changed, 1098 insertions(+), 111 deletions(-) create mode 100644 app/src/main/java/me/ash/reader/data/preference/ArticleListDatePreference.kt create mode 100644 app/src/main/java/me/ash/reader/data/preference/ArticleListDescPreference.kt create mode 100644 app/src/main/java/me/ash/reader/data/preference/ArticleListFeedIconPreference.kt create mode 100644 app/src/main/java/me/ash/reader/data/preference/ArticleListFeedNamePreference.kt create mode 100644 app/src/main/java/me/ash/reader/data/preference/ArticleListImagePreference.kt create mode 100644 app/src/main/java/me/ash/reader/data/preference/ArticleListTonalElevationPreference.kt create mode 100644 app/src/main/java/me/ash/reader/data/preference/FilterBarFilledPreference.kt create mode 100644 app/src/main/java/me/ash/reader/data/preference/FilterBarPaddingPreference.kt create mode 100644 app/src/main/java/me/ash/reader/data/preference/FilterBarStylePreference.kt create mode 100644 app/src/main/java/me/ash/reader/data/preference/FilterBarTonalElevationPreference.kt create mode 100644 app/src/main/java/me/ash/reader/data/preference/Preference.kt create mode 100644 app/src/main/java/me/ash/reader/ui/page/settings/color/flow/feedsPageStyle.kt diff --git a/app/src/main/java/me/ash/reader/data/entity/Filter.kt b/app/src/main/java/me/ash/reader/data/entity/Filter.kt index ec010e8..738ebca 100644 --- a/app/src/main/java/me/ash/reader/data/entity/Filter.kt +++ b/app/src/main/java/me/ash/reader/data/entity/Filter.kt @@ -2,13 +2,16 @@ package me.ash.reader.data.entity import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.FiberManualRecord +import androidx.compose.material.icons.rounded.FiberManualRecord +import androidx.compose.material.icons.rounded.Star import androidx.compose.material.icons.rounded.StarOutline import androidx.compose.material.icons.rounded.Subject import androidx.compose.ui.graphics.vector.ImageVector class Filter( - var index: Int, - var icon: ImageVector, + val index: Int, + val iconOutline: ImageVector, + val iconFilled: ImageVector, ) { fun isStarred(): Boolean = this == Starred fun isUnread(): Boolean = this == Unread @@ -17,15 +20,18 @@ class Filter( companion object { val Starred = Filter( index = 0, - icon = Icons.Rounded.StarOutline, + iconOutline = Icons.Rounded.StarOutline, + iconFilled = Icons.Rounded.Star, ) val Unread = Filter( index = 1, - icon = Icons.Outlined.FiberManualRecord, + iconOutline = Icons.Outlined.FiberManualRecord, + iconFilled = Icons.Rounded.FiberManualRecord, ) val All = Filter( index = 2, - icon = Icons.Rounded.Subject, + iconOutline = Icons.Rounded.Subject, + iconFilled = Icons.Rounded.Subject, ) } } \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/data/preference/ArticleListDatePreference.kt b/app/src/main/java/me/ash/reader/data/preference/ArticleListDatePreference.kt new file mode 100644 index 0000000..b89720a --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/preference/ArticleListDatePreference.kt @@ -0,0 +1,45 @@ +package me.ash.reader.data.preference + +import android.content.Context +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +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 ArticleListDatePreference(val value: Boolean) : Preference() { + object ON : ArticleListDatePreference(true) + object OFF : ArticleListDatePreference(false) + + override fun put(context: Context, scope: CoroutineScope) { + scope.launch(Dispatchers.IO) { + context.dataStore.put( + DataStoreKeys.ArticleListDate, + value + ) + } + } + + companion object { + val default = ON + val values = listOf(ON, OFF) + + val Context.articleListDate: Flow + get() = this.dataStore.data.map { + when (it[DataStoreKeys.ArticleListDate.key]) { + true -> ON + false -> OFF + else -> default + } + } + } +} + +operator fun ArticleListDatePreference.not(): ArticleListDatePreference = + when (value) { + true -> ArticleListDatePreference.OFF + false -> ArticleListDatePreference.ON + } \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/data/preference/ArticleListDescPreference.kt b/app/src/main/java/me/ash/reader/data/preference/ArticleListDescPreference.kt new file mode 100644 index 0000000..10dc06a --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/preference/ArticleListDescPreference.kt @@ -0,0 +1,45 @@ +package me.ash.reader.data.preference + +import android.content.Context +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +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 ArticleListDescPreference(val value: Boolean) : Preference() { + object ON : ArticleListDescPreference(true) + object OFF : ArticleListDescPreference(false) + + override fun put(context: Context, scope: CoroutineScope) { + scope.launch(Dispatchers.IO) { + context.dataStore.put( + DataStoreKeys.ArticleListDesc, + value + ) + } + } + + companion object { + val default = ON + val values = listOf(ON, OFF) + + val Context.articleListDesc: Flow + get() = this.dataStore.data.map { + when (it[DataStoreKeys.ArticleListDesc.key]) { + true -> ON + false -> OFF + else -> default + } + } + } +} + +operator fun ArticleListDescPreference.not(): ArticleListDescPreference = + when (value) { + true -> ArticleListDescPreference.OFF + false -> ArticleListDescPreference.ON + } \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/data/preference/ArticleListFeedIconPreference.kt b/app/src/main/java/me/ash/reader/data/preference/ArticleListFeedIconPreference.kt new file mode 100644 index 0000000..796aa61 --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/preference/ArticleListFeedIconPreference.kt @@ -0,0 +1,45 @@ +package me.ash.reader.data.preference + +import android.content.Context +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +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 ArticleListFeedIconPreference(val value: Boolean) : Preference() { + object ON : ArticleListFeedIconPreference(true) + object OFF : ArticleListFeedIconPreference(false) + + override fun put(context: Context, scope: CoroutineScope) { + scope.launch(Dispatchers.IO) { + context.dataStore.put( + DataStoreKeys.ArticleListFeedIcon, + value + ) + } + } + + companion object { + val default = ON + val values = listOf(ON, OFF) + + val Context.articleListFeedIcon: Flow + get() = this.dataStore.data.map { + when (it[DataStoreKeys.ArticleListFeedIcon.key]) { + true -> ON + false -> OFF + else -> default + } + } + } +} + +operator fun ArticleListFeedIconPreference.not(): ArticleListFeedIconPreference = + when (value) { + true -> ArticleListFeedIconPreference.OFF + false -> ArticleListFeedIconPreference.ON + } \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/data/preference/ArticleListFeedNamePreference.kt b/app/src/main/java/me/ash/reader/data/preference/ArticleListFeedNamePreference.kt new file mode 100644 index 0000000..9269d4b --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/preference/ArticleListFeedNamePreference.kt @@ -0,0 +1,45 @@ +package me.ash.reader.data.preference + +import android.content.Context +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +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 ArticleListFeedNamePreference(val value: Boolean) : Preference() { + object ON : ArticleListFeedNamePreference(true) + object OFF : ArticleListFeedNamePreference(false) + + override fun put(context: Context, scope: CoroutineScope) { + scope.launch(Dispatchers.IO) { + context.dataStore.put( + DataStoreKeys.ArticleListFeedName, + value + ) + } + } + + companion object { + val default = ON + val values = listOf(ON, OFF) + + val Context.articleListFeedName: Flow + get() = this.dataStore.data.map { + when (it[DataStoreKeys.ArticleListFeedName.key]) { + true -> ON + false -> OFF + else -> default + } + } + } +} + +operator fun ArticleListFeedNamePreference.not(): ArticleListFeedNamePreference = + when (value) { + true -> ArticleListFeedNamePreference.OFF + false -> ArticleListFeedNamePreference.ON + } \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/data/preference/ArticleListImagePreference.kt b/app/src/main/java/me/ash/reader/data/preference/ArticleListImagePreference.kt new file mode 100644 index 0000000..06bdde6 --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/preference/ArticleListImagePreference.kt @@ -0,0 +1,45 @@ +package me.ash.reader.data.preference + +import android.content.Context +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +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 ArticleListImagePreference(val value: Boolean) : Preference() { + object ON : ArticleListImagePreference(true) + object OFF : ArticleListImagePreference(false) + + override fun put(context: Context, scope: CoroutineScope) { + scope.launch(Dispatchers.IO) { + context.dataStore.put( + DataStoreKeys.ArticleListImage, + value + ) + } + } + + companion object { + val default = ON + val values = listOf(ON, OFF) + + val Context.articleListImage: Flow + get() = this.dataStore.data.map { + when (it[DataStoreKeys.ArticleListImage.key]) { + true -> ON + false -> OFF + else -> default + } + } + } +} + +operator fun ArticleListImagePreference.not(): ArticleListImagePreference = + when (value) { + true -> ArticleListImagePreference.OFF + false -> ArticleListImagePreference.ON + } \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/data/preference/ArticleListTonalElevationPreference.kt b/app/src/main/java/me/ash/reader/data/preference/ArticleListTonalElevationPreference.kt new file mode 100644 index 0000000..79a862a --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/preference/ArticleListTonalElevationPreference.kt @@ -0,0 +1,57 @@ +package me.ash.reader.data.preference + +import android.content.Context +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +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 ArticleListTonalElevationPreference(val value: Int) : Preference() { + object Level0 : ArticleListTonalElevationPreference(0) + object Level1 : ArticleListTonalElevationPreference(1) + object Level2 : ArticleListTonalElevationPreference(3) + object Level3 : ArticleListTonalElevationPreference(6) + object Level4 : ArticleListTonalElevationPreference(8) + object Level5 : ArticleListTonalElevationPreference(12) + + override fun put(context: Context, scope: CoroutineScope) { + scope.launch(Dispatchers.IO) { + context.dataStore.put( + DataStoreKeys.ArticleListTonalElevation, + value + ) + } + } + + fun getDesc(context: Context): String = + when (this) { + Level0 -> "Level 0 (0dp)" + Level1 -> "Level 1 (1dp)" + Level2 -> "Level 2 (3dp)" + Level3 -> "Level 3 (6dp)" + Level4 -> "Level 4 (8dp)" + Level5 -> "Level 5 (12dp)" + } + + companion object { + val default = Level0 + val values = listOf(Level0, Level1, Level2, Level3, Level4, Level5) + + val Context.articleListTonalElevation: Flow + get() = this.dataStore.data.map { + when (it[DataStoreKeys.ArticleListTonalElevation.key]) { + 0 -> Level0 + 1 -> Level1 + 3 -> Level2 + 6 -> Level3 + 8 -> Level4 + 12 -> Level5 + else -> default + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/data/preference/FilterBarFilledPreference.kt b/app/src/main/java/me/ash/reader/data/preference/FilterBarFilledPreference.kt new file mode 100644 index 0000000..c6265da --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/preference/FilterBarFilledPreference.kt @@ -0,0 +1,45 @@ +package me.ash.reader.data.preference + +import android.content.Context +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +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 FilterBarFilledPreference(val value: Boolean) : Preference() { + object ON : FilterBarFilledPreference(true) + object OFF : FilterBarFilledPreference(false) + + override fun put(context: Context, scope: CoroutineScope) { + scope.launch(Dispatchers.IO) { + context.dataStore.put( + DataStoreKeys.FilterBarFilled, + value + ) + } + } + + companion object { + val default = OFF + val values = listOf(ON, OFF) + + val Context.filterBarFilled: Flow + get() = this.dataStore.data.map { + when (it[DataStoreKeys.FilterBarFilled.key]) { + true -> ON + false -> OFF + else -> default + } + } + } +} + +operator fun FilterBarFilledPreference.not(): FilterBarFilledPreference = + when (value) { + true -> FilterBarFilledPreference.OFF + false -> FilterBarFilledPreference.ON + } \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/data/preference/FilterBarPaddingPreference.kt b/app/src/main/java/me/ash/reader/data/preference/FilterBarPaddingPreference.kt new file mode 100644 index 0000000..516453f --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/preference/FilterBarPaddingPreference.kt @@ -0,0 +1,29 @@ +package me.ash.reader.data.preference + +import android.content.Context +import android.util.Log +import androidx.compose.runtime.Immutable +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +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 +object FilterBarPaddingPreference { + const val default = 0 + + val Context.filterBarPadding: Flow + get() = this.dataStore.data.map { + it[DataStoreKeys.FilterBarPadding.key] ?: 0 + } + + fun put(context: Context, scope: CoroutineScope, value: Int) { + scope.launch(Dispatchers.IO) { + context.dataStore.put(DataStoreKeys.FilterBarPadding, value) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/data/preference/FilterBarStylePreference.kt b/app/src/main/java/me/ash/reader/data/preference/FilterBarStylePreference.kt new file mode 100644 index 0000000..50e6c7b --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/preference/FilterBarStylePreference.kt @@ -0,0 +1,48 @@ +package me.ash.reader.data.preference + +import android.content.Context +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +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 FilterBarStylePreference(val value: Int) : Preference() { + object Icon : FilterBarStylePreference(0) + object IconLabel : FilterBarStylePreference(1) + object IconLabelOnlySelected : FilterBarStylePreference(2) + + override fun put(context: Context, scope: CoroutineScope) { + scope.launch(Dispatchers.IO) { + context.dataStore.put( + DataStoreKeys.FilterBarStyle, + value + ) + } + } + + fun getDesc(context: Context): String = + when (this) { + Icon -> "图标" + IconLabel -> "图标 + 标签" + IconLabelOnlySelected -> "图标 + 标签(仅选中时)" + } + + companion object { + val default = Icon + val values = listOf(Icon, IconLabel, IconLabelOnlySelected) + + val Context.filterBarStyle: Flow + get() = this.dataStore.data.map { + when (it[DataStoreKeys.FilterBarStyle.key]) { + 0 -> Icon + 1 -> IconLabel + 2 -> IconLabelOnlySelected + else -> default + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/data/preference/FilterBarTonalElevationPreference.kt b/app/src/main/java/me/ash/reader/data/preference/FilterBarTonalElevationPreference.kt new file mode 100644 index 0000000..6c6fced --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/preference/FilterBarTonalElevationPreference.kt @@ -0,0 +1,57 @@ +package me.ash.reader.data.preference + +import android.content.Context +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +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 FilterBarTonalElevationPreference(val value: Int) : Preference() { + object Level0 : FilterBarTonalElevationPreference(0) + object Level1 : FilterBarTonalElevationPreference(1) + object Level2 : FilterBarTonalElevationPreference(3) + object Level3 : FilterBarTonalElevationPreference(6) + object Level4 : FilterBarTonalElevationPreference(8) + object Level5 : FilterBarTonalElevationPreference(12) + + override fun put(context: Context, scope: CoroutineScope) { + scope.launch(Dispatchers.IO) { + context.dataStore.put( + DataStoreKeys.FilterBarTonalElevation, + value + ) + } + } + + fun getDesc(context: Context): String = + when (this) { + Level0 -> "Level 0 (0dp)" + Level1 -> "Level 1 (1dp)" + Level2 -> "Level 2 (3dp)" + Level3 -> "Level 3 (6dp)" + Level4 -> "Level 4 (8dp)" + Level5 -> "Level 5 (12dp)" + } + + companion object { + val default = Level0 + val values = listOf(Level0, Level1, Level2, Level3, Level4, Level5) + + val Context.filterBarTonalElevation: Flow + get() = this.dataStore.data.map { + when (it[DataStoreKeys.FilterBarTonalElevation.key]) { + 0 -> Level0 + 1 -> Level1 + 3 -> Level2 + 6 -> Level3 + 8 -> Level4 + 12 -> Level5 + else -> default + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/data/preference/Preference.kt b/app/src/main/java/me/ash/reader/data/preference/Preference.kt new file mode 100644 index 0000000..cd451da --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/preference/Preference.kt @@ -0,0 +1,8 @@ +package me.ash.reader.data.preference + +import android.content.Context +import kotlinx.coroutines.CoroutineScope + +sealed class Preference { + abstract fun put(context: Context, scope: CoroutineScope) +} \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/ui/ext/DataStoreExt.kt b/app/src/main/java/me/ash/reader/ui/ext/DataStoreExt.kt index 52d749c..0dd5463 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 @@ -33,6 +33,7 @@ val Context.currentAccountType: Int get() = this.dataStore.get(DataStoreKeys.CurrentAccountType)!! val Context.themeIndex: Int get() = this.dataStore.get(DataStoreKeys.ThemeIndex) ?: 5 + val Context.customPrimaryColor: String get() = this.dataStore.get(DataStoreKeys.CustomPrimaryColor) ?: "" val Context.initialPage: Int @@ -129,6 +130,56 @@ sealed class DataStoreKeys { get() = stringPreferencesKey("customPrimaryColor") } + object FilterBarStyle : DataStoreKeys() { + override val key: Preferences.Key + get() = intPreferencesKey("filterBarStyle") + } + + object FilterBarFilled : DataStoreKeys() { + override val key: Preferences.Key + get() = booleanPreferencesKey("filterBarFilled") + } + + object FilterBarPadding : DataStoreKeys() { + override val key: Preferences.Key + get() = intPreferencesKey("filterBarPadding") + } + + object FilterBarTonalElevation : DataStoreKeys() { + override val key: Preferences.Key + get() = intPreferencesKey("filterBarTonalElevation") + } + + object ArticleListFeedIcon : DataStoreKeys() { + override val key: Preferences.Key + get() = booleanPreferencesKey("articleListFeedIcon") + } + + object ArticleListFeedName : DataStoreKeys() { + override val key: Preferences.Key + get() = booleanPreferencesKey("articleListFeedName") + } + + object ArticleListImage : DataStoreKeys() { + override val key: Preferences.Key + get() = booleanPreferencesKey("articleListImage") + } + + object ArticleListDesc : DataStoreKeys() { + override val key: Preferences.Key + get() = booleanPreferencesKey("articleListDesc") + } + + object ArticleListDate : DataStoreKeys() { + override val key: Preferences.Key + get() = booleanPreferencesKey("articleListDate") + } + + object ArticleListTonalElevation : DataStoreKeys() { + override val key: Preferences.Key + get() = intPreferencesKey("articleListTonalElevation") + } + object InitialPage : DataStoreKeys() { override val key: Preferences.Key get() = intPreferencesKey("initialPage") diff --git a/app/src/main/java/me/ash/reader/ui/ext/StateFlowExt.kt b/app/src/main/java/me/ash/reader/ui/ext/StateFlowExt.kt index 28ec720..f968cf5 100644 --- a/app/src/main/java/me/ash/reader/ui/ext/StateFlowExt.kt +++ b/app/src/main/java/me/ash/reader/ui/ext/StateFlowExt.kt @@ -3,10 +3,17 @@ package me.ash.reader.ui.ext import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow import kotlin.coroutines.CoroutineContext @Composable fun StateFlow.collectAsStateValue( context: CoroutineContext = Dispatchers.Default -): T = collectAsState(context).value \ No newline at end of file +): T = collectAsState(context).value + +@Composable +fun Flow.collectAsStateValue( + initial: R, + context: CoroutineContext = Dispatchers.Default +): R = collectAsState(initial, context).value \ No newline at end of file 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 bd33513..8d8208e 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 @@ -22,6 +22,7 @@ import me.ash.reader.ui.page.home.flow.FlowPage import me.ash.reader.ui.page.home.read.ReadPage import me.ash.reader.ui.page.settings.SettingsPage import me.ash.reader.ui.page.settings.color.ColorAndStyle +import me.ash.reader.ui.page.settings.color.flow.FlowPageStyle import me.ash.reader.ui.page.settings.interaction.Interaction import me.ash.reader.ui.page.settings.tips.TipsAndSupport import me.ash.reader.ui.page.startup.StartupPage @@ -97,9 +98,12 @@ fun HomeEntry( navController = navController, startDestination = if (context.isFirstLaunch) RouteName.STARTUP else RouteName.FEEDS, ) { + // Startup animatedComposable(route = RouteName.STARTUP) { StartupPage(navController) } + + // Home animatedComposable(route = RouteName.FEEDS) { FeedsPage(navController = navController, homeViewModel = homeViewModel) } @@ -113,15 +117,26 @@ fun HomeEntry( animatedComposable(route = "${RouteName.READING}/{articleId}") { ReadPage(navController = navController) } + + // Settings animatedComposable(route = RouteName.SETTINGS) { SettingsPage(navController) } + + // Color & Style animatedComposable(route = RouteName.COLOR_AND_STYLE) { ColorAndStyle(navController) } + animatedComposable(route = RouteName.FLOW_PAGE_STYLE) { + FlowPageStyle(navController) + } + + // Interaction animatedComposable(route = RouteName.INTERACTION) { Interaction(navController) } + + // Tips & Support animatedComposable(route = RouteName.TIPS_AND_SUPPORT) { TipsAndSupport(navController) } 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 aa4accb..51aba6a 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 @@ -1,12 +1,24 @@ package me.ash.reader.ui.page.common object RouteName { + // Startup const val STARTUP = "startup" + + // Home const val FEEDS = "feeds" const val FLOW = "flow" const val READING = "reading" + + // Settings const val SETTINGS = "settings" + + // Color & Style const val COLOR_AND_STYLE = "color_and_style" + const val FLOW_PAGE_STYLE = "flow_page_style" + + // Interaction const val INTERACTION = "interaction" + + // Tips & Support const val TIPS_AND_SUPPORT = "tips_and_support" } \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/ui/page/home/FilterBar.kt b/app/src/main/java/me/ash/reader/ui/page/home/FilterBar.kt index ab3cbb0..341d4fb 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/FilterBar.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/FilterBar.kt @@ -1,16 +1,27 @@ package me.ash.reader.ui.page.home import android.view.SoundEffectConstants -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.width import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalView import androidx.compose.ui.unit.dp -import androidx.compose.ui.zIndex import com.google.accompanist.pager.ExperimentalPagerApi import me.ash.reader.data.entity.Filter +import me.ash.reader.data.preference.FilterBarFilledPreference +import me.ash.reader.data.preference.FilterBarFilledPreference.Companion.filterBarFilled +import me.ash.reader.data.preference.FilterBarPaddingPreference +import me.ash.reader.data.preference.FilterBarPaddingPreference.filterBarPadding +import me.ash.reader.data.preference.FilterBarStylePreference +import me.ash.reader.data.preference.FilterBarStylePreference.Companion.filterBarStyle +import me.ash.reader.data.preference.FilterBarTonalElevationPreference +import me.ash.reader.data.preference.FilterBarTonalElevationPreference.Companion.filterBarTonalElevation +import me.ash.reader.ui.ext.collectAsStateValue import me.ash.reader.ui.ext.getName +import me.ash.reader.ui.theme.palette.onDark @OptIn(ExperimentalPagerApi::class) @Composable @@ -20,51 +31,67 @@ fun FilterBar( filterOnClick: (Filter) -> Unit = {}, ) { val view = LocalView.current + val context = LocalContext.current + val filterBarStyle = + context.filterBarStyle.collectAsStateValue(initial = FilterBarStylePreference.default) + val filterBarFilled = + context.filterBarFilled.collectAsStateValue(initial = FilterBarFilledPreference.default) + val filterBarPadding = + context.filterBarPadding.collectAsStateValue(initial = FilterBarPaddingPreference.default) + val filterBarTonalElevation = + context.filterBarTonalElevation.collectAsStateValue(initial = FilterBarTonalElevationPreference.default) - Box( -// modifier = Modifier.height(60.dp) + NavigationBar( + tonalElevation = filterBarTonalElevation.value.dp, ) { - Divider( - modifier = Modifier - .fillMaxWidth() - .height(1.dp) - .zIndex(1f), - color = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.24f) - ) - NavigationBar( -// modifier = Modifier.fillMaxSize(), -// tonalElevation = 0.dp, - ) { - Spacer(modifier = Modifier.width(60.dp)) - listOf( - Filter.Starred, - Filter.Unread, - Filter.All, - ).forEach { item -> - NavigationBarItem( - icon = { - Icon( - imageVector = item.icon, - contentDescription = item.getName() + Spacer(modifier = Modifier.width(filterBarPadding.dp)) + listOf( + Filter.Starred, + Filter.Unread, + Filter.All, + ).forEach { item -> + NavigationBarItem( +// modifier = Modifier.height(60.dp), + alwaysShowLabel = when (filterBarStyle) { + is FilterBarStylePreference.Icon -> false + is FilterBarStylePreference.IconLabel -> true + is FilterBarStylePreference.IconLabelOnlySelected -> false + }, + icon = { + Icon( + imageVector = if (filter == item && filterBarFilled.value) { + item.iconFilled + } else { + item.iconOutline + }, + contentDescription = item.getName() + ) + }, + label = if (filterBarStyle is FilterBarStylePreference.Icon) { + null + } else { + { + Text( + text = item.getName(), + style = MaterialTheme.typography.labelLarge ) - }, - label = { Text(text = item.getName(), style = MaterialTheme.typography.labelMedium) }, - selected = filter == item, - onClick = { + } + }, + selected = filter == item, + onClick = { // view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP) - view.playSoundEffect(SoundEffectConstants.CLICK) - filterOnClick(item) - }, - colors = NavigationBarItemDefaults.colors( + view.playSoundEffect(SoundEffectConstants.CLICK) + filterOnClick(item) + }, + colors = NavigationBarItemDefaults.colors( // selectedIconColor = MaterialTheme.colorScheme.onSecondaryContainer alwaysLight true, // unselectedIconColor = MaterialTheme.colorScheme.outline, // selectedTextColor = MaterialTheme.colorScheme.onSurface alwaysLight true, // unselectedTextColor = MaterialTheme.colorScheme.onSurfaceVariant, -// indicatorColor = MaterialTheme.colorScheme.primaryContainer alwaysLight true, - ) + indicatorColor = MaterialTheme.colorScheme.primaryContainer onDark MaterialTheme.colorScheme.secondaryContainer, ) - } - Spacer(modifier = Modifier.width(60.dp)) + ) } + Spacer(modifier = Modifier.width(filterBarPadding.dp)) } } \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsPage.kt b/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsPage.kt index d13ffa3..e9bc7f0 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsPage.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsPage.kt @@ -174,7 +174,7 @@ fun FeedsPage( Banner( title = filterState.filter.getName(), desc = feedsViewState.importantCount, - icon = filterState.filter.icon, + icon = filterState.filter.iconOutline, action = { Icon( imageVector = Icons.Outlined.KeyboardArrowRight, @@ -241,9 +241,6 @@ fun FeedsPage( }, bottomBar = { FilterBar( - modifier = Modifier - .height(60.dp) - .fillMaxWidth(), filter = filterState.filter, filterOnClick = { filterChange( diff --git a/app/src/main/java/me/ash/reader/ui/page/home/flow/ArticleItem.kt b/app/src/main/java/me/ash/reader/ui/page/home/flow/ArticleItem.kt index 10e569e..640cb8d 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/flow/ArticleItem.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/flow/ArticleItem.kt @@ -21,6 +21,13 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import me.ash.reader.R import me.ash.reader.data.entity.ArticleWithFeed +import me.ash.reader.data.preference.* +import me.ash.reader.data.preference.ArticleListDatePreference.Companion.articleListDate +import me.ash.reader.data.preference.ArticleListDescPreference.Companion.articleListDesc +import me.ash.reader.data.preference.ArticleListFeedIconPreference.Companion.articleListFeedIcon +import me.ash.reader.data.preference.ArticleListFeedNamePreference.Companion.articleListFeedName +import me.ash.reader.data.preference.ArticleListImagePreference.Companion.articleListImage +import me.ash.reader.ui.ext.collectAsStateValue import me.ash.reader.ui.ext.formatAsString @Composable @@ -30,6 +37,16 @@ fun ArticleItem( onClick: (ArticleWithFeed) -> Unit = {}, ) { val context = LocalContext.current + val articleListFeedIcon = + context.articleListFeedIcon.collectAsStateValue(initial = ArticleListFeedIconPreference.default) + val articleListFeedName = + context.articleListFeedName.collectAsStateValue(initial = ArticleListFeedNamePreference.default) + val articleListImage = + context.articleListImage.collectAsStateValue(initial = ArticleListImagePreference.default) + val articleListDesc = + context.articleListDesc.collectAsStateValue(initial = ArticleListDescPreference.default) + val articleListDate = + context.articleListDate.collectAsStateValue(initial = ArticleListDatePreference.default) Column( modifier = Modifier @@ -44,67 +61,84 @@ fun ArticleItem( horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, ) { - Text( - modifier = Modifier - .weight(1f) - .padding(start = 30.dp), - text = articleWithFeed.feed.name, - color = MaterialTheme.colorScheme.tertiary, - style = MaterialTheme.typography.labelMedium, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - Row( - modifier = Modifier.padding(start = 6.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - if (articleWithFeed.article.isStarred) { - Icon( - modifier = Modifier - .size(14.dp) - .padding(end = 2.dp), - imageVector = Icons.Rounded.Star, - contentDescription = stringResource(R.string.starred), - tint = MaterialTheme.colorScheme.outline.copy(alpha = 0.7f), + // Feed name + if (articleListFeedName.value) { + Text( + modifier = Modifier + .weight(1f) + .padding(start = if (articleListFeedIcon.value) 30.dp else 0.dp), + text = articleWithFeed.feed.name, + color = MaterialTheme.colorScheme.tertiary, + style = MaterialTheme.typography.labelMedium, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } + + if (articleListDate.value) { + Row( + modifier = Modifier.padding(start = 6.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + // Starred + if (articleWithFeed.article.isStarred) { + Icon( + modifier = Modifier + .size(14.dp) + .padding(end = 2.dp), + imageVector = Icons.Rounded.Star, + contentDescription = stringResource(R.string.starred), + tint = MaterialTheme.colorScheme.outline.copy(alpha = 0.7f), + ) + } + + // Date + Text( + text = articleWithFeed.article.date.formatAsString( + context, + onlyHourMinute = true + ), + color = MaterialTheme.colorScheme.outline.copy(alpha = 0.7f), + style = MaterialTheme.typography.labelMedium, ) } - Text( - text = articleWithFeed.article.date.formatAsString( - context, - onlyHourMinute = true - ), - color = MaterialTheme.colorScheme.outline.copy(alpha = 0.7f), - style = MaterialTheme.typography.labelMedium, - ) } } Row( modifier = Modifier.fillMaxWidth(), ) { - Row( - modifier = Modifier - .size(20.dp) - .clip(CircleShape) - .background(MaterialTheme.colorScheme.outline.copy(alpha = 0.2f)) - ) {} - Spacer(modifier = Modifier.width(10.dp)) + // Feed icon + if (articleListFeedIcon.value) { + Row( + modifier = Modifier + .size(20.dp) + .clip(CircleShape) + .background(MaterialTheme.colorScheme.outline.copy(alpha = 0.2f)) + ) {} + Spacer(modifier = Modifier.width(10.dp)) + } + // Article Column( modifier = Modifier.fillMaxWidth(), ) { + // Title Text( text = articleWithFeed.article.title, color = MaterialTheme.colorScheme.onSurface, style = MaterialTheme.typography.titleMedium, - maxLines = 2, - overflow = TextOverflow.Ellipsis, - ) - Text( - text = articleWithFeed.article.shortDescription, - color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f), - style = MaterialTheme.typography.bodySmall, - maxLines = 2, + maxLines = if (articleListDesc.value) 2 else 4, overflow = TextOverflow.Ellipsis, ) + // Description + if (articleListDesc.value) { + Text( + text = articleWithFeed.article.shortDescription, + color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f), + style = MaterialTheme.typography.bodySmall, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + ) + } } } } diff --git a/app/src/main/java/me/ash/reader/ui/page/home/flow/ArticleList.kt b/app/src/main/java/me/ash/reader/ui/page/home/flow/ArticleList.kt index c1cd135..2d4f736 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/flow/ArticleList.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/flow/ArticleList.kt @@ -13,6 +13,8 @@ import me.ash.reader.data.entity.ArticleWithFeed @OptIn(ExperimentalFoundationApi::class) fun LazyListScope.ArticleList( pagingItems: LazyPagingItems, + articleListFeedIcon: Boolean, + articleListTonalElevation: Int, onClick: (ArticleWithFeed) -> Unit = {}, ) { for (index in 0 until pagingItems.itemCount) { @@ -30,7 +32,7 @@ fun LazyListScope.ArticleList( val separator = pagingItems[index] as FlowItemView.Date if (separator.showSpacer) item { Spacer(modifier = Modifier.height(40.dp)) } stickyHeader { - StickyHeader(separator.date) + StickyHeader(separator.date, articleListFeedIcon, articleListTonalElevation) } } else -> {} diff --git a/app/src/main/java/me/ash/reader/ui/page/home/flow/FlowPage.kt b/app/src/main/java/me/ash/reader/ui/page/home/flow/FlowPage.kt index 80dcf20..678cdbd 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/flow/FlowPage.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/flow/FlowPage.kt @@ -15,6 +15,7 @@ import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.res.stringResource @@ -26,6 +27,10 @@ import androidx.paging.compose.LazyPagingItems import kotlinx.coroutines.delay import kotlinx.coroutines.launch import me.ash.reader.R +import me.ash.reader.data.preference.ArticleListFeedIconPreference +import me.ash.reader.data.preference.ArticleListFeedIconPreference.Companion.articleListFeedIcon +import me.ash.reader.data.preference.ArticleListTonalElevationPreference +import me.ash.reader.data.preference.ArticleListTonalElevationPreference.Companion.articleListTonalElevation import me.ash.reader.data.repository.SyncWorker.Companion.getIsSyncing import me.ash.reader.ui.component.DisplayText import me.ash.reader.ui.component.FeedbackIconButton @@ -52,6 +57,7 @@ fun FlowPage( homeViewModel: HomeViewModel, pagingItems: LazyPagingItems, ) { + val context = LocalContext.current val keyboardController = LocalSoftwareKeyboardController.current val scope = rememberCoroutineScope() @@ -64,6 +70,11 @@ fun FlowPage( val homeViewState = homeViewModel.viewState.collectAsStateValue() val listState = if (pagingItems.itemCount > 0) viewState.listState else rememberLazyListState() + val articleListTonalElevation = + context.articleListTonalElevation.collectAsStateValue(initial = ArticleListTonalElevationPreference.default) + val articleListFeedIcon = + context.articleListFeedIcon.collectAsStateValue(initial = ArticleListFeedIconPreference.default) + val owner = LocalLifecycleOwner.current var isSyncing by remember { mutableStateOf(false) } homeViewModel.syncWorkLiveData.observe(owner) { @@ -98,15 +109,17 @@ fun FlowPage( Scaffold( modifier = Modifier - .background(MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp)) + .background(MaterialTheme.colorScheme.surfaceColorAtElevation(articleListTonalElevation.value.dp)) .statusBarsPadding() .navigationBarsPadding(), - containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp), + containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(articleListTonalElevation.value.dp), topBar = { SmallTopAppBar( title = {}, colors = TopAppBarDefaults.smallTopAppBarColors( - containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp), + containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation( + articleListTonalElevation.value.dp + ), ), navigationIcon = { FeedbackIconButton( @@ -115,7 +128,7 @@ fun FlowPage( tint = MaterialTheme.colorScheme.onSurface ) { onSearch = false - if(navController.previousBackStackEntry == null) { + if (navController.previousBackStackEntry == null) { navController.navigate(RouteName.FEEDS) { launchSingleTop = true } @@ -184,7 +197,7 @@ fun FlowPage( state = listState, ) { item { - DisplayTextHeader(filterState, isSyncing) + DisplayTextHeader(filterState, isSyncing, articleListFeedIcon.value) AnimatedVisibility( visible = markAsRead, enter = fadeIn() + expandVertically(), @@ -246,6 +259,8 @@ fun FlowPage( } ArticleList( pagingItems = pagingItems, + articleListFeedIcon = articleListFeedIcon.value, + articleListTonalElevation = articleListTonalElevation.value, ) { onSearch = false navController.navigate("${RouteName.READING}/${it.article.id}") { @@ -263,9 +278,6 @@ fun FlowPage( }, bottomBar = { FilterBar( - modifier = Modifier - .height(60.dp) - .fillMaxWidth(), filter = filterState.filter, filterOnClick = { flowViewModel.dispatch(FlowViewAction.ScrollToItem(0)) @@ -280,10 +292,11 @@ fun FlowPage( @Composable private fun DisplayTextHeader( filterState: FilterState, - isSyncing: Boolean + isSyncing: Boolean, + articleListFeedIcon: Boolean, ) { DisplayText( - modifier = Modifier.padding(start = 30.dp), + modifier = Modifier.padding(start = if (articleListFeedIcon) 30.dp else 0.dp), text = when { filterState.group != null -> filterState.group.name filterState.feed != null -> filterState.feed.name diff --git a/app/src/main/java/me/ash/reader/ui/page/home/flow/StickyHeader.kt b/app/src/main/java/me/ash/reader/ui/page/home/flow/StickyHeader.kt index b2ef0e3..93125e0 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/flow/StickyHeader.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/flow/StickyHeader.kt @@ -13,16 +13,20 @@ import androidx.compose.ui.unit.dp import me.ash.reader.ui.ext.surfaceColorAtElevation @Composable -fun StickyHeader(currentItemDay: String) { +fun StickyHeader( + currentItemDay: String, + articleListFeedIcon: Boolean, + articleListTonalElevation: Int, +) { Row( modifier = Modifier .fillMaxWidth() - .background(MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp)), + .background(MaterialTheme.colorScheme.surfaceColorAtElevation(articleListTonalElevation.dp)), verticalAlignment = Alignment.CenterVertically ) { Text( modifier = Modifier - .padding(start = if (true) 54.dp else 24.dp, bottom = 4.dp), + .padding(start = if (articleListFeedIcon) 54.dp else 24.dp, bottom = 4.dp), text = currentItemDay, color = MaterialTheme.colorScheme.primary, style = MaterialTheme.typography.labelLarge, diff --git a/app/src/main/java/me/ash/reader/ui/page/settings/color/ColorAndStyle.kt b/app/src/main/java/me/ash/reader/ui/page/settings/color/ColorAndStyle.kt index e3ba114..55e491b 100644 --- a/app/src/main/java/me/ash/reader/ui/page/settings/color/ColorAndStyle.kt +++ b/app/src/main/java/me/ash/reader/ui/page/settings/color/ColorAndStyle.kt @@ -30,6 +30,7 @@ import kotlinx.coroutines.launch import me.ash.reader.R import me.ash.reader.ui.component.* import me.ash.reader.ui.ext.* +import me.ash.reader.ui.page.common.RouteName import me.ash.reader.ui.page.settings.SettingItem import me.ash.reader.ui.svg.PALETTE import me.ash.reader.ui.svg.SVGString @@ -167,8 +168,11 @@ fun ColorAndStyle( ) {} SettingItem( title = stringResource(R.string.flow_page), - enable = false, - onClick = {}, + onClick = { + navController.navigate(RouteName.FLOW_PAGE_STYLE) { + launchSingleTop = true + } + }, ) {} SettingItem( title = stringResource(R.string.reading_page), diff --git a/app/src/main/java/me/ash/reader/ui/page/settings/color/flow/feedsPageStyle.kt b/app/src/main/java/me/ash/reader/ui/page/settings/color/flow/feedsPageStyle.kt new file mode 100644 index 0000000..ab04904 --- /dev/null +++ b/app/src/main/java/me/ash/reader/ui/page/settings/color/flow/feedsPageStyle.kt @@ -0,0 +1,346 @@ +package me.ash.reader.ui.page.settings.color.flow + +import android.annotation.SuppressLint +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.* +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.entity.Article +import me.ash.reader.data.entity.ArticleWithFeed +import me.ash.reader.data.entity.Feed +import me.ash.reader.data.entity.Filter +import me.ash.reader.data.preference.* +import me.ash.reader.data.preference.ArticleListDatePreference.Companion.articleListDate +import me.ash.reader.data.preference.ArticleListDescPreference.Companion.articleListDesc +import me.ash.reader.data.preference.ArticleListFeedIconPreference.Companion.articleListFeedIcon +import me.ash.reader.data.preference.ArticleListFeedNamePreference.Companion.articleListFeedName +import me.ash.reader.data.preference.ArticleListImagePreference.Companion.articleListImage +import me.ash.reader.data.preference.ArticleListTonalElevationPreference.Companion.articleListTonalElevation +import me.ash.reader.data.preference.FilterBarFilledPreference.Companion.filterBarFilled +import me.ash.reader.data.preference.FilterBarPaddingPreference.filterBarPadding +import me.ash.reader.data.preference.FilterBarStylePreference.Companion.filterBarStyle +import me.ash.reader.data.preference.FilterBarTonalElevationPreference.Companion.filterBarTonalElevation +import me.ash.reader.ui.component.* +import me.ash.reader.ui.ext.collectAsStateValue +import me.ash.reader.ui.ext.surfaceColorAtElevation +import me.ash.reader.ui.page.home.FilterBar +import me.ash.reader.ui.page.home.flow.ArticleItem +import me.ash.reader.ui.page.settings.SettingItem +import me.ash.reader.ui.theme.palette.onLight +import java.util.* + +@SuppressLint("FlowOperatorInvokedInComposition") +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun FlowPageStyle( + navController: NavHostController, +) { + val context = LocalContext.current + val scope = rememberCoroutineScope() + val filterBarStyle = + context.filterBarStyle.collectAsStateValue(initial = FilterBarStylePreference.default) + val filterBarFilled = + context.filterBarFilled.collectAsStateValue(initial = FilterBarFilledPreference.default) + val filterBarPadding = + context.filterBarPadding.collectAsStateValue(initial = FilterBarPaddingPreference.default) + val filterBarTonalElevation = + context.filterBarTonalElevation.collectAsStateValue(initial = FilterBarTonalElevationPreference.default) + val articleListFeedIcon = + context.articleListFeedIcon.collectAsStateValue(initial = ArticleListFeedIconPreference.default) + val articleListFeedName = + context.articleListFeedName.collectAsStateValue(initial = ArticleListFeedNamePreference.default) + val articleListImage = + context.articleListImage.collectAsStateValue(initial = ArticleListImagePreference.default) + val articleListDesc = + context.articleListDesc.collectAsStateValue(initial = ArticleListDescPreference.default) + val articleListDate = + context.articleListDate.collectAsStateValue(initial = ArticleListDatePreference.default) + val articleListTonalElevation = + context.articleListTonalElevation.collectAsStateValue(initial = ArticleListTonalElevationPreference.default) + + var filterBarStyleDialogVisible by remember { mutableStateOf(false) } + var filterBarPaddingDialogVisible by remember { mutableStateOf(false) } + var filterBarTonalElevationDialogVisible by remember { mutableStateOf(false) } + var articleListTonalElevationDialogVisible by remember { mutableStateOf(false) } + + var filterBarPaddingValue: Int? by remember { mutableStateOf(filterBarPadding) } + + Scaffold( + modifier = Modifier + .background(MaterialTheme.colorScheme.surface onLight MaterialTheme.colorScheme.inverseOnSurface) + .statusBarsPadding() + .navigationBarsPadding(), + containerColor = MaterialTheme.colorScheme.surface onLight MaterialTheme.colorScheme.inverseOnSurface, + topBar = { + SmallTopAppBar( + colors = TopAppBarDefaults.smallTopAppBarColors( + containerColor = MaterialTheme.colorScheme.surface onLight MaterialTheme.colorScheme.inverseOnSurface + ), + title = {}, + navigationIcon = { + FeedbackIconButton( + imageVector = Icons.Rounded.ArrowBack, + contentDescription = stringResource(R.string.back), + tint = MaterialTheme.colorScheme.onSurface + ) { + navController.popBackStack() + } + }, + actions = {} + ) + }, + content = { + LazyColumn { + item { + DisplayText(text = stringResource(R.string.flow_page), desc = "") + } + 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 + ) { + FeedsPagePreview(articleListTonalElevation) + } + Spacer(modifier = Modifier.height(24.dp)) + } + item { + Subtitle( + modifier = Modifier.padding(horizontal = 24.dp), + text = "过滤栏", + ) + SettingItem( + title = "样式", + desc = filterBarStyle.getDesc(context), + onClick = { + filterBarStyleDialogVisible = true + }, + ) {} + SettingItem( + title = "填充已选中的图标", + onClick = { + (!filterBarFilled).put(context, scope) + }, + ) { + Switch(activated = filterBarFilled.value) { + (!filterBarFilled).put(context, scope) + } + } + SettingItem( + title = "两端边距", + desc = "${filterBarPadding}dp", + onClick = { + filterBarPaddingDialogVisible = true + }, + ) {} + SettingItem( + title = "色调海拔", + desc = "${filterBarTonalElevation.value}dp", + onClick = { + filterBarTonalElevationDialogVisible = true + }, + ) {} + Spacer(modifier = Modifier.height(24.dp)) + } + item { + Subtitle( + modifier = Modifier.padding(horizontal = 24.dp), + text = "标题栏" + ) + SettingItem( + title = "“标记为已读”按钮的位置", + desc = "顶部", + onClick = {}, + ) {} + Spacer(modifier = Modifier.height(24.dp)) + } + item { + Subtitle( + modifier = Modifier.padding(horizontal = 24.dp), + text = "文章列表" + ) + SettingItem( + title = "显示订阅源图标", + onClick = { + (!articleListFeedIcon).put(context, scope) + }, + ) { + Switch(activated = articleListFeedIcon.value) { + (!articleListFeedIcon).put(context, scope) + } + } + SettingItem( + title = "显示订阅源名称", + onClick = { + (!articleListFeedName).put(context, scope) + }, + ) { + Switch(activated = articleListFeedName.value) { + (!articleListFeedName).put(context, scope) + } + } + SettingItem( + title = "显示文章插图", + onClick = {}, + ) { + Switch(activated = false, enable = false) + } + SettingItem( + title = "显示文章描述", + onClick = { + (!articleListDesc).put(context, scope) + }, + ) { + Switch(activated = articleListDesc.value) { + (!articleListDesc).put(context, scope) + } + } + SettingItem( + title = "显示文章发布时间", + onClick = { + (!articleListDate).put(context, scope) + }, + ) { + Switch(activated = articleListDate.value) { + (!articleListDate).put(context, scope) + } + } + SettingItem( + title = "色调海拔", + desc = "${articleListTonalElevation.value}dp", + onClick = { + articleListTonalElevationDialogVisible = true + }, + ) {} + Spacer(modifier = Modifier.height(24.dp)) + } + } + } + ) + + RadioDialog( + visible = filterBarStyleDialogVisible, + title = stringResource(R.string.initial_filter), + options = FilterBarStylePreference.values.map { + RadioDialogOption( + text = it.getDesc(context), + selected = filterBarStyle == it, + ) { + it.put(context, scope) + } + } + ) { + filterBarStyleDialogVisible = false + } + + TextFieldDialog( + visible = filterBarPaddingDialogVisible, + title = "两端边距", + value = (filterBarPaddingValue ?: "").toString(), + placeholder = stringResource(R.string.name), + onValueChange = { + filterBarPaddingValue = it.toIntOrNull() + }, + onDismissRequest = { + filterBarPaddingDialogVisible = false + }, + onConfirm = { + FilterBarPaddingPreference.put(context, scope, filterBarPaddingValue ?: 0) + filterBarPaddingDialogVisible = false + } + ) + + RadioDialog( + visible = filterBarTonalElevationDialogVisible, + title = stringResource(R.string.tonal_elevation), + options = FilterBarTonalElevationPreference.values.map { + RadioDialogOption( + text = it.getDesc(context), + selected = filterBarTonalElevation == it, + ) { + it.put(context, scope) + } + } + ) { + filterBarTonalElevationDialogVisible = false + } + + RadioDialog( + visible = articleListTonalElevationDialogVisible, + title = stringResource(R.string.tonal_elevation), + options = ArticleListTonalElevationPreference.values.map { + RadioDialogOption( + text = it.getDesc(context), + selected = articleListTonalElevation == it, + ) { + it.put(context, scope) + } + } + ) { + articleListTonalElevationDialogVisible = false + } +} + +@Composable +fun FeedsPagePreview( + articleListTonalElevation: ArticleListTonalElevationPreference, +) { + var filter by remember { mutableStateOf(Filter.Unread) } + + Column( + modifier = Modifier + .background( + color = MaterialTheme.colorScheme.surfaceColorAtElevation(articleListTonalElevation.value.dp), + shape = RoundedCornerShape(24.dp) + ) + ) { + Spacer(modifier = Modifier.height(12.dp)) + ArticleItem( + articleWithFeed = ArticleWithFeed( + Article( + id = "", + title = "《黎明之剑》撒花完结,白金远瞳的“希灵三部曲”值得你通宵阅读", + shortDescription = "昨日在找书的时候无意间发现,“远瞳”的《黎明之剑》突然冲上了起点热搜榜首位,去小说中查找原因,原来是这部六百多万字的作品已经完结了。四年的时间,这部小说始终占据科幻分类前三甲的位置,不得不说“远瞳”的实力的确不容小觑。", + rawDescription = "昨日在找书的时候无意间发现,“远瞳”的《黎明之剑》突然冲上了起点热搜榜首位,去小说中查找原因,原来是这部六百多万字的作品已经完结了。四年的时间,这部小说始终占据科幻分类前三甲的位置,不得不说“远瞳”的实力的确不容小觑。", + link = "", + feedId = "", + accountId = 0, + date = Date(), + ), + feed = Feed( + id = "", + name = "佛门射手", + icon = "", + accountId = 0, + groupId = "", + url = "", + ) + ) + ) + Spacer(modifier = Modifier.height(12.dp)) + FilterBar(modifier = Modifier.padding(horizontal = 12.dp), filter = filter) { + filter = it + } + } +} \ No newline at end of file diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 84ffc0d..5920644 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -79,7 +79,7 @@ 账户 本地、FreshRSS 颜色和样式 - 主题、色彩系统、字体大小 + 主题、色调样式、字体大小 交互 启动时、触感反馈 语言 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4aa92b7..114011d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -79,7 +79,7 @@ Accounts Local, FreshRSS Color & style - Theme, color system, font size + Theme, color style, font size Interaction On start, haptic feedback Languages