diff --git a/app/src/main/java/me/ash/reader/MainActivity.kt b/app/src/main/java/me/ash/reader/MainActivity.kt index d51c87c..1473493 100644 --- a/app/src/main/java/me/ash/reader/MainActivity.kt +++ b/app/src/main/java/me/ash/reader/MainActivity.kt @@ -18,6 +18,7 @@ import coil.memory.MemoryCache import coil.request.* import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.CompletableDeferred +import me.ash.reader.data.preference.SettingsProvider import me.ash.reader.ui.page.common.HomeEntry @AndroidEntryPoint @@ -28,7 +29,9 @@ class MainActivity : ComponentActivity(), ImageLoader { WindowCompat.setDecorFitsSystemWindows(window, false) Log.i("RLog", "onCreate: ${ProfileInstallerInitializer().create(this)}") setContent { - HomeEntry() + SettingsProvider { + HomeEntry() + } } } 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/CustomPrimaryColorPreference.kt b/app/src/main/java/me/ash/reader/data/preference/CustomPrimaryColorPreference.kt new file mode 100644 index 0000000..ad5d157 --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/preference/CustomPrimaryColorPreference.kt @@ -0,0 +1,22 @@ +package me.ash.reader.data.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 CustomPrimaryColorPreference { + const val default = "" + + fun put(context: Context, scope: CoroutineScope, value: String) { + scope.launch { + context.dataStore.put(DataStoreKeys.CustomPrimaryColor, value) + } + } + + fun fromPreferences(preferences: Preferences) = + preferences[DataStoreKeys.CustomPrimaryColor.key] ?: default +} \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/data/preference/FeedsFilterBarFilledPreference.kt b/app/src/main/java/me/ash/reader/data/preference/FeedsFilterBarFilledPreference.kt new file mode 100644 index 0000000..683d1c8 --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/preference/FeedsFilterBarFilledPreference.kt @@ -0,0 +1,41 @@ +package me.ash.reader.data.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 FeedsFilterBarFilledPreference(val value: Boolean) : Preference() { + object ON : FeedsFilterBarFilledPreference(true) + object OFF : FeedsFilterBarFilledPreference(false) + + override fun put(context: Context, scope: CoroutineScope) { + scope.launch { + context.dataStore.put( + DataStoreKeys.FeedsFilterBarFilled, + value + ) + } + } + + companion object { + val default = OFF + val values = listOf(ON, OFF) + + fun fromPreferences(preferences: Preferences) = + when (preferences[DataStoreKeys.FeedsFilterBarFilled.key]) { + true -> ON + false -> OFF + else -> default + } + } +} + +operator fun FeedsFilterBarFilledPreference.not(): FeedsFilterBarFilledPreference = + when (value) { + true -> FeedsFilterBarFilledPreference.OFF + false -> FeedsFilterBarFilledPreference.ON + } \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/data/preference/FeedsFilterBarPaddingPreference.kt b/app/src/main/java/me/ash/reader/data/preference/FeedsFilterBarPaddingPreference.kt new file mode 100644 index 0000000..b868411 --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/preference/FeedsFilterBarPaddingPreference.kt @@ -0,0 +1,22 @@ +package me.ash.reader.data.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 FeedsFilterBarPaddingPreference { + const val default = 60 + + fun put(context: Context, scope: CoroutineScope, value: Int) { + scope.launch { + context.dataStore.put(DataStoreKeys.FeedsFilterBarPadding, value) + } + } + + fun fromPreferences(preferences: Preferences) = + preferences[DataStoreKeys.FeedsFilterBarPadding.key] ?: default +} \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/data/preference/FeedsFilterBarStylePreference.kt b/app/src/main/java/me/ash/reader/data/preference/FeedsFilterBarStylePreference.kt new file mode 100644 index 0000000..c553834 --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/preference/FeedsFilterBarStylePreference.kt @@ -0,0 +1,45 @@ +package me.ash.reader.data.preference + +import android.content.Context +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 FeedsFilterBarStylePreference(val value: Int) : Preference() { + object Icon : FeedsFilterBarStylePreference(0) + object IconLabel : FeedsFilterBarStylePreference(1) + object IconLabelOnlySelected : FeedsFilterBarStylePreference(2) + + override fun put(context: Context, scope: CoroutineScope) { + scope.launch { + context.dataStore.put( + DataStoreKeys.FeedsFilterBarStyle, + value + ) + } + } + + fun getDesc(context: Context): String = + when (this) { + Icon -> context.getString(R.string.icons) + IconLabel -> context.getString(R.string.icons_and_labels) + IconLabelOnlySelected -> context.getString(R.string.icons_and_label_only_selected) + } + + companion object { + val default = Icon + val values = listOf(Icon, IconLabel, IconLabelOnlySelected) + + fun fromPreferences(preferences: Preferences): FeedsFilterBarStylePreference = + when (preferences[DataStoreKeys.FeedsFilterBarStyle.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/FeedsFilterBarTonalElevationPreference.kt b/app/src/main/java/me/ash/reader/data/preference/FeedsFilterBarTonalElevationPreference.kt new file mode 100644 index 0000000..ba08eb3 --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/preference/FeedsFilterBarTonalElevationPreference.kt @@ -0,0 +1,53 @@ +package me.ash.reader.data.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 FeedsFilterBarTonalElevationPreference(val value: Int) : Preference() { + object Level0 : FeedsFilterBarTonalElevationPreference(0) + object Level1 : FeedsFilterBarTonalElevationPreference(1) + object Level2 : FeedsFilterBarTonalElevationPreference(3) + object Level3 : FeedsFilterBarTonalElevationPreference(6) + object Level4 : FeedsFilterBarTonalElevationPreference(8) + object Level5 : FeedsFilterBarTonalElevationPreference(12) + + override fun put(context: Context, scope: CoroutineScope) { + scope.launch { + context.dataStore.put( + DataStoreKeys.FeedsFilterBarTonalElevation, + 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) + + fun fromPreferences(preferences: Preferences) = + when (preferences[DataStoreKeys.FeedsFilterBarTonalElevation.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/FeedsGroupListExpandPreference.kt b/app/src/main/java/me/ash/reader/data/preference/FeedsGroupListExpandPreference.kt new file mode 100644 index 0000000..2056631 --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/preference/FeedsGroupListExpandPreference.kt @@ -0,0 +1,41 @@ +package me.ash.reader.data.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 FeedsGroupListExpandPreference(val value: Boolean) : Preference() { + object ON : FeedsGroupListExpandPreference(true) + object OFF : FeedsGroupListExpandPreference(false) + + override fun put(context: Context, scope: CoroutineScope) { + scope.launch { + context.dataStore.put( + DataStoreKeys.FeedsGroupListExpand, + value + ) + } + } + + companion object { + val default = ON + val values = listOf(ON, OFF) + + fun fromPreferences(preferences: Preferences) = + when (preferences[DataStoreKeys.FeedsGroupListExpand.key]) { + true -> ON + false -> OFF + else -> default + } + } +} + +operator fun FeedsGroupListExpandPreference.not(): FeedsGroupListExpandPreference = + when (value) { + true -> FeedsGroupListExpandPreference.OFF + false -> FeedsGroupListExpandPreference.ON + } \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/data/preference/FeedsGroupListTonalElevationPreference.kt b/app/src/main/java/me/ash/reader/data/preference/FeedsGroupListTonalElevationPreference.kt new file mode 100644 index 0000000..c65afe0 --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/preference/FeedsGroupListTonalElevationPreference.kt @@ -0,0 +1,53 @@ +package me.ash.reader.data.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 FeedsGroupListTonalElevationPreference(val value: Int) : Preference() { + object Level0 : FeedsGroupListTonalElevationPreference(0) + object Level1 : FeedsGroupListTonalElevationPreference(1) + object Level2 : FeedsGroupListTonalElevationPreference(3) + object Level3 : FeedsGroupListTonalElevationPreference(6) + object Level4 : FeedsGroupListTonalElevationPreference(8) + object Level5 : FeedsGroupListTonalElevationPreference(12) + + override fun put(context: Context, scope: CoroutineScope) { + scope.launch { + context.dataStore.put( + DataStoreKeys.FeedsGroupListTonalElevation, + 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) + + fun fromPreferences(preferences: Preferences) = + when (preferences[DataStoreKeys.FeedsGroupListTonalElevation.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/FeedsTopBarTonalElevationPreference.kt b/app/src/main/java/me/ash/reader/data/preference/FeedsTopBarTonalElevationPreference.kt new file mode 100644 index 0000000..b541b4c --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/preference/FeedsTopBarTonalElevationPreference.kt @@ -0,0 +1,53 @@ +package me.ash.reader.data.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 FeedsTopBarTonalElevationPreference(val value: Int) : Preference() { + object Level0 : FeedsTopBarTonalElevationPreference(0) + object Level1 : FeedsTopBarTonalElevationPreference(1) + object Level2 : FeedsTopBarTonalElevationPreference(3) + object Level3 : FeedsTopBarTonalElevationPreference(6) + object Level4 : FeedsTopBarTonalElevationPreference(8) + object Level5 : FeedsTopBarTonalElevationPreference(12) + + override fun put(context: Context, scope: CoroutineScope) { + scope.launch { + context.dataStore.put( + DataStoreKeys.FeedsTopBarTonalElevation, + 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) + + fun fromPreferences(preferences: Preferences) = + when (preferences[DataStoreKeys.FeedsTopBarTonalElevation.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/FlowArticleListDateStickyHeaderPreference.kt b/app/src/main/java/me/ash/reader/data/preference/FlowArticleListDateStickyHeaderPreference.kt new file mode 100644 index 0000000..cb7f6c7 --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/preference/FlowArticleListDateStickyHeaderPreference.kt @@ -0,0 +1,41 @@ +package me.ash.reader.data.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 FlowArticleListDateStickyHeaderPreference(val value: Boolean) : Preference() { + object ON : FlowArticleListDateStickyHeaderPreference(true) + object OFF : FlowArticleListDateStickyHeaderPreference(false) + + override fun put(context: Context, scope: CoroutineScope) { + scope.launch { + context.dataStore.put( + DataStoreKeys.FlowArticleListDateStickyHeader, + value + ) + } + } + + companion object { + val default = ON + val values = listOf(ON, OFF) + + fun fromPreferences(preferences: Preferences) = + when (preferences[DataStoreKeys.FlowArticleListDateStickyHeader.key]) { + true -> ON + false -> OFF + else -> default + } + } +} + +operator fun FlowArticleListDateStickyHeaderPreference.not(): FlowArticleListDateStickyHeaderPreference = + when (value) { + true -> FlowArticleListDateStickyHeaderPreference.OFF + false -> FlowArticleListDateStickyHeaderPreference.ON + } \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/data/preference/FlowArticleListDescPreference.kt b/app/src/main/java/me/ash/reader/data/preference/FlowArticleListDescPreference.kt new file mode 100644 index 0000000..8373917 --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/preference/FlowArticleListDescPreference.kt @@ -0,0 +1,41 @@ +package me.ash.reader.data.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 FlowArticleListDescPreference(val value: Boolean) : Preference() { + object ON : FlowArticleListDescPreference(true) + object OFF : FlowArticleListDescPreference(false) + + override fun put(context: Context, scope: CoroutineScope) { + scope.launch { + context.dataStore.put( + DataStoreKeys.FlowArticleListDesc, + value + ) + } + } + + companion object { + val default = ON + val values = listOf(ON, OFF) + + fun fromPreferences(preferences: Preferences) = + when (preferences[DataStoreKeys.FlowArticleListDesc.key]) { + true -> ON + false -> OFF + else -> default + } + } +} + +operator fun FlowArticleListDescPreference.not(): FlowArticleListDescPreference = + when (value) { + true -> FlowArticleListDescPreference.OFF + false -> FlowArticleListDescPreference.ON + } \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/data/preference/FlowArticleListFeedIconPreference.kt b/app/src/main/java/me/ash/reader/data/preference/FlowArticleListFeedIconPreference.kt new file mode 100644 index 0000000..f93708e --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/preference/FlowArticleListFeedIconPreference.kt @@ -0,0 +1,41 @@ +package me.ash.reader.data.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 FlowArticleListFeedIconPreference(val value: Boolean) : Preference() { + object ON : FlowArticleListFeedIconPreference(true) + object OFF : FlowArticleListFeedIconPreference(false) + + override fun put(context: Context, scope: CoroutineScope) { + scope.launch { + context.dataStore.put( + DataStoreKeys.FlowArticleListFeedIcon, + value + ) + } + } + + companion object { + val default = ON + val values = listOf(ON, OFF) + + fun fromPreferences(preferences: Preferences) = + when (preferences[DataStoreKeys.FlowArticleListFeedIcon.key]) { + true -> ON + false -> OFF + else -> default + } + } +} + +operator fun FlowArticleListFeedIconPreference.not(): FlowArticleListFeedIconPreference = + when (value) { + true -> FlowArticleListFeedIconPreference.OFF + false -> FlowArticleListFeedIconPreference.ON + } \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/data/preference/FlowArticleListFeedNamePreference.kt b/app/src/main/java/me/ash/reader/data/preference/FlowArticleListFeedNamePreference.kt new file mode 100644 index 0000000..994289d --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/preference/FlowArticleListFeedNamePreference.kt @@ -0,0 +1,41 @@ +package me.ash.reader.data.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 FlowArticleListFeedNamePreference(val value: Boolean) : Preference() { + object ON : FlowArticleListFeedNamePreference(true) + object OFF : FlowArticleListFeedNamePreference(false) + + override fun put(context: Context, scope: CoroutineScope) { + scope.launch { + context.dataStore.put( + DataStoreKeys.FlowArticleListFeedName, + value + ) + } + } + + companion object { + val default = ON + val values = listOf(ON, OFF) + + fun fromPreferences(preferences: Preferences) = + when (preferences[DataStoreKeys.FlowArticleListFeedName.key]) { + true -> ON + false -> OFF + else -> default + } + } +} + +operator fun FlowArticleListFeedNamePreference.not(): FlowArticleListFeedNamePreference = + when (value) { + true -> FlowArticleListFeedNamePreference.OFF + false -> FlowArticleListFeedNamePreference.ON + } \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/data/preference/FlowArticleListImagePreference.kt b/app/src/main/java/me/ash/reader/data/preference/FlowArticleListImagePreference.kt new file mode 100644 index 0000000..ef56d37 --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/preference/FlowArticleListImagePreference.kt @@ -0,0 +1,41 @@ +package me.ash.reader.data.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 FlowArticleListImagePreference(val value: Boolean) : Preference() { + object ON : FlowArticleListImagePreference(true) + object OFF : FlowArticleListImagePreference(false) + + override fun put(context: Context, scope: CoroutineScope) { + scope.launch { + context.dataStore.put( + DataStoreKeys.FlowArticleListImage, + value + ) + } + } + + companion object { + val default = ON + val values = listOf(ON, OFF) + + fun fromPreferences(preferences: Preferences) = + when (preferences[DataStoreKeys.FlowArticleListImage.key]) { + true -> ON + false -> OFF + else -> default + } + } +} + +operator fun FlowArticleListImagePreference.not(): FlowArticleListImagePreference = + when (value) { + true -> FlowArticleListImagePreference.OFF + false -> FlowArticleListImagePreference.ON + } \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/data/preference/FlowArticleListTimePreference.kt b/app/src/main/java/me/ash/reader/data/preference/FlowArticleListTimePreference.kt new file mode 100644 index 0000000..cbad148 --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/preference/FlowArticleListTimePreference.kt @@ -0,0 +1,41 @@ +package me.ash.reader.data.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 FlowArticleListTimePreference(val value: Boolean) : Preference() { + object ON : FlowArticleListTimePreference(true) + object OFF : FlowArticleListTimePreference(false) + + override fun put(context: Context, scope: CoroutineScope) { + scope.launch { + context.dataStore.put( + DataStoreKeys.FlowArticleListTime, + value + ) + } + } + + companion object { + val default = ON + val values = listOf(ON, OFF) + + fun fromPreferences(preferences: Preferences) = + when (preferences[DataStoreKeys.FlowArticleListTime.key]) { + true -> ON + false -> OFF + else -> default + } + } +} + +operator fun FlowArticleListTimePreference.not(): FlowArticleListTimePreference = + when (value) { + true -> FlowArticleListTimePreference.OFF + false -> FlowArticleListTimePreference.ON + } \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/data/preference/FlowArticleListTonalElevationPreference.kt b/app/src/main/java/me/ash/reader/data/preference/FlowArticleListTonalElevationPreference.kt new file mode 100644 index 0000000..2d3dd1b --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/preference/FlowArticleListTonalElevationPreference.kt @@ -0,0 +1,53 @@ +package me.ash.reader.data.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 FlowArticleListTonalElevationPreference(val value: Int) : Preference() { + object Level0 : FlowArticleListTonalElevationPreference(0) + object Level1 : FlowArticleListTonalElevationPreference(1) + object Level2 : FlowArticleListTonalElevationPreference(3) + object Level3 : FlowArticleListTonalElevationPreference(6) + object Level4 : FlowArticleListTonalElevationPreference(8) + object Level5 : FlowArticleListTonalElevationPreference(12) + + override fun put(context: Context, scope: CoroutineScope) { + scope.launch { + context.dataStore.put( + DataStoreKeys.FlowArticleListTonalElevation, + 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) + + fun fromPreferences(preferences: Preferences) = + when (preferences[DataStoreKeys.FlowArticleListTonalElevation.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/FlowFilterBarFilledPreference.kt b/app/src/main/java/me/ash/reader/data/preference/FlowFilterBarFilledPreference.kt new file mode 100644 index 0000000..632d651 --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/preference/FlowFilterBarFilledPreference.kt @@ -0,0 +1,41 @@ +package me.ash.reader.data.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 FlowFilterBarFilledPreference(val value: Boolean) : Preference() { + object ON : FlowFilterBarFilledPreference(true) + object OFF : FlowFilterBarFilledPreference(false) + + override fun put(context: Context, scope: CoroutineScope) { + scope.launch { + context.dataStore.put( + DataStoreKeys.FlowFilterBarFilled, + value + ) + } + } + + companion object { + val default = OFF + val values = listOf(ON, OFF) + + fun fromPreferences(preferences: Preferences) = + when (preferences[DataStoreKeys.FlowFilterBarFilled.key]) { + true -> ON + false -> OFF + else -> default + } + } +} + +operator fun FlowFilterBarFilledPreference.not(): FlowFilterBarFilledPreference = + when (value) { + true -> FlowFilterBarFilledPreference.OFF + false -> FlowFilterBarFilledPreference.ON + } \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/data/preference/FlowFilterBarPaddingPreference.kt b/app/src/main/java/me/ash/reader/data/preference/FlowFilterBarPaddingPreference.kt new file mode 100644 index 0000000..b9b08aa --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/preference/FlowFilterBarPaddingPreference.kt @@ -0,0 +1,22 @@ +package me.ash.reader.data.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 FlowFilterBarPaddingPreference { + const val default = 60 + + fun put(context: Context, scope: CoroutineScope, value: Int) { + scope.launch { + context.dataStore.put(DataStoreKeys.FlowFilterBarPadding, value) + } + } + + fun fromPreferences(preferences: Preferences) = + preferences[DataStoreKeys.FlowFilterBarPadding.key] ?: default +} \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/data/preference/FlowFilterBarStylePreference.kt b/app/src/main/java/me/ash/reader/data/preference/FlowFilterBarStylePreference.kt new file mode 100644 index 0000000..a25c9d2 --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/preference/FlowFilterBarStylePreference.kt @@ -0,0 +1,45 @@ +package me.ash.reader.data.preference + +import android.content.Context +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 FlowFilterBarStylePreference(val value: Int) : Preference() { + object Icon : FlowFilterBarStylePreference(0) + object IconLabel : FlowFilterBarStylePreference(1) + object IconLabelOnlySelected : FlowFilterBarStylePreference(2) + + override fun put(context: Context, scope: CoroutineScope) { + scope.launch { + context.dataStore.put( + DataStoreKeys.FlowFilterBarStyle, + value + ) + } + } + + fun getDesc(context: Context): String = + when (this) { + Icon -> context.getString(R.string.icons) + IconLabel -> context.getString(R.string.icons_and_labels) + IconLabelOnlySelected -> context.getString(R.string.icons_and_label_only_selected) + } + + companion object { + val default = Icon + val values = listOf(Icon, IconLabel, IconLabelOnlySelected) + + fun fromPreferences(preferences: Preferences) = + when (preferences[DataStoreKeys.FlowFilterBarStyle.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/FlowFilterBarTonalElevationPreference.kt b/app/src/main/java/me/ash/reader/data/preference/FlowFilterBarTonalElevationPreference.kt new file mode 100644 index 0000000..067d58c --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/preference/FlowFilterBarTonalElevationPreference.kt @@ -0,0 +1,53 @@ +package me.ash.reader.data.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 FlowFilterBarTonalElevationPreference(val value: Int) : Preference() { + object Level0 : FlowFilterBarTonalElevationPreference(0) + object Level1 : FlowFilterBarTonalElevationPreference(1) + object Level2 : FlowFilterBarTonalElevationPreference(3) + object Level3 : FlowFilterBarTonalElevationPreference(6) + object Level4 : FlowFilterBarTonalElevationPreference(8) + object Level5 : FlowFilterBarTonalElevationPreference(12) + + override fun put(context: Context, scope: CoroutineScope) { + scope.launch { + context.dataStore.put( + DataStoreKeys.FlowFilterBarTonalElevation, + 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) + + fun fromPreferences(preferences: Preferences) = + when (preferences[DataStoreKeys.FlowFilterBarTonalElevation.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/FlowTopBarTonalElevationPreference.kt b/app/src/main/java/me/ash/reader/data/preference/FlowTopBarTonalElevationPreference.kt new file mode 100644 index 0000000..90938dc --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/preference/FlowTopBarTonalElevationPreference.kt @@ -0,0 +1,53 @@ +package me.ash.reader.data.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 FlowTopBarTonalElevationPreference(val value: Int) : Preference() { + object Level0 : FlowTopBarTonalElevationPreference(0) + object Level1 : FlowTopBarTonalElevationPreference(1) + object Level2 : FlowTopBarTonalElevationPreference(3) + object Level3 : FlowTopBarTonalElevationPreference(6) + object Level4 : FlowTopBarTonalElevationPreference(8) + object Level5 : FlowTopBarTonalElevationPreference(12) + + override fun put(context: Context, scope: CoroutineScope) { + scope.launch { + context.dataStore.put( + DataStoreKeys.FlowTopBarTonalElevation, + 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) + + fun fromPreferences(preferences: Preferences) = + when (preferences[DataStoreKeys.FlowTopBarTonalElevation.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/data/preference/Settings.kt b/app/src/main/java/me/ash/reader/data/preference/Settings.kt new file mode 100644 index 0000000..b1d4022 --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/preference/Settings.kt @@ -0,0 +1,152 @@ +package me.ash.reader.data.preference + +import android.util.Log +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.compositionLocalOf +import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalContext +import androidx.datastore.preferences.core.Preferences +import kotlinx.coroutines.flow.map +import me.ash.reader.ui.ext.collectAsStateValue +import me.ash.reader.ui.ext.dataStore + +data class Settings( + val themeIndex: Int = ThemeIndexPreference.default, + val customPrimaryColor: String = CustomPrimaryColorPreference.default, + + val feedsFilterBarStyle: FeedsFilterBarStylePreference = FeedsFilterBarStylePreference.default, + val feedsFilterBarFilled: FeedsFilterBarFilledPreference = FeedsFilterBarFilledPreference.default, + val feedsFilterBarPadding: Int = FeedsFilterBarPaddingPreference.default, + val feedsFilterBarTonalElevation: FeedsFilterBarTonalElevationPreference = FeedsFilterBarTonalElevationPreference.default, + val feedsTopBarTonalElevation: FeedsTopBarTonalElevationPreference = FeedsTopBarTonalElevationPreference.default, + val feedsGroupListExpand: FeedsGroupListExpandPreference = FeedsGroupListExpandPreference.default, + val feedsGroupListTonalElevation: FeedsGroupListTonalElevationPreference = FeedsGroupListTonalElevationPreference.default, + + val flowFilterBarStyle: FlowFilterBarStylePreference = FlowFilterBarStylePreference.default, + val flowFilterBarFilled: FlowFilterBarFilledPreference = FlowFilterBarFilledPreference.default, + val flowFilterBarPadding: Int = FlowFilterBarPaddingPreference.default, + val flowFilterBarTonalElevation: FlowFilterBarTonalElevationPreference = FlowFilterBarTonalElevationPreference.default, + val flowTopBarTonalElevation: FlowTopBarTonalElevationPreference = FlowTopBarTonalElevationPreference.default, + val flowArticleListFeedIcon: FlowArticleListFeedIconPreference = FlowArticleListFeedIconPreference.default, + val flowArticleListFeedName: FlowArticleListFeedNamePreference = FlowArticleListFeedNamePreference.default, + val flowArticleListImage: FlowArticleListImagePreference = FlowArticleListImagePreference.default, + val flowArticleListDesc: FlowArticleListDescPreference = FlowArticleListDescPreference.default, + val flowArticleListTime: FlowArticleListTimePreference = FlowArticleListTimePreference.default, + val flowArticleListDateStickyHeader: FlowArticleListDateStickyHeaderPreference = FlowArticleListDateStickyHeaderPreference.default, + val flowArticleListTonalElevation: FlowArticleListTonalElevationPreference = FlowArticleListTonalElevationPreference.default, +) + +fun Preferences.toSettings(): Settings { + return Settings( + themeIndex = ThemeIndexPreference.fromPreferences(this), + customPrimaryColor = CustomPrimaryColorPreference.fromPreferences(this), + + feedsFilterBarStyle = FeedsFilterBarStylePreference.fromPreferences(this), + feedsFilterBarFilled = FeedsFilterBarFilledPreference.fromPreferences(this), + feedsFilterBarPadding = FeedsFilterBarPaddingPreference.fromPreferences(this), + feedsFilterBarTonalElevation = FeedsFilterBarTonalElevationPreference.fromPreferences(this), + feedsTopBarTonalElevation = FeedsTopBarTonalElevationPreference.fromPreferences(this), + feedsGroupListExpand = FeedsGroupListExpandPreference.fromPreferences(this), + feedsGroupListTonalElevation = FeedsGroupListTonalElevationPreference.fromPreferences(this), + + flowFilterBarStyle = FlowFilterBarStylePreference.fromPreferences(this), + flowFilterBarFilled = FlowFilterBarFilledPreference.fromPreferences(this), + flowFilterBarPadding = FlowFilterBarPaddingPreference.fromPreferences(this), + flowFilterBarTonalElevation = FlowFilterBarTonalElevationPreference.fromPreferences(this), + flowTopBarTonalElevation = FlowTopBarTonalElevationPreference.fromPreferences(this), + flowArticleListFeedIcon = FlowArticleListFeedIconPreference.fromPreferences(this), + flowArticleListFeedName = FlowArticleListFeedNamePreference.fromPreferences(this), + flowArticleListImage = FlowArticleListImagePreference.fromPreferences(this), + flowArticleListDesc = FlowArticleListDescPreference.fromPreferences(this), + flowArticleListTime = FlowArticleListTimePreference.fromPreferences(this), + flowArticleListDateStickyHeader = FlowArticleListDateStickyHeaderPreference.fromPreferences(this), + flowArticleListTonalElevation = FlowArticleListTonalElevationPreference.fromPreferences(this), + ) +} + +@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( + LocalThemeIndex provides settings.themeIndex, + LocalCustomPrimaryColor provides settings.customPrimaryColor, + + 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, + ) { + content() + } +} + +val LocalThemeIndex = + compositionLocalOf { ThemeIndexPreference.default } +val LocalCustomPrimaryColor = + compositionLocalOf { CustomPrimaryColorPreference.default } + +val LocalFeedsFilterBarStyle = + compositionLocalOf { FeedsFilterBarStylePreference.default } +val LocalFeedsFilterBarFilled = + compositionLocalOf { FeedsFilterBarFilledPreference.default } +val LocalFeedsFilterBarPadding = + compositionLocalOf { FeedsFilterBarPaddingPreference.default } +val LocalFeedsFilterBarTonalElevation = + compositionLocalOf { FeedsFilterBarTonalElevationPreference.default } +val LocalFeedsTopBarTonalElevation = + compositionLocalOf { FeedsTopBarTonalElevationPreference.default } +val LocalFeedsGroupListExpand = + compositionLocalOf { FeedsGroupListExpandPreference.default } +val LocalFeedsGroupListTonalElevation = + compositionLocalOf { FeedsGroupListTonalElevationPreference.default } + +val LocalFlowFilterBarStyle = + compositionLocalOf { FlowFilterBarStylePreference.default } +val LocalFlowFilterBarFilled = + compositionLocalOf { FlowFilterBarFilledPreference.default } +val LocalFlowFilterBarPadding = + compositionLocalOf { FlowFilterBarPaddingPreference.default } +val LocalFlowFilterBarTonalElevation = + compositionLocalOf { FlowFilterBarTonalElevationPreference.default } +val LocalFlowTopBarTonalElevation = + compositionLocalOf { FlowTopBarTonalElevationPreference.default } +val LocalFlowArticleListFeedIcon = + compositionLocalOf { FlowArticleListFeedIconPreference.default } +val LocalFlowArticleListFeedName = + compositionLocalOf { FlowArticleListFeedNamePreference.default } +val LocalFlowArticleListImage = + compositionLocalOf { FlowArticleListImagePreference.default } +val LocalFlowArticleListDesc = + compositionLocalOf { FlowArticleListDescPreference.default } +val LocalFlowArticleListTime = + compositionLocalOf { FlowArticleListTimePreference.default } +val LocalFlowArticleListDateStickyHeader = + compositionLocalOf { FlowArticleListDateStickyHeaderPreference.default } +val LocalFlowArticleListTonalElevation = + compositionLocalOf { FlowArticleListTonalElevationPreference.default } diff --git a/app/src/main/java/me/ash/reader/data/preference/ThemeIndexPreference.kt b/app/src/main/java/me/ash/reader/data/preference/ThemeIndexPreference.kt new file mode 100644 index 0000000..2c947e2 --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/preference/ThemeIndexPreference.kt @@ -0,0 +1,23 @@ +package me.ash.reader.data.preference + +import android.content.Context +import androidx.datastore.preferences.core.Preferences +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +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 ThemeIndexPreference { + const val default = 5 + + fun put(context: Context, scope: CoroutineScope, value: Int) { + scope.launch(Dispatchers.IO) { + context.dataStore.put(DataStoreKeys.ThemeIndex, value) + } + } + + fun fromPreferences(preferences: Preferences) = + preferences[DataStoreKeys.ThemeIndex.key] ?: default +} \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/ui/component/Switch.kt b/app/src/main/java/me/ash/reader/ui/component/Switch.kt index ab00983..53b545c 100644 --- a/app/src/main/java/me/ash/reader/ui/component/Switch.kt +++ b/app/src/main/java/me/ash/reader/ui/component/Switch.kt @@ -45,8 +45,8 @@ fun Switch( .alpha(if (enable) 1f else 0.5f), shape = CircleShape, color = animateColorAsState( - if (activated) (tonalPalettes primary 40) onDark (tonalPalettes neutralVariant 50) - else (tonalPalettes neutralVariant 50) onDark (tonalPalettes neutral 60) + if (activated) (tonalPalettes primary 40) onDark (tonalPalettes secondary 50) + else (tonalPalettes neutralVariant 50) onDark (tonalPalettes neutral 30) ).value ) { Box( @@ -61,7 +61,7 @@ fun Switch( shape = CircleShape, color = animateColorAsState( if (activated) tonalPalettes primary 90 - else (tonalPalettes neutralVariant 70) onDark (tonalPalettes neutral 30) + else (tonalPalettes neutralVariant 70) onDark (tonalPalettes neutral 60) ).value ) {} } diff --git a/app/src/main/java/me/ash/reader/ui/component/Tips.kt b/app/src/main/java/me/ash/reader/ui/component/Tips.kt new file mode 100644 index 0000000..bab7833 --- /dev/null +++ b/app/src/main/java/me/ash/reader/ui/component/Tips.kt @@ -0,0 +1,38 @@ +package me.ash.reader.ui.component + +import androidx.compose.foundation.layout.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Info +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import me.ash.reader.R + +@Composable +fun Tips( + modifier: Modifier = Modifier, + text: String, +) { + Column( + modifier = modifier + .fillMaxWidth() + .padding(horizontal = 24.dp, vertical = 16.dp), + ) { + Icon( + imageVector = Icons.Outlined.Info, + contentDescription = stringResource(R.string.tips_and_support), + tint = MaterialTheme.colorScheme.onSurfaceVariant, + ) + Spacer(modifier = Modifier.height(16.dp)) + Text( + text = text, + style = MaterialTheme.typography.labelLarge.copy(fontWeight = FontWeight.Light), + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/ui/ext/ColorScheme.kt b/app/src/main/java/me/ash/reader/ui/ext/ColorScheme.kt index d7fb5f5..1e2276f 100644 --- a/app/src/main/java/me/ash/reader/ui/ext/ColorScheme.kt +++ b/app/src/main/java/me/ash/reader/ui/ext/ColorScheme.kt @@ -9,8 +9,16 @@ import kotlin.math.ln fun ColorScheme.surfaceColorAtElevation( elevation: Dp, + color: Color = surface, +): Color = color.atElevation(surfaceTint, elevation) + +fun Color.atElevation( + sourceColor: Color, + elevation: Dp, ): Color { - if (elevation == 0.dp) return surface - val alpha = ((4.5f * ln(elevation.value + 1)) + 2f) / 100f - return primary.copy(alpha = alpha).compositeOver(surface) -} \ No newline at end of file + if (elevation == 0.dp) return this + return sourceColor.copy(alpha = elevation.alphaLN(constant = 4.5f)).compositeOver(this) +} + +fun Dp.alphaLN(constant: Float = 1f, weight: Float = 0f): Float = + ((constant * ln(value + 1) + weight) + 2f) / 100f 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..79728c4 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 @@ -5,10 +5,12 @@ import android.util.Log import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.* import androidx.datastore.preferences.preferencesDataStore +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext import java.io.IOException val Context.dataStore: DataStore by preferencesDataStore(name = "settings") @@ -31,10 +33,7 @@ val Context.currentAccountId: Int get() = this.dataStore.get(DataStoreKeys.CurrentAccountId)!! 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 get() = this.dataStore.get(DataStoreKeys.InitialPage) ?: 0 val Context.initialFilter: Int @@ -42,7 +41,9 @@ val Context.initialFilter: Int suspend fun DataStore.put(dataStoreKeys: DataStoreKeys, value: T) { this.edit { - it[dataStoreKeys.key] = value + withContext(Dispatchers.IO) { + it[dataStoreKeys.key] = value + } } } @@ -129,6 +130,101 @@ sealed class DataStoreKeys { get() = stringPreferencesKey("customPrimaryColor") } + object FeedsFilterBarStyle : DataStoreKeys() { + override val key: Preferences.Key + get() = intPreferencesKey("feedsFilterBarStyle") + } + + object FeedsFilterBarFilled : DataStoreKeys() { + override val key: Preferences.Key + get() = booleanPreferencesKey("feedsFilterBarFilled") + } + + object FeedsFilterBarPadding : DataStoreKeys() { + override val key: Preferences.Key + get() = intPreferencesKey("feedsFilterBarPadding") + } + + object FeedsFilterBarTonalElevation : DataStoreKeys() { + override val key: Preferences.Key + get() = intPreferencesKey("feedsFilterBarTonalElevation") + } + + object FeedsTopBarTonalElevation : DataStoreKeys() { + override val key: Preferences.Key + get() = intPreferencesKey("feedsTopBarTonalElevation") + } + + object FeedsGroupListExpand : DataStoreKeys() { + override val key: Preferences.Key + get() = booleanPreferencesKey("feedsGroupListExpand") + } + + object FeedsGroupListTonalElevation : DataStoreKeys() { + override val key: Preferences.Key + get() = intPreferencesKey("feedsGroupListTonalElevation") + } + + object FlowFilterBarStyle : DataStoreKeys() { + override val key: Preferences.Key + get() = intPreferencesKey("flowFilterBarStyle") + } + + object FlowFilterBarFilled : DataStoreKeys() { + override val key: Preferences.Key + get() = booleanPreferencesKey("flowFilterBarFilled") + } + + object FlowFilterBarPadding : DataStoreKeys() { + override val key: Preferences.Key + get() = intPreferencesKey("flowFilterBarPadding") + } + + object FlowFilterBarTonalElevation : DataStoreKeys() { + override val key: Preferences.Key + get() = intPreferencesKey("flowFilterBarTonalElevation") + } + + object FlowTopBarTonalElevation : DataStoreKeys() { + override val key: Preferences.Key + get() = intPreferencesKey("flowTopBarTonalElevation") + } + + object FlowArticleListFeedIcon : DataStoreKeys() { + override val key: Preferences.Key + get() = booleanPreferencesKey("flowArticleListFeedIcon") + } + + object FlowArticleListFeedName : DataStoreKeys() { + override val key: Preferences.Key + get() = booleanPreferencesKey("flowArticleListFeedName") + } + + object FlowArticleListImage : DataStoreKeys() { + override val key: Preferences.Key + get() = booleanPreferencesKey("flowArticleListImage") + } + + object FlowArticleListDesc : DataStoreKeys() { + override val key: Preferences.Key + get() = booleanPreferencesKey("flowArticleListDesc") + } + + object FlowArticleListTime : DataStoreKeys() { + override val key: Preferences.Key + get() = booleanPreferencesKey("flowArticleListTime") + } + + object FlowArticleListDateStickyHeader : DataStoreKeys() { + override val key: Preferences.Key + get() = booleanPreferencesKey("flowArticleListDateStickyHeader") + } + + object FlowArticleListTonalElevation : DataStoreKeys() { + override val key: Preferences.Key + get() = intPreferencesKey("flowArticleListTonalElevation") + } + 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..4aed762 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,8 @@ 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.feeds.FeedsPageStyle +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 @@ -34,6 +36,7 @@ fun HomeEntry( homeViewModel: HomeViewModel = hiltViewModel(), ) { val context = LocalContext.current + val viewState = homeViewModel.viewState.collectAsStateValue() val filterState = homeViewModel.filterState.collectAsStateValue() val pagingItems = viewState.pagingData.collectAsLazyPagingItems() @@ -97,9 +100,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 +119,29 @@ 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.FEEDS_PAGE_STYLE) { + FeedsPageStyle(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..baf917a 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,25 @@ 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 FEEDS_PAGE_STYLE = "feeds_page_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 a2f217d..75b517a 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,70 +1,84 @@ 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.LocalView -import androidx.compose.ui.unit.dp -import androidx.compose.ui.zIndex +import androidx.compose.ui.unit.Dp import com.google.accompanist.pager.ExperimentalPagerApi import me.ash.reader.data.entity.Filter +import me.ash.reader.data.preference.FlowFilterBarStylePreference import me.ash.reader.ui.ext.getName -import me.ash.reader.ui.theme.palette.alwaysLight +import me.ash.reader.ui.theme.palette.onDark @OptIn(ExperimentalPagerApi::class) @Composable fun FilterBar( modifier: Modifier = Modifier, filter: Filter, + filterBarStyle: Int, + filterBarFilled: Boolean, + filterBarPadding: Dp, + filterBarTonalElevation: Dp, filterOnClick: (Filter) -> Unit = {}, ) { val view = LocalView.current - Box( - modifier = Modifier.height(60.dp) + NavigationBar( + tonalElevation = filterBarTonalElevation, ) { - 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() - ) - }, - selected = filter == item, - onClick = { -// view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP) - 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, + Spacer(modifier = Modifier.width(filterBarPadding)) + listOf( + Filter.Starred, + Filter.Unread, + Filter.All, + ).forEach { item -> + NavigationBarItem( +// modifier = Modifier.height(60.dp), + alwaysShowLabel = when (filterBarStyle) { + FlowFilterBarStylePreference.Icon.value -> false + FlowFilterBarStylePreference.IconLabel.value -> true + FlowFilterBarStylePreference.IconLabelOnlySelected.value -> false + else -> false + }, + icon = { + Icon( + imageVector = if (filter == item && filterBarFilled) { + item.iconFilled + } else { + item.iconOutline + }, + contentDescription = item.getName() ) + }, + label = if (filterBarStyle == FlowFilterBarStylePreference.Icon.value) { + null + } else { + { + Text( + text = item.getName(), + style = MaterialTheme.typography.labelLarge + ) + } + }, + selected = filter == item, + onClick = { +// view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP) + 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 onDark MaterialTheme.colorScheme.secondaryContainer, ) - } - Spacer(modifier = Modifier.width(60.dp)) + ) } + Spacer(modifier = Modifier.width(filterBarPadding)) } } \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/ui/page/home/HomeViewModel.kt b/app/src/main/java/me/ash/reader/ui/page/home/HomeViewModel.kt index e2ea70b..e787100 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/HomeViewModel.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/HomeViewModel.kt @@ -62,7 +62,7 @@ class HomeViewModel @Inject constructor( private fun fetchArticles() { _viewState.update { it.copy( - pagingData = Pager(PagingConfig(pageSize = 10)) { + pagingData = Pager(PagingConfig(pageSize = 15)) { if (_viewState.value.searchContent.isNotBlank()) { rssRepository.get().searchArticles( content = _viewState.value.searchContent.trim(), diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedItem.kt b/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedItem.kt index db5c130..87284a2 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedItem.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedItem.kt @@ -16,11 +16,13 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.LocalView import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import me.ash.reader.data.entity.Feed import me.ash.reader.ui.page.home.feeds.option.feed.FeedOptionViewAction import me.ash.reader.ui.page.home.feeds.option.feed.FeedOptionViewModel +import kotlin.math.ln @OptIn( androidx.compose.foundation.ExperimentalFoundationApi::class, @@ -31,6 +33,7 @@ fun FeedItem( modifier: Modifier = Modifier, feed: Feed, feedOptionViewModel: FeedOptionViewModel = hiltViewModel(), + tonalElevation: Dp, onClick: () -> Unit = {}, ) { val view = LocalView.current @@ -76,18 +79,18 @@ fun FeedItem( ) } if (feed.important ?: 0 != 0) { - Row() { - Badge( - containerColor = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.24f), - contentColor = MaterialTheme.colorScheme.outline, - content = { - Text( - text = feed.important.toString(), - style = MaterialTheme.typography.labelSmall - ) - }, - ) - } + Badge( + containerColor = MaterialTheme.colorScheme.surfaceTint.copy( + alpha = (ln(tonalElevation.value + 1.4f) + 2f) / 100f + ), + contentColor = MaterialTheme.colorScheme.outline, + content = { + Text( + text = feed.important.toString(), + style = MaterialTheme.typography.labelSmall + ) + }, + ) } } } 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..336e5a0 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 @@ -29,6 +29,7 @@ import androidx.navigation.NavHostController import kotlinx.coroutines.flow.map import me.ash.reader.R import me.ash.reader.data.entity.toVersion +import me.ash.reader.data.preference.* import me.ash.reader.data.repository.SyncWorker.Companion.getIsSyncing import me.ash.reader.ui.component.Banner import me.ash.reader.ui.component.DisplayText @@ -45,6 +46,7 @@ import me.ash.reader.ui.page.home.feeds.option.group.GroupOptionDrawer import me.ash.reader.ui.page.home.feeds.subscribe.SubscribeDialog import me.ash.reader.ui.page.home.feeds.subscribe.SubscribeViewAction import me.ash.reader.ui.page.home.feeds.subscribe.SubscribeViewModel +import me.ash.reader.ui.theme.palette.onDark @SuppressLint("FlowOperatorInvokedInComposition") @OptIn( @@ -59,6 +61,14 @@ fun FeedsPage( homeViewModel: HomeViewModel, ) { val context = LocalContext.current + val topBarTonalElevation = LocalFeedsTopBarTonalElevation.current + val groupListTonalElevation = LocalFeedsGroupListTonalElevation.current + val groupListExpand = LocalFeedsGroupListExpand.current + val filterBarStyle = LocalFeedsFilterBarStyle.current + val filterBarFilled = LocalFeedsFilterBarFilled.current + val filterBarPadding = LocalFeedsFilterBarPadding.current + val filterBarTonalElevation = LocalFeedsFilterBarTonalElevation.current + val feedsViewState = feedsViewModel.viewState.collectAsStateValue() val filterState = homeViewModel.filterState.collectAsStateValue() @@ -117,11 +127,19 @@ fun FeedsPage( Scaffold( modifier = Modifier - .background(MaterialTheme.colorScheme.surface) + .background(MaterialTheme.colorScheme.surfaceColorAtElevation(topBarTonalElevation.value.dp)) .statusBarsPadding() .navigationBarsPadding(), + containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation( + groupListTonalElevation.value.dp + ) onDark MaterialTheme.colorScheme.surface, topBar = { SmallTopAppBar( + colors = TopAppBarDefaults.smallTopAppBarColors( + containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation( + topBarTonalElevation.value.dp + ), + ), title = {}, navigationIcon = { FeedbackIconButton( @@ -174,7 +192,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, @@ -204,6 +222,8 @@ fun FeedsPage( // Crossfade(targetState = groupWithFeed) { groupWithFeed -> Column { GroupItem( + isExpanded = groupListExpand.value, + tonalElevation = groupListTonalElevation.value.dp, group = groupWithFeed.group, feeds = groupWithFeed.feeds, groupOnClick = { @@ -241,19 +261,19 @@ fun FeedsPage( }, bottomBar = { FilterBar( - modifier = Modifier - .height(60.dp) - .fillMaxWidth(), filter = filterState.filter, - filterOnClick = { - filterChange( - navController = navController, - homeViewModel = homeViewModel, - filterState = filterState.copy(filter = it), - isNavigate = false, - ) - }, - ) + filterBarStyle = filterBarStyle.value, + filterBarFilled = filterBarFilled.value, + filterBarPadding = filterBarPadding.dp, + filterBarTonalElevation = filterBarTonalElevation.value.dp, + ) { + filterChange( + navController = navController, + homeViewModel = homeViewModel, + filterState = filterState.copy(filter = it), + isNavigate = false, + ) + } } ) diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feeds/GroupItem.kt b/app/src/main/java/me/ash/reader/ui/page/home/feeds/GroupItem.kt index 3f32954..3563a00 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/feeds/GroupItem.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/feeds/GroupItem.kt @@ -22,11 +22,13 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import me.ash.reader.R import me.ash.reader.data.entity.Feed import me.ash.reader.data.entity.Group +import me.ash.reader.ui.ext.alphaLN import me.ash.reader.ui.page.home.feeds.option.group.GroupOptionViewAction import me.ash.reader.ui.page.home.feeds.option.group.GroupOptionViewModel @@ -34,6 +36,7 @@ import me.ash.reader.ui.page.home.feeds.option.group.GroupOptionViewModel @Composable fun GroupItem( modifier: Modifier = Modifier, + tonalElevation: Dp, group: Group, feeds: List, isExpanded: Boolean = true, @@ -50,7 +53,9 @@ fun GroupItem( .fillMaxWidth() .padding(horizontal = 16.dp) .clip(RoundedCornerShape(32.dp)) - .background(MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.14f)) + .background( + MaterialTheme.colorScheme.secondary.copy(alpha = tonalElevation.alphaLN(weight = 1.2f)) + ) .combinedClickable( onClick = { groupOnClick() @@ -82,7 +87,11 @@ fun GroupItem( .padding(end = 20.dp) .size(24.dp) .clip(CircleShape) - .background(MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.24f)) + .background( + MaterialTheme.colorScheme.surfaceTint.copy( + alpha = tonalElevation.alphaLN(weight = 1.4f) + ) + ) .clickable { expanded = !expanded }, @@ -107,6 +116,7 @@ fun GroupItem( FeedItem( modifier = Modifier.padding(horizontal = 20.dp), feed = feed, + tonalElevation = tonalElevation, ) { feedOnClick(feed) } 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..ed74bf9 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,7 @@ 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.ui.ext.formatAsString @Composable @@ -30,6 +31,11 @@ fun ArticleItem( onClick: (ArticleWithFeed) -> Unit = {}, ) { val context = LocalContext.current + val articleListFeedIcon = LocalFlowArticleListFeedIcon.current + val articleListFeedName = LocalFlowArticleListFeedName.current + val articleListImage = LocalFlowArticleListImage.current + val articleListDesc = LocalFlowArticleListDesc.current + val articleListDate = LocalFlowArticleListTime.current Column( modifier = Modifier @@ -44,67 +50,86 @@ 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( + verticalAlignment = Alignment.CenterVertically, + ) { + if (!articleListFeedName.value) { + Spacer(Modifier.width(if (articleListFeedIcon.value) 30.dp else 0.dp)) + } + // 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..f05ad97 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,9 @@ import me.ash.reader.data.entity.ArticleWithFeed @OptIn(ExperimentalFoundationApi::class) fun LazyListScope.ArticleList( pagingItems: LazyPagingItems, + articleListFeedIcon: Boolean, + articleListDateStickyHeader: Boolean, + articleListTonalElevation: Int, onClick: (ArticleWithFeed) -> Unit = {}, ) { for (index in 0 until pagingItems.itemCount) { @@ -29,8 +32,14 @@ fun LazyListScope.ArticleList( is FlowItemView.Date -> { val separator = pagingItems[index] as FlowItemView.Date if (separator.showSpacer) item { Spacer(modifier = Modifier.height(40.dp)) } - stickyHeader { - StickyHeader(separator.date) + if (articleListDateStickyHeader) { + stickyHeader(key = separator.date) { + StickyHeader(separator.date, articleListFeedIcon, articleListTonalElevation) + } + } else { + item(key = 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 c036717..63360ed 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 @@ -11,10 +11,7 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.ArrowBack import androidx.compose.material.icons.rounded.DoneAll import androidx.compose.material.icons.rounded.Search -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold -import androidx.compose.material3.SmallTopAppBar +import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester @@ -29,17 +26,20 @@ import androidx.paging.compose.LazyPagingItems import kotlinx.coroutines.delay import kotlinx.coroutines.launch import me.ash.reader.R +import me.ash.reader.data.preference.* import me.ash.reader.data.repository.SyncWorker.Companion.getIsSyncing import me.ash.reader.ui.component.DisplayText import me.ash.reader.ui.component.FeedbackIconButton import me.ash.reader.ui.component.SwipeRefresh import me.ash.reader.ui.ext.collectAsStateValue import me.ash.reader.ui.ext.getName +import me.ash.reader.ui.ext.surfaceColorAtElevation import me.ash.reader.ui.page.common.RouteName import me.ash.reader.ui.page.home.FilterBar import me.ash.reader.ui.page.home.FilterState import me.ash.reader.ui.page.home.HomeViewAction import me.ash.reader.ui.page.home.HomeViewModel +import me.ash.reader.ui.theme.palette.onDark @OptIn( ExperimentalMaterial3Api::class, @@ -55,6 +55,14 @@ fun FlowPage( pagingItems: LazyPagingItems, ) { val keyboardController = LocalSoftwareKeyboardController.current + val topBarTonalElevation = LocalFlowTopBarTonalElevation.current + val articleListTonalElevation = LocalFlowArticleListTonalElevation.current + val articleListFeedIcon = LocalFlowArticleListFeedIcon.current + val articleListDateStickyHeader = LocalFlowArticleListDateStickyHeader.current + val filterBarStyle = LocalFlowFilterBarStyle.current + val filterBarFilled = LocalFlowFilterBarFilled.current + val filterBarPadding = LocalFlowFilterBarPadding.current + val filterBarTonalElevation = LocalFlowFilterBarTonalElevation.current val scope = rememberCoroutineScope() val focusRequester = remember { FocusRequester() } @@ -100,12 +108,20 @@ fun FlowPage( Scaffold( modifier = Modifier - .background(MaterialTheme.colorScheme.surface) + .background(MaterialTheme.colorScheme.surfaceColorAtElevation(topBarTonalElevation.value.dp)) .statusBarsPadding() .navigationBarsPadding(), + containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation( + articleListTonalElevation.value.dp + ) onDark MaterialTheme.colorScheme.surface, topBar = { SmallTopAppBar( title = {}, + colors = TopAppBarDefaults.smallTopAppBarColors( + containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation( + topBarTonalElevation.value.dp + ), + ), navigationIcon = { FeedbackIconButton( imageVector = Icons.Rounded.ArrowBack, @@ -113,7 +129,7 @@ fun FlowPage( tint = MaterialTheme.colorScheme.onSurface ) { onSearch = false - if(navController.previousBackStackEntry == null) { + if (navController.previousBackStackEntry == null) { navController.navigate(RouteName.FEEDS) { launchSingleTop = true } @@ -182,7 +198,7 @@ fun FlowPage( state = listState, ) { item { - DisplayTextHeader(filterState, isSyncing) + DisplayTextHeader(filterState, isSyncing, articleListFeedIcon.value) AnimatedVisibility( visible = markAsRead, enter = fadeIn() + expandVertically(), @@ -244,6 +260,9 @@ fun FlowPage( } ArticleList( pagingItems = pagingItems, + articleListFeedIcon = articleListFeedIcon.value, + articleListDateStickyHeader = articleListDateStickyHeader.value, + articleListTonalElevation = articleListTonalElevation.value, ) { onSearch = false navController.navigate("${RouteName.READING}/${it.article.id}") { @@ -261,16 +280,16 @@ fun FlowPage( }, bottomBar = { FilterBar( - modifier = Modifier - .height(60.dp) - .fillMaxWidth(), filter = filterState.filter, - filterOnClick = { - flowViewModel.dispatch(FlowViewAction.ScrollToItem(0)) - homeViewModel.dispatch(HomeViewAction.ChangeFilter(filterState.copy(filter = it))) - homeViewModel.dispatch(HomeViewAction.FetchArticles) - }, - ) + filterBarStyle = filterBarStyle.value, + filterBarFilled = filterBarFilled.value, + filterBarPadding = filterBarPadding.dp, + filterBarTonalElevation = filterBarTonalElevation.value.dp, + ) { + flowViewModel.dispatch(FlowViewAction.ScrollToItem(0)) + homeViewModel.dispatch(HomeViewAction.ChangeFilter(filterState.copy(filter = it))) + homeViewModel.dispatch(HomeViewAction.FetchArticles) + } } ) } @@ -278,10 +297,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 0ef4020..dcda056 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 @@ -10,18 +10,29 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import me.ash.reader.ui.ext.surfaceColorAtElevation +import me.ash.reader.ui.theme.palette.onDark @Composable -fun StickyHeader(currentItemDay: String) { +fun StickyHeader( + currentItemDay: String, + articleListFeedIcon: Boolean, + articleListTonalElevation: Int, +) { Row( modifier = Modifier .fillMaxWidth() - .background(MaterialTheme.colorScheme.surface), + .background( + MaterialTheme.colorScheme.surfaceColorAtElevation(articleListTonalElevation.dp) + onDark MaterialTheme.colorScheme.surface + ), verticalAlignment = Alignment.CenterVertically ) { Text( - modifier = Modifier - .padding(start = if (true) 54.dp else 24.dp, bottom = 4.dp), + modifier = Modifier.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..9a675ca 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 @@ -1,6 +1,7 @@ package me.ash.reader.ui.page.settings.color import android.annotation.SuppressLint +import android.content.Context import android.os.Build import androidx.compose.animation.* import androidx.compose.foundation.background @@ -24,12 +25,13 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.navigation.NavHostController -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.launch import me.ash.reader.R +import me.ash.reader.data.preference.CustomPrimaryColorPreference +import me.ash.reader.data.preference.LocalCustomPrimaryColor +import me.ash.reader.data.preference.LocalThemeIndex +import me.ash.reader.data.preference.ThemeIndexPreference 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 @@ -46,8 +48,11 @@ fun ColorAndStyle( ) { val context = LocalContext.current val useDarkTheme = LocalUseDarkTheme.current + val themeIndex = LocalThemeIndex.current + val customPrimaryColor = LocalCustomPrimaryColor.current + val wallpaperTonalPalettes = extractTonalPalettesFromUserWallpaper() - var radioButtonSelected by remember { mutableStateOf(if (context.themeIndex > 4) 0 else 1) } + var radioButtonSelected by remember { mutableStateOf(if (themeIndex > 4) 0 else 1) } Scaffold( modifier = Modifier @@ -111,6 +116,7 @@ fun ColorAndStyle( onClick = {}, ) { Palettes( + context = context, palettes = wallpaperTonalPalettes.run { if (this.size > 5) { this.subList(5, wallpaperTonalPalettes.size) @@ -118,7 +124,9 @@ fun ColorAndStyle( emptyList() } }, + themeIndex = themeIndex, themeIndexPrefix = 5, + customPrimaryColor = customPrimaryColor, ) }, BlockRadioGroupButtonItem( @@ -126,7 +134,10 @@ fun ColorAndStyle( onClick = {}, ) { Palettes( - palettes = wallpaperTonalPalettes.subList(0, 5) + context = context, + themeIndex = themeIndex, + palettes = wallpaperTonalPalettes.subList(0, 5), + customPrimaryColor = customPrimaryColor, ) }, ), @@ -162,13 +173,19 @@ fun ColorAndStyle( ) SettingItem( title = stringResource(R.string.feeds_page), - enable = false, - onClick = {}, + onClick = { + navController.navigate(RouteName.FEEDS_PAGE_STYLE) { + launchSingleTop = true + } + }, ) {} 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), @@ -186,20 +203,16 @@ fun ColorAndStyle( @Composable fun Palettes( modifier: Modifier = Modifier, + context: Context, palettes: List, + themeIndex: Int = 0, themeIndexPrefix: Int = 0, + customPrimaryColor: String = "", ) { - val context = LocalContext.current val scope = rememberCoroutineScope() - val themeIndex = context.dataStore.data - .map { it[DataStoreKeys.ThemeIndex.key] ?: 5 } - .collectAsState(initial = 5).value - val customPrimaryColor = context.dataStore.data - .map { it[DataStoreKeys.CustomPrimaryColor.key] ?: "" } - .collectAsState(initial = "").value val tonalPalettes = customPrimaryColor.safeHexToColor().toTonalPalettes() var addDialogVisible by remember { mutableStateOf(false) } - var customColorValue by remember { mutableStateOf(context.customPrimaryColor) } + var customColorValue by remember { mutableStateOf(customPrimaryColor) } if (palettes.isEmpty()) { Row( @@ -239,14 +252,10 @@ fun Palettes( isCustom = isCustom, onClick = { if (isCustom) { + customColorValue = customPrimaryColor addDialogVisible = true } else { - scope.launch(Dispatchers.IO) { - context.dataStore.put( - DataStoreKeys.ThemeIndex, - themeIndexPrefix + index - ) - } + ThemeIndexPreference.put(context, scope, themeIndexPrefix + index) } }, palette = if (isCustom) tonalPalettes else palette @@ -266,16 +275,12 @@ fun Palettes( }, onDismissRequest = { addDialogVisible = false - customColorValue = context.customPrimaryColor }, onConfirm = { it.checkColorHex()?.let { - scope.launch(Dispatchers.IO) { - context.dataStore.put(DataStoreKeys.CustomPrimaryColor, it) - context.dataStore.put(DataStoreKeys.ThemeIndex, 4) - } + CustomPrimaryColorPreference.put(context, scope, it) + ThemeIndexPreference.put(context, scope, 4) addDialogVisible = false - customColorValue = it } } ) diff --git a/app/src/main/java/me/ash/reader/ui/page/settings/color/feeds/FeedsPageStyle.kt b/app/src/main/java/me/ash/reader/ui/page/settings/color/feeds/FeedsPageStyle.kt new file mode 100644 index 0000000..a7fce97 --- /dev/null +++ b/app/src/main/java/me/ash/reader/ui/page/settings/color/feeds/FeedsPageStyle.kt @@ -0,0 +1,367 @@ +package me.ash.reader.ui.page.settings.color.feeds + +import androidx.compose.animation.animateContentSize +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.Add +import androidx.compose.material.icons.rounded.ArrowBack +import androidx.compose.material.icons.rounded.Refresh +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.compose.ui.unit.dp +import androidx.navigation.NavHostController +import me.ash.reader.R +import me.ash.reader.data.entity.Feed +import me.ash.reader.data.entity.Filter +import me.ash.reader.data.entity.Group +import me.ash.reader.data.preference.* +import me.ash.reader.ui.component.* +import me.ash.reader.ui.ext.surfaceColorAtElevation +import me.ash.reader.ui.page.home.FilterBar +import me.ash.reader.ui.page.home.feeds.GroupItem +import me.ash.reader.ui.page.settings.SettingItem +import me.ash.reader.ui.theme.palette.onDark +import me.ash.reader.ui.theme.palette.onLight + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun FeedsPageStyle( + navController: NavHostController, +) { + val context = LocalContext.current + val filterBarStyle = LocalFeedsFilterBarStyle.current + val filterBarFilled = LocalFeedsFilterBarFilled.current + val filterBarPadding = LocalFeedsFilterBarPadding.current + val filterBarTonalElevation = LocalFeedsFilterBarTonalElevation.current + val topBarTonalElevation = LocalFeedsTopBarTonalElevation.current + val groupListExpand = LocalFeedsGroupListExpand.current + val groupListTonalElevation = LocalFeedsGroupListTonalElevation.current + + val scope = rememberCoroutineScope() + + var filterBarStyleDialogVisible by remember { mutableStateOf(false) } + var filterBarPaddingDialogVisible by remember { mutableStateOf(false) } + var filterBarTonalElevationDialogVisible by remember { mutableStateOf(false) } + var topBarTonalElevationDialogVisible by remember { mutableStateOf(false) } + var groupListTonalElevationDialogVisible 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.feeds_page), 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 + ) { + FeedsPagePreview( + topBarTonalElevation = topBarTonalElevation, + groupListExpand = groupListExpand, + groupListTonalElevation = groupListTonalElevation, + filterBarStyle = filterBarStyle.value, + filterBarFilled = filterBarFilled.value, + filterBarPadding = filterBarPadding.dp, + filterBarTonalElevation = filterBarTonalElevation.value.dp, + ) + } + Spacer(modifier = Modifier.height(24.dp)) + } + + // Top Bar + item { + Subtitle( + modifier = Modifier.padding(horizontal = 24.dp), + text = stringResource(R.string.top_bar) + ) + SettingItem( + title = stringResource(R.string.tonal_elevation), + desc = "${topBarTonalElevation.value}dp", + onClick = { + topBarTonalElevationDialogVisible = true + }, + ) {} +// Tips(text = stringResource(R.string.tips_top_bar_tonal_elevation)) + Spacer(modifier = Modifier.height(24.dp)) + } + + // Group List + item { + Subtitle( + modifier = Modifier.padding(horizontal = 24.dp), + text = stringResource(R.string.group_list) + ) + SettingItem( + title = stringResource(R.string.always_expand), + onClick = { + (!groupListExpand).put(context, scope) + }, + ) { + Switch(activated = groupListExpand.value) { + (!groupListExpand).put(context, scope) + } + } + SettingItem( + title = stringResource(R.string.tonal_elevation), + desc = "${groupListTonalElevation.value}dp", + onClick = { + groupListTonalElevationDialogVisible = true + }, + ) {} + Tips(text = stringResource(R.string.tips_group_list_tonal_elevation)) + Spacer(modifier = Modifier.height(24.dp)) + } + + // Filter Bar + item { + Subtitle( + modifier = Modifier.padding(horizontal = 24.dp), + text = stringResource(R.string.filter_bar), + ) + SettingItem( + title = stringResource(R.string.style), + desc = filterBarStyle.getDesc(context), + onClick = { + filterBarStyleDialogVisible = true + }, + ) {} + SettingItem( + title = stringResource(R.string.fill_selected_icon), + onClick = { + (!filterBarFilled).put(context, scope) + }, + ) { + Switch(activated = filterBarFilled.value) { + (!filterBarFilled).put(context, scope) + } + } + SettingItem( + title = stringResource(R.string.padding_on_both_ends), + desc = "${filterBarPadding}dp", + onClick = { + filterBarPaddingValue = filterBarPadding + filterBarPaddingDialogVisible = true + }, + ) {} + SettingItem( + title = stringResource(R.string.tonal_elevation), + desc = "${filterBarTonalElevation.value}dp", + onClick = { + filterBarTonalElevationDialogVisible = true + }, + ) {} + Spacer(modifier = Modifier.height(24.dp)) + } + } + } + ) + + RadioDialog( + visible = filterBarStyleDialogVisible, + title = stringResource(R.string.style), + options = FeedsFilterBarStylePreference.values.map { + RadioDialogOption( + text = it.getDesc(context), + selected = filterBarStyle == it, + ) { + it.put(context, scope) + } + } + ) { + filterBarStyleDialogVisible = false + } + + TextFieldDialog( + visible = filterBarPaddingDialogVisible, + title = stringResource(R.string.padding_on_both_ends), + value = (filterBarPaddingValue ?: "").toString(), + placeholder = stringResource(R.string.value), + onValueChange = { + filterBarPaddingValue = it.filter { it.isDigit() }.toIntOrNull() + }, + onDismissRequest = { + filterBarPaddingDialogVisible = false + }, + onConfirm = { + FeedsFilterBarPaddingPreference.put(context, scope, filterBarPaddingValue ?: 0) + filterBarPaddingDialogVisible = false + } + ) + + RadioDialog( + visible = filterBarTonalElevationDialogVisible, + title = stringResource(R.string.tonal_elevation), + options = FeedsFilterBarTonalElevationPreference.values.map { + RadioDialogOption( + text = it.getDesc(context), + selected = it == filterBarTonalElevation, + ) { + it.put(context, scope) + } + } + ) { + filterBarTonalElevationDialogVisible = false + } + + RadioDialog( + visible = topBarTonalElevationDialogVisible, + title = stringResource(R.string.tonal_elevation), + options = FeedsTopBarTonalElevationPreference.values.map { + RadioDialogOption( + text = it.getDesc(context), + selected = it == topBarTonalElevation, + ) { + it.put(context, scope) + } + } + ) { + topBarTonalElevationDialogVisible = false + } + + RadioDialog( + visible = groupListTonalElevationDialogVisible, + title = stringResource(R.string.tonal_elevation), + options = FeedsGroupListTonalElevationPreference.values.map { + RadioDialogOption( + text = it.getDesc(context), + selected = it == groupListTonalElevation, + ) { + it.put(context, scope) + } + } + ) { + groupListTonalElevationDialogVisible = false + } +} + +@Composable +fun FeedsPagePreview( + topBarTonalElevation: FeedsTopBarTonalElevationPreference, + groupListExpand: FeedsGroupListExpandPreference, + groupListTonalElevation: FeedsGroupListTonalElevationPreference, + filterBarStyle: Int, + filterBarFilled: Boolean, + filterBarPadding: Dp, + filterBarTonalElevation: Dp, +) { + var filter by remember { mutableStateOf(Filter.Unread) } + + Column( + modifier = Modifier + .animateContentSize() + .background( + color = MaterialTheme.colorScheme.surfaceColorAtElevation( + groupListTonalElevation.value.dp + ) onDark MaterialTheme.colorScheme.surface, + shape = RoundedCornerShape(24.dp) + ) + ) { + SmallTopAppBar( + title = {}, + colors = TopAppBarDefaults.smallTopAppBarColors( + containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation( + topBarTonalElevation.value.dp + ), + ), + navigationIcon = { + FeedbackIconButton( + imageVector = Icons.Rounded.ArrowBack, + contentDescription = stringResource(R.string.back), + tint = MaterialTheme.colorScheme.onSurface + ) {} + }, + actions = { + FeedbackIconButton( + imageVector = Icons.Rounded.Refresh, + contentDescription = stringResource(R.string.refresh), + tint = MaterialTheme.colorScheme.onSurface, + ) {} + FeedbackIconButton( + imageVector = Icons.Rounded.Add, + contentDescription = stringResource(R.string.subscribe), + tint = MaterialTheme.colorScheme.onSurface, + ) {} + } + ) + Spacer(modifier = Modifier.height(12.dp)) + GroupItem( + isExpanded = groupListExpand.value, + tonalElevation = groupListTonalElevation.value.dp, + group = Group( + id = "", + name = stringResource(R.string.defaults), + accountId = 0, + ), + feeds = listOf( + Feed( + id = "", + name = stringResource(R.string.preview_feed_name), + icon = "", + accountId = 0, + groupId = "", + url = "", + ).apply { + important = 100 + } + ), + ) + Spacer(modifier = Modifier.height(12.dp)) + FilterBar( + modifier = Modifier.padding(horizontal = 12.dp), + filter = filter, + filterBarStyle = filterBarStyle, + filterBarFilled = filterBarFilled, + filterBarPadding = filterBarPadding, + filterBarTonalElevation = filterBarTonalElevation, + ) { + filter = it + } + } +} \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/ui/page/settings/color/flow/FlowPageStyle.kt b/app/src/main/java/me/ash/reader/ui/page/settings/color/flow/FlowPageStyle.kt new file mode 100644 index 0000000..991762a --- /dev/null +++ b/app/src/main/java/me/ash/reader/ui/page/settings/color/flow/FlowPageStyle.kt @@ -0,0 +1,429 @@ +package me.ash.reader.ui.page.settings.color.flow + +import android.annotation.SuppressLint +import androidx.compose.animation.animateContentSize +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.material.icons.rounded.DoneAll +import androidx.compose.material.icons.rounded.Search +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.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.ui.component.* +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.onDark +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 filterBarStyle = LocalFlowFilterBarStyle.current + val filterBarFilled = LocalFlowFilterBarFilled.current + val filterBarPadding = LocalFlowFilterBarPadding.current + val filterBarTonalElevation = LocalFlowFilterBarTonalElevation.current + val topBarTonalElevation = LocalFlowTopBarTonalElevation.current + val articleListFeedIcon = LocalFlowArticleListFeedIcon.current + val articleListFeedName = LocalFlowArticleListFeedName.current + val articleListImage = LocalFlowArticleListImage.current + val articleListDesc = LocalFlowArticleListDesc.current + val articleListTime = LocalFlowArticleListTime.current + val articleListStickyDate = LocalFlowArticleListDateStickyHeader.current + val articleListTonalElevation = LocalFlowArticleListTonalElevation.current + + val scope = rememberCoroutineScope() + + var filterBarStyleDialogVisible by remember { mutableStateOf(false) } + var filterBarPaddingDialogVisible by remember { mutableStateOf(false) } + var filterBarTonalElevationDialogVisible by remember { mutableStateOf(false) } + var topBarTonalElevationDialogVisible 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 = "") + } + + // 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 + ) { + FlowPagePreview( + topBarTonalElevation = topBarTonalElevation, + articleListTonalElevation = articleListTonalElevation, + filterBarStyle = filterBarStyle.value, + filterBarFilled = filterBarFilled.value, + filterBarPadding = filterBarPadding.dp, + filterBarTonalElevation = filterBarTonalElevation.value.dp, + ) + } + Spacer(modifier = Modifier.height(24.dp)) + } + + // Top Bar + item { + Subtitle( + modifier = Modifier.padding(horizontal = 24.dp), + text = stringResource(R.string.top_bar) + ) + SettingItem( + title = stringResource(R.string.mark_as_read_button_position), + desc = stringResource(R.string.top), + enable = false, + onClick = {}, + ) {} + SettingItem( + title = stringResource(R.string.tonal_elevation), + desc = "${topBarTonalElevation.value}dp", + onClick = { + topBarTonalElevationDialogVisible = true + }, + ) {} +// Tips(text = stringResource(R.string.tips_top_bar_tonal_elevation)) + Spacer(modifier = Modifier.height(24.dp)) + } + + // Article List + item { + Subtitle( + modifier = Modifier.padding(horizontal = 24.dp), + text = stringResource(R.string.article_list) + ) + SettingItem( + title = stringResource(R.string.display_feed_favicon), + onClick = { + (!articleListFeedIcon).put(context, scope) + }, + ) { + Switch(activated = articleListFeedIcon.value) { + (!articleListFeedIcon).put(context, scope) + } + } + SettingItem( + title = stringResource(R.string.display_feed_name), + onClick = { + (!articleListFeedName).put(context, scope) + }, + ) { + Switch(activated = articleListFeedName.value) { + (!articleListFeedName).put(context, scope) + } + } + SettingItem( + title = stringResource(R.string.display_article_image), + enable = false, + onClick = {}, + ) { + Switch(activated = false, enable = false) + } + SettingItem( + title = stringResource(R.string.display_article_desc), + onClick = { + (!articleListDesc).put(context, scope) + }, + ) { + Switch(activated = articleListDesc.value) { + (!articleListDesc).put(context, scope) + } + } + SettingItem( + title = stringResource(R.string.display_article_date), + onClick = { + (!articleListTime).put(context, scope) + }, + ) { + Switch(activated = articleListTime.value) { + (!articleListTime).put(context, scope) + } + } + SettingItem( + title = stringResource(R.string.article_date_sticky_header), + onClick = { + (!articleListStickyDate).put(context, scope) + }, + ) { + Switch(activated = articleListStickyDate.value) { + (!articleListStickyDate).put(context, scope) + } + } + SettingItem( + title = stringResource(R.string.tonal_elevation), + desc = "${articleListTonalElevation.value}dp", + onClick = { + articleListTonalElevationDialogVisible = true + }, + ) {} + Tips(text = stringResource(R.string.tips_article_list_tonal_elevation)) + Spacer(modifier = Modifier.height(24.dp)) + } + + // Filter Bar + item { + Subtitle( + modifier = Modifier.padding(horizontal = 24.dp), + text = stringResource(R.string.filter_bar), + ) + SettingItem( + title = stringResource(R.string.style), + desc = filterBarStyle.getDesc(context), + onClick = { + filterBarStyleDialogVisible = true + }, + ) {} + SettingItem( + title = stringResource(R.string.fill_selected_icon), + onClick = { + (!filterBarFilled).put(context, scope) + }, + ) { + Switch(activated = filterBarFilled.value) { + (!filterBarFilled).put(context, scope) + } + } + SettingItem( + title = stringResource(R.string.padding_on_both_ends), + desc = "${filterBarPadding}dp", + onClick = { + filterBarPaddingValue = filterBarPadding + filterBarPaddingDialogVisible = true + }, + ) {} + SettingItem( + title = stringResource(R.string.tonal_elevation), + desc = "${filterBarTonalElevation.value}dp", + onClick = { + filterBarTonalElevationDialogVisible = true + }, + ) {} + Spacer(modifier = Modifier.height(24.dp)) + } + } + } + ) + + RadioDialog( + visible = filterBarStyleDialogVisible, + title = stringResource(R.string.style), + options = FlowFilterBarStylePreference.values.map { + RadioDialogOption( + text = it.getDesc(context), + selected = it == filterBarStyle, + ) { + it.put(context, scope) + } + } + ) { + filterBarStyleDialogVisible = false + } + + TextFieldDialog( + visible = filterBarPaddingDialogVisible, + title = stringResource(R.string.padding_on_both_ends), + value = (filterBarPaddingValue ?: "").toString(), + placeholder = stringResource(R.string.value), + onValueChange = { + filterBarPaddingValue = it.filter { it.isDigit() }.toIntOrNull() + }, + onDismissRequest = { + filterBarPaddingDialogVisible = false + }, + onConfirm = { + FlowFilterBarPaddingPreference.put(context, scope, filterBarPaddingValue ?: 0) + filterBarPaddingDialogVisible = false + } + ) + + RadioDialog( + visible = filterBarTonalElevationDialogVisible, + title = stringResource(R.string.tonal_elevation), + options = FlowFilterBarTonalElevationPreference.values.map { + RadioDialogOption( + text = it.getDesc(context), + selected = it == filterBarTonalElevation, + ) { + it.put(context, scope) + } + } + ) { + filterBarTonalElevationDialogVisible = false + } + + RadioDialog( + visible = topBarTonalElevationDialogVisible, + title = stringResource(R.string.tonal_elevation), + options = FlowTopBarTonalElevationPreference.values.map { + RadioDialogOption( + text = it.getDesc(context), + selected = it == topBarTonalElevation, + ) { + it.put(context, scope) + } + } + ) { + topBarTonalElevationDialogVisible = false + } + + RadioDialog( + visible = articleListTonalElevationDialogVisible, + title = stringResource(R.string.tonal_elevation), + options = FlowArticleListTonalElevationPreference.values.map { + RadioDialogOption( + text = it.getDesc(context), + selected = it == articleListTonalElevation, + ) { + it.put(context, scope) + } + } + ) { + articleListTonalElevationDialogVisible = false + } +} + +@Composable +fun FlowPagePreview( + topBarTonalElevation: FlowTopBarTonalElevationPreference, + articleListTonalElevation: FlowArticleListTonalElevationPreference, + filterBarStyle: Int, + filterBarFilled: Boolean, + filterBarPadding: Dp, + filterBarTonalElevation: Dp, +) { + var filter by remember { mutableStateOf(Filter.Unread) } + + Column( + modifier = Modifier + .animateContentSize() + .background( + color = MaterialTheme.colorScheme.surfaceColorAtElevation( + articleListTonalElevation.value.dp + ) onDark MaterialTheme.colorScheme.surface, + shape = RoundedCornerShape(24.dp) + ) + ) { + SmallTopAppBar( + title = {}, + colors = TopAppBarDefaults.smallTopAppBarColors( + containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation( + topBarTonalElevation.value.dp + ), + ), + navigationIcon = { + FeedbackIconButton( + imageVector = Icons.Rounded.ArrowBack, + contentDescription = stringResource(R.string.back), + tint = MaterialTheme.colorScheme.onSurface + ) {} + }, + actions = { + FeedbackIconButton( + imageVector = Icons.Rounded.DoneAll, + contentDescription = stringResource(R.string.mark_all_as_read), + tint = MaterialTheme.colorScheme.onSurface, + ) {} + FeedbackIconButton( + imageVector = Icons.Rounded.Search, + contentDescription = stringResource(R.string.search), + tint = MaterialTheme.colorScheme.onSurface, + ) {} + } + ) + Spacer(modifier = Modifier.height(12.dp)) + ArticleItem( + articleWithFeed = ArticleWithFeed( + Article( + id = "", + title = stringResource(R.string.preview_article_title), + shortDescription = stringResource(R.string.preview_article_desc), + rawDescription = stringResource(R.string.preview_article_desc), + link = "", + feedId = "", + accountId = 0, + date = Date(), + isStarred = true, + ), + feed = Feed( + id = "", + name = stringResource(R.string.preview_feed_name), + icon = "", + accountId = 0, + groupId = "", + url = "", + ), + ) + ) + Spacer(modifier = Modifier.height(12.dp)) + FilterBar( + modifier = Modifier.padding(horizontal = 12.dp), + filter = filter, + filterBarStyle = filterBarStyle, + filterBarFilled = filterBarFilled, + filterBarPadding = filterBarPadding, + filterBarTonalElevation = filterBarTonalElevation, + ) { + filter = it + } + } +} \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/ui/page/settings/interaction/Interaction.kt b/app/src/main/java/me/ash/reader/ui/page/settings/interaction/Interaction.kt index 40ce721..ca362b6 100644 --- a/app/src/main/java/me/ash/reader/ui/page/settings/interaction/Interaction.kt +++ b/app/src/main/java/me/ash/reader/ui/page/settings/interaction/Interaction.kt @@ -10,11 +10,9 @@ import androidx.compose.material3.* import androidx.compose.runtime.* 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.unit.dp import androidx.navigation.NavHostController -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import me.ash.reader.R @@ -32,7 +30,6 @@ fun Interaction( navController: NavHostController, ) { val context = LocalContext.current - val view = LocalView.current val scope = rememberCoroutineScope() var initialPageDialogVisible by remember { mutableStateOf(false) } var initialFilterDialogVisible by remember { mutableStateOf(false) } @@ -116,7 +113,7 @@ fun Interaction( text = stringResource(R.string.feeds_page), selected = initialPage == 0, ) { - scope.launch(Dispatchers.IO) { + scope.launch { context.dataStore.put(DataStoreKeys.InitialPage, 0) } }, @@ -124,7 +121,7 @@ fun Interaction( text = stringResource(R.string.flow_page), selected = initialPage == 1, ) { - scope.launch(Dispatchers.IO) { + scope.launch { context.dataStore.put(DataStoreKeys.InitialPage, 1) } }, @@ -141,7 +138,7 @@ fun Interaction( text = stringResource(R.string.starred), selected = initialFilter == 0, ) { - scope.launch(Dispatchers.IO) { + scope.launch { context.dataStore.put(DataStoreKeys.InitialFilter, 0) } }, @@ -149,7 +146,7 @@ fun Interaction( text = stringResource(R.string.unread), selected = initialFilter == 1, ) { - scope.launch(Dispatchers.IO) { + scope.launch { context.dataStore.put(DataStoreKeys.InitialFilter, 1) } }, @@ -157,7 +154,7 @@ fun Interaction( text = stringResource(R.string.all), selected = initialFilter == 2, ) { - scope.launch(Dispatchers.IO) { + scope.launch { context.dataStore.put(DataStoreKeys.InitialFilter, 2) } }, diff --git a/app/src/main/java/me/ash/reader/ui/page/startup/StartupPage.kt b/app/src/main/java/me/ash/reader/ui/page/startup/StartupPage.kt index c302a91..cc30fbd 100644 --- a/app/src/main/java/me/ash/reader/ui/page/startup/StartupPage.kt +++ b/app/src/main/java/me/ash/reader/ui/page/startup/StartupPage.kt @@ -6,7 +6,6 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Info import androidx.compose.material.icons.rounded.CheckCircleOutline import androidx.compose.material3.* import androidx.compose.runtime.Composable @@ -21,6 +20,7 @@ import kotlinx.coroutines.launch import me.ash.reader.R import me.ash.reader.ui.component.DisplayText import me.ash.reader.ui.component.DynamicSVGImage +import me.ash.reader.ui.component.Tips import me.ash.reader.ui.ext.DataStoreKeys import me.ash.reader.ui.ext.dataStore import me.ash.reader.ui.ext.put @@ -57,14 +57,12 @@ fun StartupPage( ) } item { - TipsItem( - modifier = Modifier - .padding(horizontal = 24.dp) - .padding(top = 40.dp) + Tips( + modifier = Modifier.padding(top = 40.dp), + text = stringResource(R.string.agree_terms), ) } item { - Spacer(modifier = Modifier.height(10.dp)) TextButton( modifier = Modifier.padding(horizontal = 12.dp), onClick = { @@ -119,26 +117,4 @@ fun StartupPage( ) } ) -} - -@Composable -private fun TipsItem( - modifier: Modifier = Modifier, -) { - Column( - modifier = modifier - .fillMaxWidth() - ) { - Icon( - imageVector = Icons.Outlined.Info, - contentDescription = stringResource(R.string.tips_and_support), - tint = MaterialTheme.colorScheme.onSurfaceVariant, - ) - Spacer(modifier = Modifier.height(22.dp)) - Text( - text = stringResource(R.string.agree_terms), - style = MaterialTheme.typography.titleSmall, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) - } } \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/ui/svg/SVGString.kt b/app/src/main/java/me/ash/reader/ui/svg/SVGString.kt index bbadd60..5bf1e59 100644 --- a/app/src/main/java/me/ash/reader/ui/svg/SVGString.kt +++ b/app/src/main/java/me/ash/reader/ui/svg/SVGString.kt @@ -10,6 +10,7 @@ object SVGString fun String.parseDynamicColor(tonalPalettes: TonalPalettes, isDarkTheme: Boolean): String = replace("fill=\"(.+?)\"".toRegex()) { val value = it.groupValues[1] + Log.i("RLog", "parseDynamicColor: $value") if (value.startsWith("#")) return@replace it.value try { val (scheme, tone) = value.split("(?<=\\d)(?=\\D)|(?=\\d)(?<=\\D)".toRegex()) @@ -24,6 +25,7 @@ fun String.parseDynamicColor(tonalPalettes: TonalPalettes, isDarkTheme: Boolean) }?.toArgb() ?: 0xFFFFFF "fill=\"${String.format("#%06X", 0xFFFFFF and argb)}\"" } catch (e: Exception) { + e.printStackTrace() Log.e("RLog", "parseDynamicColor: ${e.message}") it.value } diff --git a/app/src/main/java/me/ash/reader/ui/theme/Theme.kt b/app/src/main/java/me/ash/reader/ui/theme/Theme.kt index da76775..694093b 100644 --- a/app/src/main/java/me/ash/reader/ui/theme/Theme.kt +++ b/app/src/main/java/me/ash/reader/ui/theme/Theme.kt @@ -1,16 +1,11 @@ package me.ash.reader.ui.theme -import android.annotation.SuppressLint import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.compositionLocalOf -import androidx.compose.ui.platform.LocalContext -import kotlinx.coroutines.flow.map -import me.ash.reader.ui.ext.DataStoreKeys -import me.ash.reader.ui.ext.dataStore +import me.ash.reader.data.preference.LocalThemeIndex import me.ash.reader.ui.theme.palette.LocalTonalPalettes import me.ash.reader.ui.theme.palette.TonalPalettes import me.ash.reader.ui.theme.palette.core.ProvideZcamViewingConditions @@ -20,17 +15,13 @@ import me.ash.reader.ui.theme.palette.dynamicLightColorScheme val LocalUseDarkTheme = compositionLocalOf { false } -@SuppressLint("FlowOperatorInvokedInComposition") @Composable fun AppTheme( useDarkTheme: Boolean = isSystemInDarkTheme(), wallpaperPalettes: List = extractTonalPalettesFromUserWallpaper(), content: @Composable () -> Unit ) { - val context = LocalContext.current - val themeIndex = context.dataStore.data - .map { it[DataStoreKeys.ThemeIndex.key] ?: 5 } - .collectAsState(initial = 5).value + val themeIndex = LocalThemeIndex.current val tonalPalettes = wallpaperPalettes[ if (themeIndex >= wallpaperPalettes.size) { @@ -47,7 +38,7 @@ fun AppTheme( ProvideZcamViewingConditions { CompositionLocalProvider( LocalTonalPalettes provides tonalPalettes.also { it.Preheating() }, - LocalUseDarkTheme provides useDarkTheme + LocalUseDarkTheme provides useDarkTheme, ) { MaterialTheme( colorScheme = 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 818d16c..3df4107 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 @@ -31,6 +31,7 @@ fun dynamicLightColorScheme(): ColorScheme { onSurface = palettes neutral 10, surfaceVariant = palettes neutralVariant 90, onSurfaceVariant = palettes neutralVariant 30, + surfaceTint = palettes primary 40, inverseSurface = palettes neutral 20, inverseOnSurface = palettes neutral 95, outline = palettes neutralVariant 50, @@ -60,6 +61,7 @@ fun dynamicDarkColorScheme(): ColorScheme { onSurface = palettes neutral 90, surfaceVariant = palettes neutralVariant 30, onSurfaceVariant = palettes neutralVariant 80, + surfaceTint = palettes primary 80, inverseSurface = palettes neutral 90, inverseOnSurface = palettes neutral 20, outline = palettes neutralVariant 60, diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 84ffc0d..bea1e53 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 颜色和样式 - 主题、色彩系统、字体大小 + 主题、色调样式、字体大小 交互 启动时、触感反馈 语言 @@ -99,7 +99,7 @@ 例如 #666666 或 666666 外观 样式 - 深色模式 + 深色主题 跟随系统设置 色调海拔 字体 @@ -124,4 +124,29 @@ 启动时 起始页面 起始过滤条件 + 呜呜呜,黎明之剑完结了 + 是宴席,就有结束的时候,但这本书真的陪了我好久啊,好舍不得,而且主线结束了坑却没填完,不知道啥时候才有番外啊 + 漩涡书院 + + 两端边距 + 显示文章发布时间 + 显示文章描述 + 显示文章插图 + 显示订阅源名称 + 显示订阅源图标 + 文章发布日期粘性标签(实验性) + 文章列表 + 分组列表 + 始终展开 + 顶部 + “标记为已读”按钮的位置 + 标题栏 + 填充已选中的图标 + 过滤栏 + 图标 + 图标和文字 + 图标和文字(仅选中时) + 标题栏的色调海拔仅在滚动时可用。 + 文章列表的色调海拔仅在亮色主题时可用。 + 分组列表的色调海拔仅在亮色主题时可用。 \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4aa92b7..6e0a66d 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 @@ -88,9 +88,9 @@ About, open source Welcome Before you can continue, you need to agree to Read You\'s Terms of Service and Privacy Policy. - View the <i><u>Terms of Service and Privacy Policy</u></i> + View the <i><u>Terms of Service & Privacy Policy</u></i> https://github.com/Ashinch/ReadYou/blob/main/TERMS_OF_SERVICE_AND_PRIVACY_POLICY.md - Agree and Continue + Agree Wallpaper Colors No Palettes Only Android 8.1+ @@ -116,7 +116,7 @@ Update Skip This Version Checking for updates… - This is the latest version + This is latest version Check failure Download failure The request rate is limited @@ -124,4 +124,29 @@ On Start Initial Page Initial Filter + The novel "Lord of the Mysteries" has finally come to an end + The Fool is the eighth and final volume of the Lord of the Mysteries series written by Cuttlefish That Loves Diving. + Reddit + value + Padding on Both Ends + Display Article Publish Time + Display Article Descriptions + Display Article Images + Display Feed Names + Display Feed Favicons + Article Publish Date Sticky Header (Experimental) + Article List + Group List + Always Expand + Top + \"Mark as Read\" Button Position + Top Bar + Fill The Selected Icon + Filter Bar + Icons + Icons & Labels + Icons & Label (Only Selected) + Tone elevation of the top bar is only available when scrolling. + Tone elevation of the article list is only available for light theme. + Tone elevation of the group list is only available for light theme. \ No newline at end of file