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