commit
c1a4c8ec5b
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
152
app/src/main/java/me/ash/reader/data/preference/Settings.kt
Normal file
152
app/src/main/java/me/ash/reader/data/preference/Settings.kt
Normal file
|
@ -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> { FeedsFilterBarStylePreference.default }
|
||||
val LocalFeedsFilterBarFilled =
|
||||
compositionLocalOf<FeedsFilterBarFilledPreference> { FeedsFilterBarFilledPreference.default }
|
||||
val LocalFeedsFilterBarPadding =
|
||||
compositionLocalOf { FeedsFilterBarPaddingPreference.default }
|
||||
val LocalFeedsFilterBarTonalElevation =
|
||||
compositionLocalOf<FeedsFilterBarTonalElevationPreference> { FeedsFilterBarTonalElevationPreference.default }
|
||||
val LocalFeedsTopBarTonalElevation =
|
||||
compositionLocalOf<FeedsTopBarTonalElevationPreference> { FeedsTopBarTonalElevationPreference.default }
|
||||
val LocalFeedsGroupListExpand =
|
||||
compositionLocalOf<FeedsGroupListExpandPreference> { FeedsGroupListExpandPreference.default }
|
||||
val LocalFeedsGroupListTonalElevation =
|
||||
compositionLocalOf<FeedsGroupListTonalElevationPreference> { FeedsGroupListTonalElevationPreference.default }
|
||||
|
||||
val LocalFlowFilterBarStyle =
|
||||
compositionLocalOf<FlowFilterBarStylePreference> { FlowFilterBarStylePreference.default }
|
||||
val LocalFlowFilterBarFilled =
|
||||
compositionLocalOf<FlowFilterBarFilledPreference> { FlowFilterBarFilledPreference.default }
|
||||
val LocalFlowFilterBarPadding =
|
||||
compositionLocalOf { FlowFilterBarPaddingPreference.default }
|
||||
val LocalFlowFilterBarTonalElevation =
|
||||
compositionLocalOf<FlowFilterBarTonalElevationPreference> { FlowFilterBarTonalElevationPreference.default }
|
||||
val LocalFlowTopBarTonalElevation =
|
||||
compositionLocalOf<FlowTopBarTonalElevationPreference> { FlowTopBarTonalElevationPreference.default }
|
||||
val LocalFlowArticleListFeedIcon =
|
||||
compositionLocalOf<FlowArticleListFeedIconPreference> { FlowArticleListFeedIconPreference.default }
|
||||
val LocalFlowArticleListFeedName =
|
||||
compositionLocalOf<FlowArticleListFeedNamePreference> { FlowArticleListFeedNamePreference.default }
|
||||
val LocalFlowArticleListImage =
|
||||
compositionLocalOf<FlowArticleListImagePreference> { FlowArticleListImagePreference.default }
|
||||
val LocalFlowArticleListDesc =
|
||||
compositionLocalOf<FlowArticleListDescPreference> { FlowArticleListDescPreference.default }
|
||||
val LocalFlowArticleListTime =
|
||||
compositionLocalOf<FlowArticleListTimePreference> { FlowArticleListTimePreference.default }
|
||||
val LocalFlowArticleListDateStickyHeader =
|
||||
compositionLocalOf<FlowArticleListDateStickyHeaderPreference> { FlowArticleListDateStickyHeaderPreference.default }
|
||||
val LocalFlowArticleListTonalElevation =
|
||||
compositionLocalOf<FlowArticleListTonalElevationPreference> { FlowArticleListTonalElevationPreference.default }
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
) {}
|
||||
}
|
||||
|
|
38
app/src/main/java/me/ash/reader/ui/component/Tips.kt
Normal file
38
app/src/main/java/me/ash/reader/ui/component/Tips.kt
Normal file
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
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
|
||||
|
|
|
@ -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<Preferences> 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 <T> DataStore<Preferences>.put(dataStoreKeys: DataStoreKeys<T>, value: T) {
|
||||
this.edit {
|
||||
it[dataStoreKeys.key] = value
|
||||
withContext(Dispatchers.IO) {
|
||||
it[dataStoreKeys.key] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -129,6 +130,101 @@ sealed class DataStoreKeys<T> {
|
|||
get() = stringPreferencesKey("customPrimaryColor")
|
||||
}
|
||||
|
||||
object FeedsFilterBarStyle : DataStoreKeys<Int>() {
|
||||
override val key: Preferences.Key<Int>
|
||||
get() = intPreferencesKey("feedsFilterBarStyle")
|
||||
}
|
||||
|
||||
object FeedsFilterBarFilled : DataStoreKeys<Boolean>() {
|
||||
override val key: Preferences.Key<Boolean>
|
||||
get() = booleanPreferencesKey("feedsFilterBarFilled")
|
||||
}
|
||||
|
||||
object FeedsFilterBarPadding : DataStoreKeys<Int>() {
|
||||
override val key: Preferences.Key<Int>
|
||||
get() = intPreferencesKey("feedsFilterBarPadding")
|
||||
}
|
||||
|
||||
object FeedsFilterBarTonalElevation : DataStoreKeys<Int>() {
|
||||
override val key: Preferences.Key<Int>
|
||||
get() = intPreferencesKey("feedsFilterBarTonalElevation")
|
||||
}
|
||||
|
||||
object FeedsTopBarTonalElevation : DataStoreKeys<Int>() {
|
||||
override val key: Preferences.Key<Int>
|
||||
get() = intPreferencesKey("feedsTopBarTonalElevation")
|
||||
}
|
||||
|
||||
object FeedsGroupListExpand : DataStoreKeys<Boolean>() {
|
||||
override val key: Preferences.Key<Boolean>
|
||||
get() = booleanPreferencesKey("feedsGroupListExpand")
|
||||
}
|
||||
|
||||
object FeedsGroupListTonalElevation : DataStoreKeys<Int>() {
|
||||
override val key: Preferences.Key<Int>
|
||||
get() = intPreferencesKey("feedsGroupListTonalElevation")
|
||||
}
|
||||
|
||||
object FlowFilterBarStyle : DataStoreKeys<Int>() {
|
||||
override val key: Preferences.Key<Int>
|
||||
get() = intPreferencesKey("flowFilterBarStyle")
|
||||
}
|
||||
|
||||
object FlowFilterBarFilled : DataStoreKeys<Boolean>() {
|
||||
override val key: Preferences.Key<Boolean>
|
||||
get() = booleanPreferencesKey("flowFilterBarFilled")
|
||||
}
|
||||
|
||||
object FlowFilterBarPadding : DataStoreKeys<Int>() {
|
||||
override val key: Preferences.Key<Int>
|
||||
get() = intPreferencesKey("flowFilterBarPadding")
|
||||
}
|
||||
|
||||
object FlowFilterBarTonalElevation : DataStoreKeys<Int>() {
|
||||
override val key: Preferences.Key<Int>
|
||||
get() = intPreferencesKey("flowFilterBarTonalElevation")
|
||||
}
|
||||
|
||||
object FlowTopBarTonalElevation : DataStoreKeys<Int>() {
|
||||
override val key: Preferences.Key<Int>
|
||||
get() = intPreferencesKey("flowTopBarTonalElevation")
|
||||
}
|
||||
|
||||
object FlowArticleListFeedIcon : DataStoreKeys<Boolean>() {
|
||||
override val key: Preferences.Key<Boolean>
|
||||
get() = booleanPreferencesKey("flowArticleListFeedIcon")
|
||||
}
|
||||
|
||||
object FlowArticleListFeedName : DataStoreKeys<Boolean>() {
|
||||
override val key: Preferences.Key<Boolean>
|
||||
get() = booleanPreferencesKey("flowArticleListFeedName")
|
||||
}
|
||||
|
||||
object FlowArticleListImage : DataStoreKeys<Boolean>() {
|
||||
override val key: Preferences.Key<Boolean>
|
||||
get() = booleanPreferencesKey("flowArticleListImage")
|
||||
}
|
||||
|
||||
object FlowArticleListDesc : DataStoreKeys<Boolean>() {
|
||||
override val key: Preferences.Key<Boolean>
|
||||
get() = booleanPreferencesKey("flowArticleListDesc")
|
||||
}
|
||||
|
||||
object FlowArticleListTime : DataStoreKeys<Boolean>() {
|
||||
override val key: Preferences.Key<Boolean>
|
||||
get() = booleanPreferencesKey("flowArticleListTime")
|
||||
}
|
||||
|
||||
object FlowArticleListDateStickyHeader : DataStoreKeys<Boolean>() {
|
||||
override val key: Preferences.Key<Boolean>
|
||||
get() = booleanPreferencesKey("flowArticleListDateStickyHeader")
|
||||
}
|
||||
|
||||
object FlowArticleListTonalElevation : DataStoreKeys<Int>() {
|
||||
override val key: Preferences.Key<Int>
|
||||
get() = intPreferencesKey("flowArticleListTonalElevation")
|
||||
}
|
||||
|
||||
object InitialPage : DataStoreKeys<Int>() {
|
||||
override val key: Preferences.Key<Int>
|
||||
get() = intPreferencesKey("initialPage")
|
||||
|
|
|
@ -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 <T> StateFlow<T>.collectAsStateValue(
|
||||
context: CoroutineContext = Dispatchers.Default
|
||||
): T = collectAsState(context).value
|
||||
): T = collectAsState(context).value
|
||||
|
||||
@Composable
|
||||
fun <T : R, R> Flow<T>.collectAsStateValue(
|
||||
initial: R,
|
||||
context: CoroutineContext = Dispatchers.Default
|
||||
): R = collectAsState(initial, context).value
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
}
|
|
@ -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(),
|
||||
|
|
|
@ -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
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
|
|
|
@ -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<Feed>,
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,9 @@ import me.ash.reader.data.entity.ArticleWithFeed
|
|||
@OptIn(ExperimentalFoundationApi::class)
|
||||
fun LazyListScope.ArticleList(
|
||||
pagingItems: LazyPagingItems<FlowItemView>,
|
||||
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 -> {}
|
||||
|
|
|
@ -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<FlowItemView>,
|
||||
) {
|
||||
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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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<TonalPalettes>,
|
||||
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
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
},
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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<TonalPalettes> = 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 =
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -79,7 +79,7 @@
|
|||
<string name="accounts">账户</string>
|
||||
<string name="accounts_desc">本地、FreshRSS</string>
|
||||
<string name="color_and_style">颜色和样式</string>
|
||||
<string name="color_and_style_desc">主题、色彩系统、字体大小</string>
|
||||
<string name="color_and_style_desc">主题、色调样式、字体大小</string>
|
||||
<string name="interaction">交互</string>
|
||||
<string name="interaction_desc">启动时、触感反馈</string>
|
||||
<string name="languages">语言</string>
|
||||
|
@ -99,7 +99,7 @@
|
|||
<string name="primary_color_hint">例如 #666666 或 666666</string>
|
||||
<string name="appearance">外观</string>
|
||||
<string name="style">样式</string>
|
||||
<string name="dark_theme">深色模式</string>
|
||||
<string name="dark_theme">深色主题</string>
|
||||
<string name="use_device_theme">跟随系统设置</string>
|
||||
<string name="tonal_elevation">色调海拔</string>
|
||||
<string name="fonts">字体</string>
|
||||
|
@ -124,4 +124,29 @@
|
|||
<string name="on_start">启动时</string>
|
||||
<string name="initial_page">起始页面</string>
|
||||
<string name="initial_filter">起始过滤条件</string>
|
||||
<string name="preview_article_title">呜呜呜,黎明之剑完结了</string>
|
||||
<string name="preview_article_desc">是宴席,就有结束的时候,但这本书真的陪了我好久啊,好舍不得,而且主线结束了坑却没填完,不知道啥时候才有番外啊</string>
|
||||
<string name="preview_feed_name">漩涡书院</string>
|
||||
<string name="value">值</string>
|
||||
<string name="padding_on_both_ends">两端边距</string>
|
||||
<string name="display_article_date">显示文章发布时间</string>
|
||||
<string name="display_article_desc">显示文章描述</string>
|
||||
<string name="display_article_image">显示文章插图</string>
|
||||
<string name="display_feed_name">显示订阅源名称</string>
|
||||
<string name="display_feed_favicon">显示订阅源图标</string>
|
||||
<string name="article_date_sticky_header">文章发布日期粘性标签(实验性)</string>
|
||||
<string name="article_list">文章列表</string>
|
||||
<string name="group_list">分组列表</string>
|
||||
<string name="always_expand">始终展开</string>
|
||||
<string name="top">顶部</string>
|
||||
<string name="mark_as_read_button_position">“标记为已读”按钮的位置</string>
|
||||
<string name="top_bar">标题栏</string>
|
||||
<string name="fill_selected_icon">填充已选中的图标</string>
|
||||
<string name="filter_bar">过滤栏</string>
|
||||
<string name="icons">图标</string>
|
||||
<string name="icons_and_labels">图标和文字</string>
|
||||
<string name="icons_and_label_only_selected">图标和文字(仅选中时)</string>
|
||||
<string name="tips_top_bar_tonal_elevation">标题栏的色调海拔仅在滚动时可用。</string>
|
||||
<string name="tips_article_list_tonal_elevation">文章列表的色调海拔仅在亮色主题时可用。</string>
|
||||
<string name="tips_group_list_tonal_elevation">分组列表的色调海拔仅在亮色主题时可用。</string>
|
||||
</resources>
|
|
@ -79,7 +79,7 @@
|
|||
<string name="accounts">Accounts</string>
|
||||
<string name="accounts_desc">Local, FreshRSS</string>
|
||||
<string name="color_and_style">Color & style</string>
|
||||
<string name="color_and_style_desc">Theme, color system, font size</string>
|
||||
<string name="color_and_style_desc">Theme, color style, font size</string>
|
||||
<string name="interaction">Interaction</string>
|
||||
<string name="interaction_desc">On start, haptic feedback</string>
|
||||
<string name="languages">Languages</string>
|
||||
|
@ -88,9 +88,9 @@
|
|||
<string name="tips_and_support_desc">About, open source</string>
|
||||
<string name="welcome">Welcome</string>
|
||||
<string name="agree_terms">Before you can continue, you need to agree to Read You\'s Terms of Service and Privacy Policy.</string>
|
||||
<string name="view_terms">View the <i><u>Terms of Service and Privacy Policy</u></i></string>
|
||||
<string name="view_terms">View the <i><u>Terms of Service & Privacy Policy</u></i></string>
|
||||
<string name="terms_link">https://github.com/Ashinch/ReadYou/blob/main/TERMS_OF_SERVICE_AND_PRIVACY_POLICY.md</string>
|
||||
<string name="agree_and_continue">Agree and Continue</string>
|
||||
<string name="agree_and_continue">Agree</string>
|
||||
<string name="wallpaper_colors">Wallpaper Colors</string>
|
||||
<string name="no_palettes">No Palettes</string>
|
||||
<string name="only_android_8.1_plus">Only Android 8.1+</string>
|
||||
|
@ -116,7 +116,7 @@
|
|||
<string name="update">Update</string>
|
||||
<string name="skip_this_version">Skip This Version</string>
|
||||
<string name="checking_updates">Checking for updates…</string>
|
||||
<string name="is_latest_version">This is the latest version</string>
|
||||
<string name="is_latest_version">This is latest version</string>
|
||||
<string name="check_failure">Check failure</string>
|
||||
<string name="download_failure">Download failure</string>
|
||||
<string name="rate_limit">The request rate is limited</string>
|
||||
|
@ -124,4 +124,29 @@
|
|||
<string name="on_start">On Start</string>
|
||||
<string name="initial_page">Initial Page</string>
|
||||
<string name="initial_filter">Initial Filter</string>
|
||||
<string name="preview_article_title">The novel "Lord of the Mysteries" has finally come to an end</string>
|
||||
<string name="preview_article_desc">The Fool is the eighth and final volume of the Lord of the Mysteries series written by Cuttlefish That Loves Diving.</string>
|
||||
<string name="preview_feed_name">Reddit</string>
|
||||
<string name="value">value</string>
|
||||
<string name="padding_on_both_ends">Padding on Both Ends</string>
|
||||
<string name="display_article_date">Display Article Publish Time</string>
|
||||
<string name="display_article_desc">Display Article Descriptions</string>
|
||||
<string name="display_article_image">Display Article Images</string>
|
||||
<string name="display_feed_name">Display Feed Names</string>
|
||||
<string name="display_feed_favicon">Display Feed Favicons</string>
|
||||
<string name="article_date_sticky_header">Article Publish Date Sticky Header (Experimental)</string>
|
||||
<string name="article_list">Article List</string>
|
||||
<string name="group_list">Group List</string>
|
||||
<string name="always_expand">Always Expand</string>
|
||||
<string name="top">Top</string>
|
||||
<string name="mark_as_read_button_position">\"Mark as Read\" Button Position</string>
|
||||
<string name="top_bar">Top Bar</string>
|
||||
<string name="fill_selected_icon">Fill The Selected Icon</string>
|
||||
<string name="filter_bar">Filter Bar</string>
|
||||
<string name="icons">Icons</string>
|
||||
<string name="icons_and_labels">Icons & Labels</string>
|
||||
<string name="icons_and_label_only_selected">Icons & Label (Only Selected)</string>
|
||||
<string name="tips_top_bar_tonal_elevation">Tone elevation of the top bar is only available when scrolling.</string>
|
||||
<string name="tips_article_list_tonal_elevation">Tone elevation of the article list is only available for light theme.</string>
|
||||
<string name="tips_group_list_tonal_elevation">Tone elevation of the group list is only available for light theme.</string>
|
||||
</resources>
|
Loading…
Reference in New Issue
Block a user