commit
c1a4c8ec5b
|
@ -18,6 +18,7 @@ import coil.memory.MemoryCache
|
||||||
import coil.request.*
|
import coil.request.*
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.CompletableDeferred
|
import kotlinx.coroutines.CompletableDeferred
|
||||||
|
import me.ash.reader.data.preference.SettingsProvider
|
||||||
import me.ash.reader.ui.page.common.HomeEntry
|
import me.ash.reader.ui.page.common.HomeEntry
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
|
@ -28,7 +29,9 @@ class MainActivity : ComponentActivity(), ImageLoader {
|
||||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||||
Log.i("RLog", "onCreate: ${ProfileInstallerInitializer().create(this)}")
|
Log.i("RLog", "onCreate: ${ProfileInstallerInitializer().create(this)}")
|
||||||
setContent {
|
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.Icons
|
||||||
import androidx.compose.material.icons.outlined.FiberManualRecord
|
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.StarOutline
|
||||||
import androidx.compose.material.icons.rounded.Subject
|
import androidx.compose.material.icons.rounded.Subject
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
|
||||||
class Filter(
|
class Filter(
|
||||||
var index: Int,
|
val index: Int,
|
||||||
var icon: ImageVector,
|
val iconOutline: ImageVector,
|
||||||
|
val iconFilled: ImageVector,
|
||||||
) {
|
) {
|
||||||
fun isStarred(): Boolean = this == Starred
|
fun isStarred(): Boolean = this == Starred
|
||||||
fun isUnread(): Boolean = this == Unread
|
fun isUnread(): Boolean = this == Unread
|
||||||
|
@ -17,15 +20,18 @@ class Filter(
|
||||||
companion object {
|
companion object {
|
||||||
val Starred = Filter(
|
val Starred = Filter(
|
||||||
index = 0,
|
index = 0,
|
||||||
icon = Icons.Rounded.StarOutline,
|
iconOutline = Icons.Rounded.StarOutline,
|
||||||
|
iconFilled = Icons.Rounded.Star,
|
||||||
)
|
)
|
||||||
val Unread = Filter(
|
val Unread = Filter(
|
||||||
index = 1,
|
index = 1,
|
||||||
icon = Icons.Outlined.FiberManualRecord,
|
iconOutline = Icons.Outlined.FiberManualRecord,
|
||||||
|
iconFilled = Icons.Rounded.FiberManualRecord,
|
||||||
)
|
)
|
||||||
val All = Filter(
|
val All = Filter(
|
||||||
index = 2,
|
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),
|
.alpha(if (enable) 1f else 0.5f),
|
||||||
shape = CircleShape,
|
shape = CircleShape,
|
||||||
color = animateColorAsState(
|
color = animateColorAsState(
|
||||||
if (activated) (tonalPalettes primary 40) onDark (tonalPalettes neutralVariant 50)
|
if (activated) (tonalPalettes primary 40) onDark (tonalPalettes secondary 50)
|
||||||
else (tonalPalettes neutralVariant 50) onDark (tonalPalettes neutral 60)
|
else (tonalPalettes neutralVariant 50) onDark (tonalPalettes neutral 30)
|
||||||
).value
|
).value
|
||||||
) {
|
) {
|
||||||
Box(
|
Box(
|
||||||
|
@ -61,7 +61,7 @@ fun Switch(
|
||||||
shape = CircleShape,
|
shape = CircleShape,
|
||||||
color = animateColorAsState(
|
color = animateColorAsState(
|
||||||
if (activated) tonalPalettes primary 90
|
if (activated) tonalPalettes primary 90
|
||||||
else (tonalPalettes neutralVariant 70) onDark (tonalPalettes neutral 30)
|
else (tonalPalettes neutralVariant 70) onDark (tonalPalettes neutral 60)
|
||||||
).value
|
).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(
|
fun ColorScheme.surfaceColorAtElevation(
|
||||||
elevation: Dp,
|
elevation: Dp,
|
||||||
|
color: Color = surface,
|
||||||
|
): Color = color.atElevation(surfaceTint, elevation)
|
||||||
|
|
||||||
|
fun Color.atElevation(
|
||||||
|
sourceColor: Color,
|
||||||
|
elevation: Dp,
|
||||||
): Color {
|
): Color {
|
||||||
if (elevation == 0.dp) return surface
|
if (elevation == 0.dp) return this
|
||||||
val alpha = ((4.5f * ln(elevation.value + 1)) + 2f) / 100f
|
return sourceColor.copy(alpha = elevation.alphaLN(constant = 4.5f)).compositeOver(this)
|
||||||
return primary.copy(alpha = alpha).compositeOver(surface)
|
}
|
||||||
}
|
|
||||||
|
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.core.DataStore
|
||||||
import androidx.datastore.preferences.core.*
|
import androidx.datastore.preferences.core.*
|
||||||
import androidx.datastore.preferences.preferencesDataStore
|
import androidx.datastore.preferences.preferencesDataStore
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.catch
|
import kotlinx.coroutines.flow.catch
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")
|
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")
|
||||||
|
@ -31,10 +33,7 @@ val Context.currentAccountId: Int
|
||||||
get() = this.dataStore.get(DataStoreKeys.CurrentAccountId)!!
|
get() = this.dataStore.get(DataStoreKeys.CurrentAccountId)!!
|
||||||
val Context.currentAccountType: Int
|
val Context.currentAccountType: Int
|
||||||
get() = this.dataStore.get(DataStoreKeys.CurrentAccountType)!!
|
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
|
val Context.initialPage: Int
|
||||||
get() = this.dataStore.get(DataStoreKeys.InitialPage) ?: 0
|
get() = this.dataStore.get(DataStoreKeys.InitialPage) ?: 0
|
||||||
val Context.initialFilter: Int
|
val Context.initialFilter: Int
|
||||||
|
@ -42,7 +41,9 @@ val Context.initialFilter: Int
|
||||||
|
|
||||||
suspend fun <T> DataStore<Preferences>.put(dataStoreKeys: DataStoreKeys<T>, value: T) {
|
suspend fun <T> DataStore<Preferences>.put(dataStoreKeys: DataStoreKeys<T>, value: T) {
|
||||||
this.edit {
|
this.edit {
|
||||||
it[dataStoreKeys.key] = value
|
withContext(Dispatchers.IO) {
|
||||||
|
it[dataStoreKeys.key] = value
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,6 +130,101 @@ sealed class DataStoreKeys<T> {
|
||||||
get() = stringPreferencesKey("customPrimaryColor")
|
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>() {
|
object InitialPage : DataStoreKeys<Int>() {
|
||||||
override val key: Preferences.Key<Int>
|
override val key: Preferences.Key<Int>
|
||||||
get() = intPreferencesKey("initialPage")
|
get() = intPreferencesKey("initialPage")
|
||||||
|
|
|
@ -3,10 +3,17 @@ package me.ash.reader.ui.ext
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun <T> StateFlow<T>.collectAsStateValue(
|
fun <T> StateFlow<T>.collectAsStateValue(
|
||||||
context: CoroutineContext = Dispatchers.Default
|
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.home.read.ReadPage
|
||||||
import me.ash.reader.ui.page.settings.SettingsPage
|
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.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.interaction.Interaction
|
||||||
import me.ash.reader.ui.page.settings.tips.TipsAndSupport
|
import me.ash.reader.ui.page.settings.tips.TipsAndSupport
|
||||||
import me.ash.reader.ui.page.startup.StartupPage
|
import me.ash.reader.ui.page.startup.StartupPage
|
||||||
|
@ -34,6 +36,7 @@ fun HomeEntry(
|
||||||
homeViewModel: HomeViewModel = hiltViewModel(),
|
homeViewModel: HomeViewModel = hiltViewModel(),
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
val viewState = homeViewModel.viewState.collectAsStateValue()
|
val viewState = homeViewModel.viewState.collectAsStateValue()
|
||||||
val filterState = homeViewModel.filterState.collectAsStateValue()
|
val filterState = homeViewModel.filterState.collectAsStateValue()
|
||||||
val pagingItems = viewState.pagingData.collectAsLazyPagingItems()
|
val pagingItems = viewState.pagingData.collectAsLazyPagingItems()
|
||||||
|
@ -97,9 +100,12 @@ fun HomeEntry(
|
||||||
navController = navController,
|
navController = navController,
|
||||||
startDestination = if (context.isFirstLaunch) RouteName.STARTUP else RouteName.FEEDS,
|
startDestination = if (context.isFirstLaunch) RouteName.STARTUP else RouteName.FEEDS,
|
||||||
) {
|
) {
|
||||||
|
// Startup
|
||||||
animatedComposable(route = RouteName.STARTUP) {
|
animatedComposable(route = RouteName.STARTUP) {
|
||||||
StartupPage(navController)
|
StartupPage(navController)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Home
|
||||||
animatedComposable(route = RouteName.FEEDS) {
|
animatedComposable(route = RouteName.FEEDS) {
|
||||||
FeedsPage(navController = navController, homeViewModel = homeViewModel)
|
FeedsPage(navController = navController, homeViewModel = homeViewModel)
|
||||||
}
|
}
|
||||||
|
@ -113,15 +119,29 @@ fun HomeEntry(
|
||||||
animatedComposable(route = "${RouteName.READING}/{articleId}") {
|
animatedComposable(route = "${RouteName.READING}/{articleId}") {
|
||||||
ReadPage(navController = navController)
|
ReadPage(navController = navController)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Settings
|
||||||
animatedComposable(route = RouteName.SETTINGS) {
|
animatedComposable(route = RouteName.SETTINGS) {
|
||||||
SettingsPage(navController)
|
SettingsPage(navController)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Color & Style
|
||||||
animatedComposable(route = RouteName.COLOR_AND_STYLE) {
|
animatedComposable(route = RouteName.COLOR_AND_STYLE) {
|
||||||
ColorAndStyle(navController)
|
ColorAndStyle(navController)
|
||||||
}
|
}
|
||||||
|
animatedComposable(route = RouteName.FEEDS_PAGE_STYLE) {
|
||||||
|
FeedsPageStyle(navController)
|
||||||
|
}
|
||||||
|
animatedComposable(route = RouteName.FLOW_PAGE_STYLE) {
|
||||||
|
FlowPageStyle(navController)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interaction
|
||||||
animatedComposable(route = RouteName.INTERACTION) {
|
animatedComposable(route = RouteName.INTERACTION) {
|
||||||
Interaction(navController)
|
Interaction(navController)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tips & Support
|
||||||
animatedComposable(route = RouteName.TIPS_AND_SUPPORT) {
|
animatedComposable(route = RouteName.TIPS_AND_SUPPORT) {
|
||||||
TipsAndSupport(navController)
|
TipsAndSupport(navController)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,25 @@
|
||||||
package me.ash.reader.ui.page.common
|
package me.ash.reader.ui.page.common
|
||||||
|
|
||||||
object RouteName {
|
object RouteName {
|
||||||
|
// Startup
|
||||||
const val STARTUP = "startup"
|
const val STARTUP = "startup"
|
||||||
|
|
||||||
|
// Home
|
||||||
const val FEEDS = "feeds"
|
const val FEEDS = "feeds"
|
||||||
const val FLOW = "flow"
|
const val FLOW = "flow"
|
||||||
const val READING = "reading"
|
const val READING = "reading"
|
||||||
|
|
||||||
|
// Settings
|
||||||
const val SETTINGS = "settings"
|
const val SETTINGS = "settings"
|
||||||
|
|
||||||
|
// Color & Style
|
||||||
const val COLOR_AND_STYLE = "color_and_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"
|
const val INTERACTION = "interaction"
|
||||||
|
|
||||||
|
// Tips & Support
|
||||||
const val TIPS_AND_SUPPORT = "tips_and_support"
|
const val TIPS_AND_SUPPORT = "tips_and_support"
|
||||||
}
|
}
|
|
@ -1,70 +1,84 @@
|
||||||
package me.ash.reader.ui.page.home
|
package me.ash.reader.ui.page.home
|
||||||
|
|
||||||
import android.view.SoundEffectConstants
|
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.material3.*
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalView
|
import androidx.compose.ui.platform.LocalView
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.zIndex
|
|
||||||
import com.google.accompanist.pager.ExperimentalPagerApi
|
import com.google.accompanist.pager.ExperimentalPagerApi
|
||||||
import me.ash.reader.data.entity.Filter
|
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.ext.getName
|
||||||
import me.ash.reader.ui.theme.palette.alwaysLight
|
import me.ash.reader.ui.theme.palette.onDark
|
||||||
|
|
||||||
@OptIn(ExperimentalPagerApi::class)
|
@OptIn(ExperimentalPagerApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun FilterBar(
|
fun FilterBar(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
filter: Filter,
|
filter: Filter,
|
||||||
|
filterBarStyle: Int,
|
||||||
|
filterBarFilled: Boolean,
|
||||||
|
filterBarPadding: Dp,
|
||||||
|
filterBarTonalElevation: Dp,
|
||||||
filterOnClick: (Filter) -> Unit = {},
|
filterOnClick: (Filter) -> Unit = {},
|
||||||
) {
|
) {
|
||||||
val view = LocalView.current
|
val view = LocalView.current
|
||||||
|
|
||||||
Box(
|
NavigationBar(
|
||||||
modifier = Modifier.height(60.dp)
|
tonalElevation = filterBarTonalElevation,
|
||||||
) {
|
) {
|
||||||
Divider(
|
Spacer(modifier = Modifier.width(filterBarPadding))
|
||||||
modifier = Modifier
|
listOf(
|
||||||
.fillMaxWidth()
|
Filter.Starred,
|
||||||
.height(1.dp)
|
Filter.Unread,
|
||||||
.zIndex(1f),
|
Filter.All,
|
||||||
color = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.24f)
|
).forEach { item ->
|
||||||
)
|
NavigationBarItem(
|
||||||
NavigationBar(
|
// modifier = Modifier.height(60.dp),
|
||||||
modifier = Modifier.fillMaxSize(),
|
alwaysShowLabel = when (filterBarStyle) {
|
||||||
tonalElevation = 0.dp,
|
FlowFilterBarStylePreference.Icon.value -> false
|
||||||
) {
|
FlowFilterBarStylePreference.IconLabel.value -> true
|
||||||
Spacer(modifier = Modifier.width(60.dp))
|
FlowFilterBarStylePreference.IconLabelOnlySelected.value -> false
|
||||||
listOf(
|
else -> false
|
||||||
Filter.Starred,
|
},
|
||||||
Filter.Unread,
|
icon = {
|
||||||
Filter.All,
|
Icon(
|
||||||
).forEach { item ->
|
imageVector = if (filter == item && filterBarFilled) {
|
||||||
NavigationBarItem(
|
item.iconFilled
|
||||||
icon = {
|
} else {
|
||||||
Icon(
|
item.iconOutline
|
||||||
imageVector = item.icon,
|
},
|
||||||
contentDescription = item.getName()
|
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,
|
|
||||||
)
|
)
|
||||||
|
},
|
||||||
|
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() {
|
private fun fetchArticles() {
|
||||||
_viewState.update {
|
_viewState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
pagingData = Pager(PagingConfig(pageSize = 10)) {
|
pagingData = Pager(PagingConfig(pageSize = 15)) {
|
||||||
if (_viewState.value.searchContent.isNotBlank()) {
|
if (_viewState.value.searchContent.isNotBlank()) {
|
||||||
rssRepository.get().searchArticles(
|
rssRepository.get().searchArticles(
|
||||||
content = _viewState.value.searchContent.trim(),
|
content = _viewState.value.searchContent.trim(),
|
||||||
|
|
|
@ -16,11 +16,13 @@ import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.platform.LocalView
|
import androidx.compose.ui.platform.LocalView
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import me.ash.reader.data.entity.Feed
|
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.FeedOptionViewAction
|
||||||
import me.ash.reader.ui.page.home.feeds.option.feed.FeedOptionViewModel
|
import me.ash.reader.ui.page.home.feeds.option.feed.FeedOptionViewModel
|
||||||
|
import kotlin.math.ln
|
||||||
|
|
||||||
@OptIn(
|
@OptIn(
|
||||||
androidx.compose.foundation.ExperimentalFoundationApi::class,
|
androidx.compose.foundation.ExperimentalFoundationApi::class,
|
||||||
|
@ -31,6 +33,7 @@ fun FeedItem(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
feed: Feed,
|
feed: Feed,
|
||||||
feedOptionViewModel: FeedOptionViewModel = hiltViewModel(),
|
feedOptionViewModel: FeedOptionViewModel = hiltViewModel(),
|
||||||
|
tonalElevation: Dp,
|
||||||
onClick: () -> Unit = {},
|
onClick: () -> Unit = {},
|
||||||
) {
|
) {
|
||||||
val view = LocalView.current
|
val view = LocalView.current
|
||||||
|
@ -76,18 +79,18 @@ fun FeedItem(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (feed.important ?: 0 != 0) {
|
if (feed.important ?: 0 != 0) {
|
||||||
Row() {
|
Badge(
|
||||||
Badge(
|
containerColor = MaterialTheme.colorScheme.surfaceTint.copy(
|
||||||
containerColor = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.24f),
|
alpha = (ln(tonalElevation.value + 1.4f) + 2f) / 100f
|
||||||
contentColor = MaterialTheme.colorScheme.outline,
|
),
|
||||||
content = {
|
contentColor = MaterialTheme.colorScheme.outline,
|
||||||
Text(
|
content = {
|
||||||
text = feed.important.toString(),
|
Text(
|
||||||
style = MaterialTheme.typography.labelSmall
|
text = feed.important.toString(),
|
||||||
)
|
style = MaterialTheme.typography.labelSmall
|
||||||
},
|
)
|
||||||
)
|
},
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ import androidx.navigation.NavHostController
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import me.ash.reader.R
|
import me.ash.reader.R
|
||||||
import me.ash.reader.data.entity.toVersion
|
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.data.repository.SyncWorker.Companion.getIsSyncing
|
||||||
import me.ash.reader.ui.component.Banner
|
import me.ash.reader.ui.component.Banner
|
||||||
import me.ash.reader.ui.component.DisplayText
|
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.SubscribeDialog
|
||||||
import me.ash.reader.ui.page.home.feeds.subscribe.SubscribeViewAction
|
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.page.home.feeds.subscribe.SubscribeViewModel
|
||||||
|
import me.ash.reader.ui.theme.palette.onDark
|
||||||
|
|
||||||
@SuppressLint("FlowOperatorInvokedInComposition")
|
@SuppressLint("FlowOperatorInvokedInComposition")
|
||||||
@OptIn(
|
@OptIn(
|
||||||
|
@ -59,6 +61,14 @@ fun FeedsPage(
|
||||||
homeViewModel: HomeViewModel,
|
homeViewModel: HomeViewModel,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
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 feedsViewState = feedsViewModel.viewState.collectAsStateValue()
|
||||||
val filterState = homeViewModel.filterState.collectAsStateValue()
|
val filterState = homeViewModel.filterState.collectAsStateValue()
|
||||||
|
|
||||||
|
@ -117,11 +127,19 @@ fun FeedsPage(
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.background(MaterialTheme.colorScheme.surface)
|
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(topBarTonalElevation.value.dp))
|
||||||
.statusBarsPadding()
|
.statusBarsPadding()
|
||||||
.navigationBarsPadding(),
|
.navigationBarsPadding(),
|
||||||
|
containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(
|
||||||
|
groupListTonalElevation.value.dp
|
||||||
|
) onDark MaterialTheme.colorScheme.surface,
|
||||||
topBar = {
|
topBar = {
|
||||||
SmallTopAppBar(
|
SmallTopAppBar(
|
||||||
|
colors = TopAppBarDefaults.smallTopAppBarColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(
|
||||||
|
topBarTonalElevation.value.dp
|
||||||
|
),
|
||||||
|
),
|
||||||
title = {},
|
title = {},
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
FeedbackIconButton(
|
FeedbackIconButton(
|
||||||
|
@ -174,7 +192,7 @@ fun FeedsPage(
|
||||||
Banner(
|
Banner(
|
||||||
title = filterState.filter.getName(),
|
title = filterState.filter.getName(),
|
||||||
desc = feedsViewState.importantCount,
|
desc = feedsViewState.importantCount,
|
||||||
icon = filterState.filter.icon,
|
icon = filterState.filter.iconOutline,
|
||||||
action = {
|
action = {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Outlined.KeyboardArrowRight,
|
imageVector = Icons.Outlined.KeyboardArrowRight,
|
||||||
|
@ -204,6 +222,8 @@ fun FeedsPage(
|
||||||
// Crossfade(targetState = groupWithFeed) { groupWithFeed ->
|
// Crossfade(targetState = groupWithFeed) { groupWithFeed ->
|
||||||
Column {
|
Column {
|
||||||
GroupItem(
|
GroupItem(
|
||||||
|
isExpanded = groupListExpand.value,
|
||||||
|
tonalElevation = groupListTonalElevation.value.dp,
|
||||||
group = groupWithFeed.group,
|
group = groupWithFeed.group,
|
||||||
feeds = groupWithFeed.feeds,
|
feeds = groupWithFeed.feeds,
|
||||||
groupOnClick = {
|
groupOnClick = {
|
||||||
|
@ -241,19 +261,19 @@ fun FeedsPage(
|
||||||
},
|
},
|
||||||
bottomBar = {
|
bottomBar = {
|
||||||
FilterBar(
|
FilterBar(
|
||||||
modifier = Modifier
|
|
||||||
.height(60.dp)
|
|
||||||
.fillMaxWidth(),
|
|
||||||
filter = filterState.filter,
|
filter = filterState.filter,
|
||||||
filterOnClick = {
|
filterBarStyle = filterBarStyle.value,
|
||||||
filterChange(
|
filterBarFilled = filterBarFilled.value,
|
||||||
navController = navController,
|
filterBarPadding = filterBarPadding.dp,
|
||||||
homeViewModel = homeViewModel,
|
filterBarTonalElevation = filterBarTonalElevation.value.dp,
|
||||||
filterState = filterState.copy(filter = it),
|
) {
|
||||||
isNavigate = false,
|
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.platform.LocalView
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import me.ash.reader.R
|
import me.ash.reader.R
|
||||||
import me.ash.reader.data.entity.Feed
|
import me.ash.reader.data.entity.Feed
|
||||||
import me.ash.reader.data.entity.Group
|
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.GroupOptionViewAction
|
||||||
import me.ash.reader.ui.page.home.feeds.option.group.GroupOptionViewModel
|
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
|
@Composable
|
||||||
fun GroupItem(
|
fun GroupItem(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
tonalElevation: Dp,
|
||||||
group: Group,
|
group: Group,
|
||||||
feeds: List<Feed>,
|
feeds: List<Feed>,
|
||||||
isExpanded: Boolean = true,
|
isExpanded: Boolean = true,
|
||||||
|
@ -50,7 +53,9 @@ fun GroupItem(
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = 16.dp)
|
.padding(horizontal = 16.dp)
|
||||||
.clip(RoundedCornerShape(32.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(
|
.combinedClickable(
|
||||||
onClick = {
|
onClick = {
|
||||||
groupOnClick()
|
groupOnClick()
|
||||||
|
@ -82,7 +87,11 @@ fun GroupItem(
|
||||||
.padding(end = 20.dp)
|
.padding(end = 20.dp)
|
||||||
.size(24.dp)
|
.size(24.dp)
|
||||||
.clip(CircleShape)
|
.clip(CircleShape)
|
||||||
.background(MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.24f))
|
.background(
|
||||||
|
MaterialTheme.colorScheme.surfaceTint.copy(
|
||||||
|
alpha = tonalElevation.alphaLN(weight = 1.4f)
|
||||||
|
)
|
||||||
|
)
|
||||||
.clickable {
|
.clickable {
|
||||||
expanded = !expanded
|
expanded = !expanded
|
||||||
},
|
},
|
||||||
|
@ -107,6 +116,7 @@ fun GroupItem(
|
||||||
FeedItem(
|
FeedItem(
|
||||||
modifier = Modifier.padding(horizontal = 20.dp),
|
modifier = Modifier.padding(horizontal = 20.dp),
|
||||||
feed = feed,
|
feed = feed,
|
||||||
|
tonalElevation = tonalElevation,
|
||||||
) {
|
) {
|
||||||
feedOnClick(feed)
|
feedOnClick(feed)
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import me.ash.reader.R
|
import me.ash.reader.R
|
||||||
import me.ash.reader.data.entity.ArticleWithFeed
|
import me.ash.reader.data.entity.ArticleWithFeed
|
||||||
|
import me.ash.reader.data.preference.*
|
||||||
import me.ash.reader.ui.ext.formatAsString
|
import me.ash.reader.ui.ext.formatAsString
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@ -30,6 +31,11 @@ fun ArticleItem(
|
||||||
onClick: (ArticleWithFeed) -> Unit = {},
|
onClick: (ArticleWithFeed) -> Unit = {},
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
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(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
@ -44,67 +50,86 @@ fun ArticleItem(
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
Text(
|
// Feed name
|
||||||
modifier = Modifier
|
if (articleListFeedName.value) {
|
||||||
.weight(1f)
|
Text(
|
||||||
.padding(start = 30.dp),
|
modifier = Modifier
|
||||||
text = articleWithFeed.feed.name,
|
.weight(1f)
|
||||||
color = MaterialTheme.colorScheme.tertiary,
|
.padding(start = if (articleListFeedIcon.value) 30.dp else 0.dp),
|
||||||
style = MaterialTheme.typography.labelMedium,
|
text = articleWithFeed.feed.name,
|
||||||
maxLines = 1,
|
color = MaterialTheme.colorScheme.tertiary,
|
||||||
overflow = TextOverflow.Ellipsis,
|
style = MaterialTheme.typography.labelMedium,
|
||||||
)
|
maxLines = 1,
|
||||||
Row(
|
overflow = TextOverflow.Ellipsis,
|
||||||
modifier = Modifier.padding(start = 6.dp),
|
)
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
}
|
||||||
) {
|
|
||||||
if (articleWithFeed.article.isStarred) {
|
if (articleListDate.value) {
|
||||||
Icon(
|
Row(
|
||||||
modifier = Modifier
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
.size(14.dp)
|
) {
|
||||||
.padding(end = 2.dp),
|
if (!articleListFeedName.value) {
|
||||||
imageVector = Icons.Rounded.Star,
|
Spacer(Modifier.width(if (articleListFeedIcon.value) 30.dp else 0.dp))
|
||||||
contentDescription = stringResource(R.string.starred),
|
}
|
||||||
tint = MaterialTheme.colorScheme.outline.copy(alpha = 0.7f),
|
// 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(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
) {
|
) {
|
||||||
Row(
|
// Feed icon
|
||||||
modifier = Modifier
|
if (articleListFeedIcon.value) {
|
||||||
.size(20.dp)
|
Row(
|
||||||
.clip(CircleShape)
|
modifier = Modifier
|
||||||
.background(MaterialTheme.colorScheme.outline.copy(alpha = 0.2f))
|
.size(20.dp)
|
||||||
) {}
|
.clip(CircleShape)
|
||||||
Spacer(modifier = Modifier.width(10.dp))
|
.background(MaterialTheme.colorScheme.outline.copy(alpha = 0.2f))
|
||||||
|
) {}
|
||||||
|
Spacer(modifier = Modifier.width(10.dp))
|
||||||
|
}
|
||||||
|
// Article
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
) {
|
) {
|
||||||
|
// Title
|
||||||
Text(
|
Text(
|
||||||
text = articleWithFeed.article.title,
|
text = articleWithFeed.article.title,
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
maxLines = 2,
|
maxLines = if (articleListDesc.value) 2 else 4,
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = articleWithFeed.article.shortDescription,
|
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f),
|
|
||||||
style = MaterialTheme.typography.bodySmall,
|
|
||||||
maxLines = 2,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
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)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
fun LazyListScope.ArticleList(
|
fun LazyListScope.ArticleList(
|
||||||
pagingItems: LazyPagingItems<FlowItemView>,
|
pagingItems: LazyPagingItems<FlowItemView>,
|
||||||
|
articleListFeedIcon: Boolean,
|
||||||
|
articleListDateStickyHeader: Boolean,
|
||||||
|
articleListTonalElevation: Int,
|
||||||
onClick: (ArticleWithFeed) -> Unit = {},
|
onClick: (ArticleWithFeed) -> Unit = {},
|
||||||
) {
|
) {
|
||||||
for (index in 0 until pagingItems.itemCount) {
|
for (index in 0 until pagingItems.itemCount) {
|
||||||
|
@ -29,8 +32,14 @@ fun LazyListScope.ArticleList(
|
||||||
is FlowItemView.Date -> {
|
is FlowItemView.Date -> {
|
||||||
val separator = pagingItems[index] as FlowItemView.Date
|
val separator = pagingItems[index] as FlowItemView.Date
|
||||||
if (separator.showSpacer) item { Spacer(modifier = Modifier.height(40.dp)) }
|
if (separator.showSpacer) item { Spacer(modifier = Modifier.height(40.dp)) }
|
||||||
stickyHeader {
|
if (articleListDateStickyHeader) {
|
||||||
StickyHeader(separator.date)
|
stickyHeader(key = separator.date) {
|
||||||
|
StickyHeader(separator.date, articleListFeedIcon, articleListTonalElevation)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
item(key = separator.date) {
|
||||||
|
StickyHeader(separator.date, articleListFeedIcon, articleListTonalElevation)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> {}
|
else -> {}
|
||||||
|
|
|
@ -11,10 +11,7 @@ import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.rounded.ArrowBack
|
import androidx.compose.material.icons.rounded.ArrowBack
|
||||||
import androidx.compose.material.icons.rounded.DoneAll
|
import androidx.compose.material.icons.rounded.DoneAll
|
||||||
import androidx.compose.material.icons.rounded.Search
|
import androidx.compose.material.icons.rounded.Search
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Scaffold
|
|
||||||
import androidx.compose.material3.SmallTopAppBar
|
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
|
@ -29,17 +26,20 @@ import androidx.paging.compose.LazyPagingItems
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import me.ash.reader.R
|
import me.ash.reader.R
|
||||||
|
import me.ash.reader.data.preference.*
|
||||||
import me.ash.reader.data.repository.SyncWorker.Companion.getIsSyncing
|
import me.ash.reader.data.repository.SyncWorker.Companion.getIsSyncing
|
||||||
import me.ash.reader.ui.component.DisplayText
|
import me.ash.reader.ui.component.DisplayText
|
||||||
import me.ash.reader.ui.component.FeedbackIconButton
|
import me.ash.reader.ui.component.FeedbackIconButton
|
||||||
import me.ash.reader.ui.component.SwipeRefresh
|
import me.ash.reader.ui.component.SwipeRefresh
|
||||||
import me.ash.reader.ui.ext.collectAsStateValue
|
import me.ash.reader.ui.ext.collectAsStateValue
|
||||||
import me.ash.reader.ui.ext.getName
|
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.common.RouteName
|
||||||
import me.ash.reader.ui.page.home.FilterBar
|
import me.ash.reader.ui.page.home.FilterBar
|
||||||
import me.ash.reader.ui.page.home.FilterState
|
import me.ash.reader.ui.page.home.FilterState
|
||||||
import me.ash.reader.ui.page.home.HomeViewAction
|
import me.ash.reader.ui.page.home.HomeViewAction
|
||||||
import me.ash.reader.ui.page.home.HomeViewModel
|
import me.ash.reader.ui.page.home.HomeViewModel
|
||||||
|
import me.ash.reader.ui.theme.palette.onDark
|
||||||
|
|
||||||
@OptIn(
|
@OptIn(
|
||||||
ExperimentalMaterial3Api::class,
|
ExperimentalMaterial3Api::class,
|
||||||
|
@ -55,6 +55,14 @@ fun FlowPage(
|
||||||
pagingItems: LazyPagingItems<FlowItemView>,
|
pagingItems: LazyPagingItems<FlowItemView>,
|
||||||
) {
|
) {
|
||||||
val keyboardController = LocalSoftwareKeyboardController.current
|
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 scope = rememberCoroutineScope()
|
||||||
val focusRequester = remember { FocusRequester() }
|
val focusRequester = remember { FocusRequester() }
|
||||||
|
@ -100,12 +108,20 @@ fun FlowPage(
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.background(MaterialTheme.colorScheme.surface)
|
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(topBarTonalElevation.value.dp))
|
||||||
.statusBarsPadding()
|
.statusBarsPadding()
|
||||||
.navigationBarsPadding(),
|
.navigationBarsPadding(),
|
||||||
|
containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(
|
||||||
|
articleListTonalElevation.value.dp
|
||||||
|
) onDark MaterialTheme.colorScheme.surface,
|
||||||
topBar = {
|
topBar = {
|
||||||
SmallTopAppBar(
|
SmallTopAppBar(
|
||||||
title = {},
|
title = {},
|
||||||
|
colors = TopAppBarDefaults.smallTopAppBarColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(
|
||||||
|
topBarTonalElevation.value.dp
|
||||||
|
),
|
||||||
|
),
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
FeedbackIconButton(
|
FeedbackIconButton(
|
||||||
imageVector = Icons.Rounded.ArrowBack,
|
imageVector = Icons.Rounded.ArrowBack,
|
||||||
|
@ -113,7 +129,7 @@ fun FlowPage(
|
||||||
tint = MaterialTheme.colorScheme.onSurface
|
tint = MaterialTheme.colorScheme.onSurface
|
||||||
) {
|
) {
|
||||||
onSearch = false
|
onSearch = false
|
||||||
if(navController.previousBackStackEntry == null) {
|
if (navController.previousBackStackEntry == null) {
|
||||||
navController.navigate(RouteName.FEEDS) {
|
navController.navigate(RouteName.FEEDS) {
|
||||||
launchSingleTop = true
|
launchSingleTop = true
|
||||||
}
|
}
|
||||||
|
@ -182,7 +198,7 @@ fun FlowPage(
|
||||||
state = listState,
|
state = listState,
|
||||||
) {
|
) {
|
||||||
item {
|
item {
|
||||||
DisplayTextHeader(filterState, isSyncing)
|
DisplayTextHeader(filterState, isSyncing, articleListFeedIcon.value)
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
visible = markAsRead,
|
visible = markAsRead,
|
||||||
enter = fadeIn() + expandVertically(),
|
enter = fadeIn() + expandVertically(),
|
||||||
|
@ -244,6 +260,9 @@ fun FlowPage(
|
||||||
}
|
}
|
||||||
ArticleList(
|
ArticleList(
|
||||||
pagingItems = pagingItems,
|
pagingItems = pagingItems,
|
||||||
|
articleListFeedIcon = articleListFeedIcon.value,
|
||||||
|
articleListDateStickyHeader = articleListDateStickyHeader.value,
|
||||||
|
articleListTonalElevation = articleListTonalElevation.value,
|
||||||
) {
|
) {
|
||||||
onSearch = false
|
onSearch = false
|
||||||
navController.navigate("${RouteName.READING}/${it.article.id}") {
|
navController.navigate("${RouteName.READING}/${it.article.id}") {
|
||||||
|
@ -261,16 +280,16 @@ fun FlowPage(
|
||||||
},
|
},
|
||||||
bottomBar = {
|
bottomBar = {
|
||||||
FilterBar(
|
FilterBar(
|
||||||
modifier = Modifier
|
|
||||||
.height(60.dp)
|
|
||||||
.fillMaxWidth(),
|
|
||||||
filter = filterState.filter,
|
filter = filterState.filter,
|
||||||
filterOnClick = {
|
filterBarStyle = filterBarStyle.value,
|
||||||
flowViewModel.dispatch(FlowViewAction.ScrollToItem(0))
|
filterBarFilled = filterBarFilled.value,
|
||||||
homeViewModel.dispatch(HomeViewAction.ChangeFilter(filterState.copy(filter = it)))
|
filterBarPadding = filterBarPadding.dp,
|
||||||
homeViewModel.dispatch(HomeViewAction.FetchArticles)
|
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
|
@Composable
|
||||||
private fun DisplayTextHeader(
|
private fun DisplayTextHeader(
|
||||||
filterState: FilterState,
|
filterState: FilterState,
|
||||||
isSyncing: Boolean
|
isSyncing: Boolean,
|
||||||
|
articleListFeedIcon: Boolean,
|
||||||
) {
|
) {
|
||||||
DisplayText(
|
DisplayText(
|
||||||
modifier = Modifier.padding(start = 30.dp),
|
modifier = Modifier.padding(start = if (articleListFeedIcon) 30.dp else 0.dp),
|
||||||
text = when {
|
text = when {
|
||||||
filterState.group != null -> filterState.group.name
|
filterState.group != null -> filterState.group.name
|
||||||
filterState.feed != null -> filterState.feed.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.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import me.ash.reader.ui.ext.surfaceColorAtElevation
|
||||||
|
import me.ash.reader.ui.theme.palette.onDark
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun StickyHeader(currentItemDay: String) {
|
fun StickyHeader(
|
||||||
|
currentItemDay: String,
|
||||||
|
articleListFeedIcon: Boolean,
|
||||||
|
articleListTonalElevation: Int,
|
||||||
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.background(MaterialTheme.colorScheme.surface),
|
.background(
|
||||||
|
MaterialTheme.colorScheme.surfaceColorAtElevation(articleListTonalElevation.dp)
|
||||||
|
onDark MaterialTheme.colorScheme.surface
|
||||||
|
),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier
|
modifier = Modifier.padding(
|
||||||
.padding(start = if (true) 54.dp else 24.dp, bottom = 4.dp),
|
start = if (articleListFeedIcon) 54.dp else 24.dp,
|
||||||
|
bottom = 4.dp
|
||||||
|
),
|
||||||
text = currentItemDay,
|
text = currentItemDay,
|
||||||
color = MaterialTheme.colorScheme.primary,
|
color = MaterialTheme.colorScheme.primary,
|
||||||
style = MaterialTheme.typography.labelLarge,
|
style = MaterialTheme.typography.labelLarge,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package me.ash.reader.ui.page.settings.color
|
package me.ash.reader.ui.page.settings.color
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.compose.animation.*
|
import androidx.compose.animation.*
|
||||||
import androidx.compose.foundation.background
|
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.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.navigation.NavHostController
|
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.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.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.page.settings.SettingItem
|
||||||
import me.ash.reader.ui.svg.PALETTE
|
import me.ash.reader.ui.svg.PALETTE
|
||||||
import me.ash.reader.ui.svg.SVGString
|
import me.ash.reader.ui.svg.SVGString
|
||||||
|
@ -46,8 +48,11 @@ fun ColorAndStyle(
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val useDarkTheme = LocalUseDarkTheme.current
|
val useDarkTheme = LocalUseDarkTheme.current
|
||||||
|
val themeIndex = LocalThemeIndex.current
|
||||||
|
val customPrimaryColor = LocalCustomPrimaryColor.current
|
||||||
|
|
||||||
val wallpaperTonalPalettes = extractTonalPalettesFromUserWallpaper()
|
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(
|
Scaffold(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
@ -111,6 +116,7 @@ fun ColorAndStyle(
|
||||||
onClick = {},
|
onClick = {},
|
||||||
) {
|
) {
|
||||||
Palettes(
|
Palettes(
|
||||||
|
context = context,
|
||||||
palettes = wallpaperTonalPalettes.run {
|
palettes = wallpaperTonalPalettes.run {
|
||||||
if (this.size > 5) {
|
if (this.size > 5) {
|
||||||
this.subList(5, wallpaperTonalPalettes.size)
|
this.subList(5, wallpaperTonalPalettes.size)
|
||||||
|
@ -118,7 +124,9 @@ fun ColorAndStyle(
|
||||||
emptyList()
|
emptyList()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
themeIndex = themeIndex,
|
||||||
themeIndexPrefix = 5,
|
themeIndexPrefix = 5,
|
||||||
|
customPrimaryColor = customPrimaryColor,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
BlockRadioGroupButtonItem(
|
BlockRadioGroupButtonItem(
|
||||||
|
@ -126,7 +134,10 @@ fun ColorAndStyle(
|
||||||
onClick = {},
|
onClick = {},
|
||||||
) {
|
) {
|
||||||
Palettes(
|
Palettes(
|
||||||
palettes = wallpaperTonalPalettes.subList(0, 5)
|
context = context,
|
||||||
|
themeIndex = themeIndex,
|
||||||
|
palettes = wallpaperTonalPalettes.subList(0, 5),
|
||||||
|
customPrimaryColor = customPrimaryColor,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -162,13 +173,19 @@ fun ColorAndStyle(
|
||||||
)
|
)
|
||||||
SettingItem(
|
SettingItem(
|
||||||
title = stringResource(R.string.feeds_page),
|
title = stringResource(R.string.feeds_page),
|
||||||
enable = false,
|
onClick = {
|
||||||
onClick = {},
|
navController.navigate(RouteName.FEEDS_PAGE_STYLE) {
|
||||||
|
launchSingleTop = true
|
||||||
|
}
|
||||||
|
},
|
||||||
) {}
|
) {}
|
||||||
SettingItem(
|
SettingItem(
|
||||||
title = stringResource(R.string.flow_page),
|
title = stringResource(R.string.flow_page),
|
||||||
enable = false,
|
onClick = {
|
||||||
onClick = {},
|
navController.navigate(RouteName.FLOW_PAGE_STYLE) {
|
||||||
|
launchSingleTop = true
|
||||||
|
}
|
||||||
|
},
|
||||||
) {}
|
) {}
|
||||||
SettingItem(
|
SettingItem(
|
||||||
title = stringResource(R.string.reading_page),
|
title = stringResource(R.string.reading_page),
|
||||||
|
@ -186,20 +203,16 @@ fun ColorAndStyle(
|
||||||
@Composable
|
@Composable
|
||||||
fun Palettes(
|
fun Palettes(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
context: Context,
|
||||||
palettes: List<TonalPalettes>,
|
palettes: List<TonalPalettes>,
|
||||||
|
themeIndex: Int = 0,
|
||||||
themeIndexPrefix: Int = 0,
|
themeIndexPrefix: Int = 0,
|
||||||
|
customPrimaryColor: String = "",
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
|
||||||
val scope = rememberCoroutineScope()
|
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()
|
val tonalPalettes = customPrimaryColor.safeHexToColor().toTonalPalettes()
|
||||||
var addDialogVisible by remember { mutableStateOf(false) }
|
var addDialogVisible by remember { mutableStateOf(false) }
|
||||||
var customColorValue by remember { mutableStateOf(context.customPrimaryColor) }
|
var customColorValue by remember { mutableStateOf(customPrimaryColor) }
|
||||||
|
|
||||||
if (palettes.isEmpty()) {
|
if (palettes.isEmpty()) {
|
||||||
Row(
|
Row(
|
||||||
|
@ -239,14 +252,10 @@ fun Palettes(
|
||||||
isCustom = isCustom,
|
isCustom = isCustom,
|
||||||
onClick = {
|
onClick = {
|
||||||
if (isCustom) {
|
if (isCustom) {
|
||||||
|
customColorValue = customPrimaryColor
|
||||||
addDialogVisible = true
|
addDialogVisible = true
|
||||||
} else {
|
} else {
|
||||||
scope.launch(Dispatchers.IO) {
|
ThemeIndexPreference.put(context, scope, themeIndexPrefix + index)
|
||||||
context.dataStore.put(
|
|
||||||
DataStoreKeys.ThemeIndex,
|
|
||||||
themeIndexPrefix + index
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
palette = if (isCustom) tonalPalettes else palette
|
palette = if (isCustom) tonalPalettes else palette
|
||||||
|
@ -266,16 +275,12 @@ fun Palettes(
|
||||||
},
|
},
|
||||||
onDismissRequest = {
|
onDismissRequest = {
|
||||||
addDialogVisible = false
|
addDialogVisible = false
|
||||||
customColorValue = context.customPrimaryColor
|
|
||||||
},
|
},
|
||||||
onConfirm = {
|
onConfirm = {
|
||||||
it.checkColorHex()?.let {
|
it.checkColorHex()?.let {
|
||||||
scope.launch(Dispatchers.IO) {
|
CustomPrimaryColorPreference.put(context, scope, it)
|
||||||
context.dataStore.put(DataStoreKeys.CustomPrimaryColor, it)
|
ThemeIndexPreference.put(context, scope, 4)
|
||||||
context.dataStore.put(DataStoreKeys.ThemeIndex, 4)
|
|
||||||
}
|
|
||||||
addDialogVisible = false
|
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.runtime.*
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalView
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import me.ash.reader.R
|
import me.ash.reader.R
|
||||||
|
@ -32,7 +30,6 @@ fun Interaction(
|
||||||
navController: NavHostController,
|
navController: NavHostController,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val view = LocalView.current
|
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
var initialPageDialogVisible by remember { mutableStateOf(false) }
|
var initialPageDialogVisible by remember { mutableStateOf(false) }
|
||||||
var initialFilterDialogVisible by remember { mutableStateOf(false) }
|
var initialFilterDialogVisible by remember { mutableStateOf(false) }
|
||||||
|
@ -116,7 +113,7 @@ fun Interaction(
|
||||||
text = stringResource(R.string.feeds_page),
|
text = stringResource(R.string.feeds_page),
|
||||||
selected = initialPage == 0,
|
selected = initialPage == 0,
|
||||||
) {
|
) {
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch {
|
||||||
context.dataStore.put(DataStoreKeys.InitialPage, 0)
|
context.dataStore.put(DataStoreKeys.InitialPage, 0)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -124,7 +121,7 @@ fun Interaction(
|
||||||
text = stringResource(R.string.flow_page),
|
text = stringResource(R.string.flow_page),
|
||||||
selected = initialPage == 1,
|
selected = initialPage == 1,
|
||||||
) {
|
) {
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch {
|
||||||
context.dataStore.put(DataStoreKeys.InitialPage, 1)
|
context.dataStore.put(DataStoreKeys.InitialPage, 1)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -141,7 +138,7 @@ fun Interaction(
|
||||||
text = stringResource(R.string.starred),
|
text = stringResource(R.string.starred),
|
||||||
selected = initialFilter == 0,
|
selected = initialFilter == 0,
|
||||||
) {
|
) {
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch {
|
||||||
context.dataStore.put(DataStoreKeys.InitialFilter, 0)
|
context.dataStore.put(DataStoreKeys.InitialFilter, 0)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -149,7 +146,7 @@ fun Interaction(
|
||||||
text = stringResource(R.string.unread),
|
text = stringResource(R.string.unread),
|
||||||
selected = initialFilter == 1,
|
selected = initialFilter == 1,
|
||||||
) {
|
) {
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch {
|
||||||
context.dataStore.put(DataStoreKeys.InitialFilter, 1)
|
context.dataStore.put(DataStoreKeys.InitialFilter, 1)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -157,7 +154,7 @@ fun Interaction(
|
||||||
text = stringResource(R.string.all),
|
text = stringResource(R.string.all),
|
||||||
selected = initialFilter == 2,
|
selected = initialFilter == 2,
|
||||||
) {
|
) {
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch {
|
||||||
context.dataStore.put(DataStoreKeys.InitialFilter, 2)
|
context.dataStore.put(DataStoreKeys.InitialFilter, 2)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -6,7 +6,6 @@ import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.Info
|
|
||||||
import androidx.compose.material.icons.rounded.CheckCircleOutline
|
import androidx.compose.material.icons.rounded.CheckCircleOutline
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
@ -21,6 +20,7 @@ import kotlinx.coroutines.launch
|
||||||
import me.ash.reader.R
|
import me.ash.reader.R
|
||||||
import me.ash.reader.ui.component.DisplayText
|
import me.ash.reader.ui.component.DisplayText
|
||||||
import me.ash.reader.ui.component.DynamicSVGImage
|
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.DataStoreKeys
|
||||||
import me.ash.reader.ui.ext.dataStore
|
import me.ash.reader.ui.ext.dataStore
|
||||||
import me.ash.reader.ui.ext.put
|
import me.ash.reader.ui.ext.put
|
||||||
|
@ -57,14 +57,12 @@ fun StartupPage(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
TipsItem(
|
Tips(
|
||||||
modifier = Modifier
|
modifier = Modifier.padding(top = 40.dp),
|
||||||
.padding(horizontal = 24.dp)
|
text = stringResource(R.string.agree_terms),
|
||||||
.padding(top = 40.dp)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
Spacer(modifier = Modifier.height(10.dp))
|
|
||||||
TextButton(
|
TextButton(
|
||||||
modifier = Modifier.padding(horizontal = 12.dp),
|
modifier = Modifier.padding(horizontal = 12.dp),
|
||||||
onClick = {
|
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 =
|
fun String.parseDynamicColor(tonalPalettes: TonalPalettes, isDarkTheme: Boolean): String =
|
||||||
replace("fill=\"(.+?)\"".toRegex()) {
|
replace("fill=\"(.+?)\"".toRegex()) {
|
||||||
val value = it.groupValues[1]
|
val value = it.groupValues[1]
|
||||||
|
Log.i("RLog", "parseDynamicColor: $value")
|
||||||
if (value.startsWith("#")) return@replace it.value
|
if (value.startsWith("#")) return@replace it.value
|
||||||
try {
|
try {
|
||||||
val (scheme, tone) = value.split("(?<=\\d)(?=\\D)|(?=\\d)(?<=\\D)".toRegex())
|
val (scheme, tone) = value.split("(?<=\\d)(?=\\D)|(?=\\d)(?<=\\D)".toRegex())
|
||||||
|
@ -24,6 +25,7 @@ fun String.parseDynamicColor(tonalPalettes: TonalPalettes, isDarkTheme: Boolean)
|
||||||
}?.toArgb() ?: 0xFFFFFF
|
}?.toArgb() ?: 0xFFFFFF
|
||||||
"fill=\"${String.format("#%06X", 0xFFFFFF and argb)}\""
|
"fill=\"${String.format("#%06X", 0xFFFFFF and argb)}\""
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
Log.e("RLog", "parseDynamicColor: ${e.message}")
|
Log.e("RLog", "parseDynamicColor: ${e.message}")
|
||||||
it.value
|
it.value
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,11 @@
|
||||||
package me.ash.reader.ui.theme
|
package me.ash.reader.ui.theme
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.runtime.collectAsState
|
|
||||||
import androidx.compose.runtime.compositionLocalOf
|
import androidx.compose.runtime.compositionLocalOf
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import me.ash.reader.data.preference.LocalThemeIndex
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import me.ash.reader.ui.ext.DataStoreKeys
|
|
||||||
import me.ash.reader.ui.ext.dataStore
|
|
||||||
import me.ash.reader.ui.theme.palette.LocalTonalPalettes
|
import me.ash.reader.ui.theme.palette.LocalTonalPalettes
|
||||||
import me.ash.reader.ui.theme.palette.TonalPalettes
|
import me.ash.reader.ui.theme.palette.TonalPalettes
|
||||||
import me.ash.reader.ui.theme.palette.core.ProvideZcamViewingConditions
|
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 }
|
val LocalUseDarkTheme = compositionLocalOf { false }
|
||||||
|
|
||||||
@SuppressLint("FlowOperatorInvokedInComposition")
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AppTheme(
|
fun AppTheme(
|
||||||
useDarkTheme: Boolean = isSystemInDarkTheme(),
|
useDarkTheme: Boolean = isSystemInDarkTheme(),
|
||||||
wallpaperPalettes: List<TonalPalettes> = extractTonalPalettesFromUserWallpaper(),
|
wallpaperPalettes: List<TonalPalettes> = extractTonalPalettesFromUserWallpaper(),
|
||||||
content: @Composable () -> Unit
|
content: @Composable () -> Unit
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val themeIndex = LocalThemeIndex.current
|
||||||
val themeIndex = context.dataStore.data
|
|
||||||
.map { it[DataStoreKeys.ThemeIndex.key] ?: 5 }
|
|
||||||
.collectAsState(initial = 5).value
|
|
||||||
|
|
||||||
val tonalPalettes = wallpaperPalettes[
|
val tonalPalettes = wallpaperPalettes[
|
||||||
if (themeIndex >= wallpaperPalettes.size) {
|
if (themeIndex >= wallpaperPalettes.size) {
|
||||||
|
@ -47,7 +38,7 @@ fun AppTheme(
|
||||||
ProvideZcamViewingConditions {
|
ProvideZcamViewingConditions {
|
||||||
CompositionLocalProvider(
|
CompositionLocalProvider(
|
||||||
LocalTonalPalettes provides tonalPalettes.also { it.Preheating() },
|
LocalTonalPalettes provides tonalPalettes.also { it.Preheating() },
|
||||||
LocalUseDarkTheme provides useDarkTheme
|
LocalUseDarkTheme provides useDarkTheme,
|
||||||
) {
|
) {
|
||||||
MaterialTheme(
|
MaterialTheme(
|
||||||
colorScheme =
|
colorScheme =
|
||||||
|
|
|
@ -31,6 +31,7 @@ fun dynamicLightColorScheme(): ColorScheme {
|
||||||
onSurface = palettes neutral 10,
|
onSurface = palettes neutral 10,
|
||||||
surfaceVariant = palettes neutralVariant 90,
|
surfaceVariant = palettes neutralVariant 90,
|
||||||
onSurfaceVariant = palettes neutralVariant 30,
|
onSurfaceVariant = palettes neutralVariant 30,
|
||||||
|
surfaceTint = palettes primary 40,
|
||||||
inverseSurface = palettes neutral 20,
|
inverseSurface = palettes neutral 20,
|
||||||
inverseOnSurface = palettes neutral 95,
|
inverseOnSurface = palettes neutral 95,
|
||||||
outline = palettes neutralVariant 50,
|
outline = palettes neutralVariant 50,
|
||||||
|
@ -60,6 +61,7 @@ fun dynamicDarkColorScheme(): ColorScheme {
|
||||||
onSurface = palettes neutral 90,
|
onSurface = palettes neutral 90,
|
||||||
surfaceVariant = palettes neutralVariant 30,
|
surfaceVariant = palettes neutralVariant 30,
|
||||||
onSurfaceVariant = palettes neutralVariant 80,
|
onSurfaceVariant = palettes neutralVariant 80,
|
||||||
|
surfaceTint = palettes primary 80,
|
||||||
inverseSurface = palettes neutral 90,
|
inverseSurface = palettes neutral 90,
|
||||||
inverseOnSurface = palettes neutral 20,
|
inverseOnSurface = palettes neutral 20,
|
||||||
outline = palettes neutralVariant 60,
|
outline = palettes neutralVariant 60,
|
||||||
|
|
|
@ -79,7 +79,7 @@
|
||||||
<string name="accounts">账户</string>
|
<string name="accounts">账户</string>
|
||||||
<string name="accounts_desc">本地、FreshRSS</string>
|
<string name="accounts_desc">本地、FreshRSS</string>
|
||||||
<string name="color_and_style">颜色和样式</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">交互</string>
|
||||||
<string name="interaction_desc">启动时、触感反馈</string>
|
<string name="interaction_desc">启动时、触感反馈</string>
|
||||||
<string name="languages">语言</string>
|
<string name="languages">语言</string>
|
||||||
|
@ -99,7 +99,7 @@
|
||||||
<string name="primary_color_hint">例如 #666666 或 666666</string>
|
<string name="primary_color_hint">例如 #666666 或 666666</string>
|
||||||
<string name="appearance">外观</string>
|
<string name="appearance">外观</string>
|
||||||
<string name="style">样式</string>
|
<string name="style">样式</string>
|
||||||
<string name="dark_theme">深色模式</string>
|
<string name="dark_theme">深色主题</string>
|
||||||
<string name="use_device_theme">跟随系统设置</string>
|
<string name="use_device_theme">跟随系统设置</string>
|
||||||
<string name="tonal_elevation">色调海拔</string>
|
<string name="tonal_elevation">色调海拔</string>
|
||||||
<string name="fonts">字体</string>
|
<string name="fonts">字体</string>
|
||||||
|
@ -124,4 +124,29 @@
|
||||||
<string name="on_start">启动时</string>
|
<string name="on_start">启动时</string>
|
||||||
<string name="initial_page">起始页面</string>
|
<string name="initial_page">起始页面</string>
|
||||||
<string name="initial_filter">起始过滤条件</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>
|
</resources>
|
|
@ -79,7 +79,7 @@
|
||||||
<string name="accounts">Accounts</string>
|
<string name="accounts">Accounts</string>
|
||||||
<string name="accounts_desc">Local, FreshRSS</string>
|
<string name="accounts_desc">Local, FreshRSS</string>
|
||||||
<string name="color_and_style">Color & style</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">Interaction</string>
|
||||||
<string name="interaction_desc">On start, haptic feedback</string>
|
<string name="interaction_desc">On start, haptic feedback</string>
|
||||||
<string name="languages">Languages</string>
|
<string name="languages">Languages</string>
|
||||||
|
@ -88,9 +88,9 @@
|
||||||
<string name="tips_and_support_desc">About, open source</string>
|
<string name="tips_and_support_desc">About, open source</string>
|
||||||
<string name="welcome">Welcome</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="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="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="wallpaper_colors">Wallpaper Colors</string>
|
||||||
<string name="no_palettes">No Palettes</string>
|
<string name="no_palettes">No Palettes</string>
|
||||||
<string name="only_android_8.1_plus">Only Android 8.1+</string>
|
<string name="only_android_8.1_plus">Only Android 8.1+</string>
|
||||||
|
@ -116,7 +116,7 @@
|
||||||
<string name="update">Update</string>
|
<string name="update">Update</string>
|
||||||
<string name="skip_this_version">Skip This Version</string>
|
<string name="skip_this_version">Skip This Version</string>
|
||||||
<string name="checking_updates">Checking for updates…</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="check_failure">Check failure</string>
|
||||||
<string name="download_failure">Download failure</string>
|
<string name="download_failure">Download failure</string>
|
||||||
<string name="rate_limit">The request rate is limited</string>
|
<string name="rate_limit">The request rate is limited</string>
|
||||||
|
@ -124,4 +124,29 @@
|
||||||
<string name="on_start">On Start</string>
|
<string name="on_start">On Start</string>
|
||||||
<string name="initial_page">Initial Page</string>
|
<string name="initial_page">Initial Page</string>
|
||||||
<string name="initial_filter">Initial Filter</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>
|
</resources>
|
Loading…
Reference in New Issue
Block a user