Add FlowPage style settings

This commit is contained in:
Ash 2022-05-01 06:28:52 +08:00
parent 0cfd63c571
commit 9bea2e8e8b
26 changed files with 1098 additions and 111 deletions

View File

@ -2,13 +2,16 @@ package me.ash.reader.data.entity
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.FiberManualRecord
import androidx.compose.material.icons.rounded.FiberManualRecord
import androidx.compose.material.icons.rounded.Star
import androidx.compose.material.icons.rounded.StarOutline
import androidx.compose.material.icons.rounded.Subject
import androidx.compose.ui.graphics.vector.ImageVector
class Filter(
var index: Int,
var icon: ImageVector,
val index: Int,
val iconOutline: ImageVector,
val iconFilled: ImageVector,
) {
fun isStarred(): Boolean = this == Starred
fun isUnread(): Boolean = this == Unread
@ -17,15 +20,18 @@ class Filter(
companion object {
val Starred = Filter(
index = 0,
icon = Icons.Rounded.StarOutline,
iconOutline = Icons.Rounded.StarOutline,
iconFilled = Icons.Rounded.Star,
)
val Unread = Filter(
index = 1,
icon = Icons.Outlined.FiberManualRecord,
iconOutline = Icons.Outlined.FiberManualRecord,
iconFilled = Icons.Rounded.FiberManualRecord,
)
val All = Filter(
index = 2,
icon = Icons.Rounded.Subject,
iconOutline = Icons.Rounded.Subject,
iconFilled = Icons.Rounded.Subject,
)
}
}

View File

@ -0,0 +1,45 @@
package me.ash.reader.data.preference
import android.content.Context
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import me.ash.reader.ui.ext.DataStoreKeys
import me.ash.reader.ui.ext.dataStore
import me.ash.reader.ui.ext.put
sealed class ArticleListDatePreference(val value: Boolean) : Preference() {
object ON : ArticleListDatePreference(true)
object OFF : ArticleListDatePreference(false)
override fun put(context: Context, scope: CoroutineScope) {
scope.launch(Dispatchers.IO) {
context.dataStore.put(
DataStoreKeys.ArticleListDate,
value
)
}
}
companion object {
val default = ON
val values = listOf(ON, OFF)
val Context.articleListDate: Flow<ArticleListDatePreference>
get() = this.dataStore.data.map {
when (it[DataStoreKeys.ArticleListDate.key]) {
true -> ON
false -> OFF
else -> default
}
}
}
}
operator fun ArticleListDatePreference.not(): ArticleListDatePreference =
when (value) {
true -> ArticleListDatePreference.OFF
false -> ArticleListDatePreference.ON
}

View File

@ -0,0 +1,45 @@
package me.ash.reader.data.preference
import android.content.Context
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import me.ash.reader.ui.ext.DataStoreKeys
import me.ash.reader.ui.ext.dataStore
import me.ash.reader.ui.ext.put
sealed class ArticleListDescPreference(val value: Boolean) : Preference() {
object ON : ArticleListDescPreference(true)
object OFF : ArticleListDescPreference(false)
override fun put(context: Context, scope: CoroutineScope) {
scope.launch(Dispatchers.IO) {
context.dataStore.put(
DataStoreKeys.ArticleListDesc,
value
)
}
}
companion object {
val default = ON
val values = listOf(ON, OFF)
val Context.articleListDesc: Flow<ArticleListDescPreference>
get() = this.dataStore.data.map {
when (it[DataStoreKeys.ArticleListDesc.key]) {
true -> ON
false -> OFF
else -> default
}
}
}
}
operator fun ArticleListDescPreference.not(): ArticleListDescPreference =
when (value) {
true -> ArticleListDescPreference.OFF
false -> ArticleListDescPreference.ON
}

View File

@ -0,0 +1,45 @@
package me.ash.reader.data.preference
import android.content.Context
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import me.ash.reader.ui.ext.DataStoreKeys
import me.ash.reader.ui.ext.dataStore
import me.ash.reader.ui.ext.put
sealed class ArticleListFeedIconPreference(val value: Boolean) : Preference() {
object ON : ArticleListFeedIconPreference(true)
object OFF : ArticleListFeedIconPreference(false)
override fun put(context: Context, scope: CoroutineScope) {
scope.launch(Dispatchers.IO) {
context.dataStore.put(
DataStoreKeys.ArticleListFeedIcon,
value
)
}
}
companion object {
val default = ON
val values = listOf(ON, OFF)
val Context.articleListFeedIcon: Flow<ArticleListFeedIconPreference>
get() = this.dataStore.data.map {
when (it[DataStoreKeys.ArticleListFeedIcon.key]) {
true -> ON
false -> OFF
else -> default
}
}
}
}
operator fun ArticleListFeedIconPreference.not(): ArticleListFeedIconPreference =
when (value) {
true -> ArticleListFeedIconPreference.OFF
false -> ArticleListFeedIconPreference.ON
}

View File

@ -0,0 +1,45 @@
package me.ash.reader.data.preference
import android.content.Context
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import me.ash.reader.ui.ext.DataStoreKeys
import me.ash.reader.ui.ext.dataStore
import me.ash.reader.ui.ext.put
sealed class ArticleListFeedNamePreference(val value: Boolean) : Preference() {
object ON : ArticleListFeedNamePreference(true)
object OFF : ArticleListFeedNamePreference(false)
override fun put(context: Context, scope: CoroutineScope) {
scope.launch(Dispatchers.IO) {
context.dataStore.put(
DataStoreKeys.ArticleListFeedName,
value
)
}
}
companion object {
val default = ON
val values = listOf(ON, OFF)
val Context.articleListFeedName: Flow<ArticleListFeedNamePreference>
get() = this.dataStore.data.map {
when (it[DataStoreKeys.ArticleListFeedName.key]) {
true -> ON
false -> OFF
else -> default
}
}
}
}
operator fun ArticleListFeedNamePreference.not(): ArticleListFeedNamePreference =
when (value) {
true -> ArticleListFeedNamePreference.OFF
false -> ArticleListFeedNamePreference.ON
}

View File

@ -0,0 +1,45 @@
package me.ash.reader.data.preference
import android.content.Context
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import me.ash.reader.ui.ext.DataStoreKeys
import me.ash.reader.ui.ext.dataStore
import me.ash.reader.ui.ext.put
sealed class ArticleListImagePreference(val value: Boolean) : Preference() {
object ON : ArticleListImagePreference(true)
object OFF : ArticleListImagePreference(false)
override fun put(context: Context, scope: CoroutineScope) {
scope.launch(Dispatchers.IO) {
context.dataStore.put(
DataStoreKeys.ArticleListImage,
value
)
}
}
companion object {
val default = ON
val values = listOf(ON, OFF)
val Context.articleListImage: Flow<ArticleListImagePreference>
get() = this.dataStore.data.map {
when (it[DataStoreKeys.ArticleListImage.key]) {
true -> ON
false -> OFF
else -> default
}
}
}
}
operator fun ArticleListImagePreference.not(): ArticleListImagePreference =
when (value) {
true -> ArticleListImagePreference.OFF
false -> ArticleListImagePreference.ON
}

View File

@ -0,0 +1,57 @@
package me.ash.reader.data.preference
import android.content.Context
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import me.ash.reader.ui.ext.DataStoreKeys
import me.ash.reader.ui.ext.dataStore
import me.ash.reader.ui.ext.put
sealed class ArticleListTonalElevationPreference(val value: Int) : Preference() {
object Level0 : ArticleListTonalElevationPreference(0)
object Level1 : ArticleListTonalElevationPreference(1)
object Level2 : ArticleListTonalElevationPreference(3)
object Level3 : ArticleListTonalElevationPreference(6)
object Level4 : ArticleListTonalElevationPreference(8)
object Level5 : ArticleListTonalElevationPreference(12)
override fun put(context: Context, scope: CoroutineScope) {
scope.launch(Dispatchers.IO) {
context.dataStore.put(
DataStoreKeys.ArticleListTonalElevation,
value
)
}
}
fun getDesc(context: Context): String =
when (this) {
Level0 -> "Level 0 (0dp)"
Level1 -> "Level 1 (1dp)"
Level2 -> "Level 2 (3dp)"
Level3 -> "Level 3 (6dp)"
Level4 -> "Level 4 (8dp)"
Level5 -> "Level 5 (12dp)"
}
companion object {
val default = Level0
val values = listOf(Level0, Level1, Level2, Level3, Level4, Level5)
val Context.articleListTonalElevation: Flow<ArticleListTonalElevationPreference>
get() = this.dataStore.data.map {
when (it[DataStoreKeys.ArticleListTonalElevation.key]) {
0 -> Level0
1 -> Level1
3 -> Level2
6 -> Level3
8 -> Level4
12 -> Level5
else -> default
}
}
}
}

View File

@ -0,0 +1,45 @@
package me.ash.reader.data.preference
import android.content.Context
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import me.ash.reader.ui.ext.DataStoreKeys
import me.ash.reader.ui.ext.dataStore
import me.ash.reader.ui.ext.put
sealed class FilterBarFilledPreference(val value: Boolean) : Preference() {
object ON : FilterBarFilledPreference(true)
object OFF : FilterBarFilledPreference(false)
override fun put(context: Context, scope: CoroutineScope) {
scope.launch(Dispatchers.IO) {
context.dataStore.put(
DataStoreKeys.FilterBarFilled,
value
)
}
}
companion object {
val default = OFF
val values = listOf(ON, OFF)
val Context.filterBarFilled: Flow<FilterBarFilledPreference>
get() = this.dataStore.data.map {
when (it[DataStoreKeys.FilterBarFilled.key]) {
true -> ON
false -> OFF
else -> default
}
}
}
}
operator fun FilterBarFilledPreference.not(): FilterBarFilledPreference =
when (value) {
true -> FilterBarFilledPreference.OFF
false -> FilterBarFilledPreference.ON
}

View File

@ -0,0 +1,29 @@
package me.ash.reader.data.preference
import android.content.Context
import android.util.Log
import androidx.compose.runtime.Immutable
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import me.ash.reader.ui.ext.DataStoreKeys
import me.ash.reader.ui.ext.dataStore
import me.ash.reader.ui.ext.put
@Immutable
object FilterBarPaddingPreference {
const val default = 0
val Context.filterBarPadding: Flow<Int>
get() = this.dataStore.data.map {
it[DataStoreKeys.FilterBarPadding.key] ?: 0
}
fun put(context: Context, scope: CoroutineScope, value: Int) {
scope.launch(Dispatchers.IO) {
context.dataStore.put(DataStoreKeys.FilterBarPadding, value)
}
}
}

View File

@ -0,0 +1,48 @@
package me.ash.reader.data.preference
import android.content.Context
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import me.ash.reader.ui.ext.DataStoreKeys
import me.ash.reader.ui.ext.dataStore
import me.ash.reader.ui.ext.put
sealed class FilterBarStylePreference(val value: Int) : Preference() {
object Icon : FilterBarStylePreference(0)
object IconLabel : FilterBarStylePreference(1)
object IconLabelOnlySelected : FilterBarStylePreference(2)
override fun put(context: Context, scope: CoroutineScope) {
scope.launch(Dispatchers.IO) {
context.dataStore.put(
DataStoreKeys.FilterBarStyle,
value
)
}
}
fun getDesc(context: Context): String =
when (this) {
Icon -> "图标"
IconLabel -> "图标 + 标签"
IconLabelOnlySelected -> "图标 + 标签(仅选中时)"
}
companion object {
val default = Icon
val values = listOf(Icon, IconLabel, IconLabelOnlySelected)
val Context.filterBarStyle: Flow<FilterBarStylePreference>
get() = this.dataStore.data.map {
when (it[DataStoreKeys.FilterBarStyle.key]) {
0 -> Icon
1 -> IconLabel
2 -> IconLabelOnlySelected
else -> default
}
}
}
}

View File

@ -0,0 +1,57 @@
package me.ash.reader.data.preference
import android.content.Context
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import me.ash.reader.ui.ext.DataStoreKeys
import me.ash.reader.ui.ext.dataStore
import me.ash.reader.ui.ext.put
sealed class FilterBarTonalElevationPreference(val value: Int) : Preference() {
object Level0 : FilterBarTonalElevationPreference(0)
object Level1 : FilterBarTonalElevationPreference(1)
object Level2 : FilterBarTonalElevationPreference(3)
object Level3 : FilterBarTonalElevationPreference(6)
object Level4 : FilterBarTonalElevationPreference(8)
object Level5 : FilterBarTonalElevationPreference(12)
override fun put(context: Context, scope: CoroutineScope) {
scope.launch(Dispatchers.IO) {
context.dataStore.put(
DataStoreKeys.FilterBarTonalElevation,
value
)
}
}
fun getDesc(context: Context): String =
when (this) {
Level0 -> "Level 0 (0dp)"
Level1 -> "Level 1 (1dp)"
Level2 -> "Level 2 (3dp)"
Level3 -> "Level 3 (6dp)"
Level4 -> "Level 4 (8dp)"
Level5 -> "Level 5 (12dp)"
}
companion object {
val default = Level0
val values = listOf(Level0, Level1, Level2, Level3, Level4, Level5)
val Context.filterBarTonalElevation: Flow<FilterBarTonalElevationPreference>
get() = this.dataStore.data.map {
when (it[DataStoreKeys.FilterBarTonalElevation.key]) {
0 -> Level0
1 -> Level1
3 -> Level2
6 -> Level3
8 -> Level4
12 -> Level5
else -> default
}
}
}
}

View File

@ -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)
}

View File

@ -33,6 +33,7 @@ val Context.currentAccountType: Int
get() = this.dataStore.get(DataStoreKeys.CurrentAccountType)!!
val Context.themeIndex: Int
get() = this.dataStore.get(DataStoreKeys.ThemeIndex) ?: 5
val Context.customPrimaryColor: String
get() = this.dataStore.get(DataStoreKeys.CustomPrimaryColor) ?: ""
val Context.initialPage: Int
@ -129,6 +130,56 @@ sealed class DataStoreKeys<T> {
get() = stringPreferencesKey("customPrimaryColor")
}
object FilterBarStyle : DataStoreKeys<Int>() {
override val key: Preferences.Key<Int>
get() = intPreferencesKey("filterBarStyle")
}
object FilterBarFilled : DataStoreKeys<Boolean>() {
override val key: Preferences.Key<Boolean>
get() = booleanPreferencesKey("filterBarFilled")
}
object FilterBarPadding : DataStoreKeys<Int>() {
override val key: Preferences.Key<Int>
get() = intPreferencesKey("filterBarPadding")
}
object FilterBarTonalElevation : DataStoreKeys<Int>() {
override val key: Preferences.Key<Int>
get() = intPreferencesKey("filterBarTonalElevation")
}
object ArticleListFeedIcon : DataStoreKeys<Boolean>() {
override val key: Preferences.Key<Boolean>
get() = booleanPreferencesKey("articleListFeedIcon")
}
object ArticleListFeedName : DataStoreKeys<Boolean>() {
override val key: Preferences.Key<Boolean>
get() = booleanPreferencesKey("articleListFeedName")
}
object ArticleListImage : DataStoreKeys<Boolean>() {
override val key: Preferences.Key<Boolean>
get() = booleanPreferencesKey("articleListImage")
}
object ArticleListDesc : DataStoreKeys<Boolean>() {
override val key: Preferences.Key<Boolean>
get() = booleanPreferencesKey("articleListDesc")
}
object ArticleListDate : DataStoreKeys<Boolean>() {
override val key: Preferences.Key<Boolean>
get() = booleanPreferencesKey("articleListDate")
}
object ArticleListTonalElevation : DataStoreKeys<Int>() {
override val key: Preferences.Key<Int>
get() = intPreferencesKey("articleListTonalElevation")
}
object InitialPage : DataStoreKeys<Int>() {
override val key: Preferences.Key<Int>
get() = intPreferencesKey("initialPage")

View File

@ -3,6 +3,7 @@ package me.ash.reader.ui.ext
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import kotlin.coroutines.CoroutineContext
@ -10,3 +11,9 @@ import kotlin.coroutines.CoroutineContext
fun <T> StateFlow<T>.collectAsStateValue(
context: CoroutineContext = Dispatchers.Default
): T = collectAsState(context).value
@Composable
fun <T : R, R> Flow<T>.collectAsStateValue(
initial: R,
context: CoroutineContext = Dispatchers.Default
): R = collectAsState(initial, context).value

View File

@ -22,6 +22,7 @@ import me.ash.reader.ui.page.home.flow.FlowPage
import me.ash.reader.ui.page.home.read.ReadPage
import me.ash.reader.ui.page.settings.SettingsPage
import me.ash.reader.ui.page.settings.color.ColorAndStyle
import me.ash.reader.ui.page.settings.color.flow.FlowPageStyle
import me.ash.reader.ui.page.settings.interaction.Interaction
import me.ash.reader.ui.page.settings.tips.TipsAndSupport
import me.ash.reader.ui.page.startup.StartupPage
@ -97,9 +98,12 @@ fun HomeEntry(
navController = navController,
startDestination = if (context.isFirstLaunch) RouteName.STARTUP else RouteName.FEEDS,
) {
// Startup
animatedComposable(route = RouteName.STARTUP) {
StartupPage(navController)
}
// Home
animatedComposable(route = RouteName.FEEDS) {
FeedsPage(navController = navController, homeViewModel = homeViewModel)
}
@ -113,15 +117,26 @@ fun HomeEntry(
animatedComposable(route = "${RouteName.READING}/{articleId}") {
ReadPage(navController = navController)
}
// Settings
animatedComposable(route = RouteName.SETTINGS) {
SettingsPage(navController)
}
// Color & Style
animatedComposable(route = RouteName.COLOR_AND_STYLE) {
ColorAndStyle(navController)
}
animatedComposable(route = RouteName.FLOW_PAGE_STYLE) {
FlowPageStyle(navController)
}
// Interaction
animatedComposable(route = RouteName.INTERACTION) {
Interaction(navController)
}
// Tips & Support
animatedComposable(route = RouteName.TIPS_AND_SUPPORT) {
TipsAndSupport(navController)
}

View File

@ -1,12 +1,24 @@
package me.ash.reader.ui.page.common
object RouteName {
// Startup
const val STARTUP = "startup"
// Home
const val FEEDS = "feeds"
const val FLOW = "flow"
const val READING = "reading"
// Settings
const val SETTINGS = "settings"
// Color & Style
const val COLOR_AND_STYLE = "color_and_style"
const val FLOW_PAGE_STYLE = "flow_page_style"
// Interaction
const val INTERACTION = "interaction"
// Tips & Support
const val TIPS_AND_SUPPORT = "tips_and_support"
}

View File

@ -1,16 +1,27 @@
package me.ash.reader.ui.page.home
import android.view.SoundEffectConstants
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.width
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
import com.google.accompanist.pager.ExperimentalPagerApi
import me.ash.reader.data.entity.Filter
import me.ash.reader.data.preference.FilterBarFilledPreference
import me.ash.reader.data.preference.FilterBarFilledPreference.Companion.filterBarFilled
import me.ash.reader.data.preference.FilterBarPaddingPreference
import me.ash.reader.data.preference.FilterBarPaddingPreference.filterBarPadding
import me.ash.reader.data.preference.FilterBarStylePreference
import me.ash.reader.data.preference.FilterBarStylePreference.Companion.filterBarStyle
import me.ash.reader.data.preference.FilterBarTonalElevationPreference
import me.ash.reader.data.preference.FilterBarTonalElevationPreference.Companion.filterBarTonalElevation
import me.ash.reader.ui.ext.collectAsStateValue
import me.ash.reader.ui.ext.getName
import me.ash.reader.ui.theme.palette.onDark
@OptIn(ExperimentalPagerApi::class)
@Composable
@ -20,51 +31,67 @@ fun FilterBar(
filterOnClick: (Filter) -> Unit = {},
) {
val view = LocalView.current
val context = LocalContext.current
val filterBarStyle =
context.filterBarStyle.collectAsStateValue(initial = FilterBarStylePreference.default)
val filterBarFilled =
context.filterBarFilled.collectAsStateValue(initial = FilterBarFilledPreference.default)
val filterBarPadding =
context.filterBarPadding.collectAsStateValue(initial = FilterBarPaddingPreference.default)
val filterBarTonalElevation =
context.filterBarTonalElevation.collectAsStateValue(initial = FilterBarTonalElevationPreference.default)
Box(
// modifier = Modifier.height(60.dp)
NavigationBar(
tonalElevation = filterBarTonalElevation.value.dp,
) {
Divider(
modifier = Modifier
.fillMaxWidth()
.height(1.dp)
.zIndex(1f),
color = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.24f)
)
NavigationBar(
// modifier = Modifier.fillMaxSize(),
// tonalElevation = 0.dp,
) {
Spacer(modifier = Modifier.width(60.dp))
listOf(
Filter.Starred,
Filter.Unread,
Filter.All,
).forEach { item ->
NavigationBarItem(
icon = {
Icon(
imageVector = item.icon,
contentDescription = item.getName()
Spacer(modifier = Modifier.width(filterBarPadding.dp))
listOf(
Filter.Starred,
Filter.Unread,
Filter.All,
).forEach { item ->
NavigationBarItem(
// modifier = Modifier.height(60.dp),
alwaysShowLabel = when (filterBarStyle) {
is FilterBarStylePreference.Icon -> false
is FilterBarStylePreference.IconLabel -> true
is FilterBarStylePreference.IconLabelOnlySelected -> false
},
icon = {
Icon(
imageVector = if (filter == item && filterBarFilled.value) {
item.iconFilled
} else {
item.iconOutline
},
contentDescription = item.getName()
)
},
label = if (filterBarStyle is FilterBarStylePreference.Icon) {
null
} else {
{
Text(
text = item.getName(),
style = MaterialTheme.typography.labelLarge
)
},
label = { Text(text = item.getName(), style = MaterialTheme.typography.labelMedium) },
selected = filter == item,
onClick = {
}
},
selected = filter == item,
onClick = {
// view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
view.playSoundEffect(SoundEffectConstants.CLICK)
filterOnClick(item)
},
colors = NavigationBarItemDefaults.colors(
view.playSoundEffect(SoundEffectConstants.CLICK)
filterOnClick(item)
},
colors = NavigationBarItemDefaults.colors(
// selectedIconColor = MaterialTheme.colorScheme.onSecondaryContainer alwaysLight true,
// unselectedIconColor = MaterialTheme.colorScheme.outline,
// selectedTextColor = MaterialTheme.colorScheme.onSurface alwaysLight true,
// unselectedTextColor = MaterialTheme.colorScheme.onSurfaceVariant,
// indicatorColor = MaterialTheme.colorScheme.primaryContainer alwaysLight true,
)
indicatorColor = MaterialTheme.colorScheme.primaryContainer onDark MaterialTheme.colorScheme.secondaryContainer,
)
}
Spacer(modifier = Modifier.width(60.dp))
)
}
Spacer(modifier = Modifier.width(filterBarPadding.dp))
}
}

View File

@ -174,7 +174,7 @@ fun FeedsPage(
Banner(
title = filterState.filter.getName(),
desc = feedsViewState.importantCount,
icon = filterState.filter.icon,
icon = filterState.filter.iconOutline,
action = {
Icon(
imageVector = Icons.Outlined.KeyboardArrowRight,
@ -241,9 +241,6 @@ fun FeedsPage(
},
bottomBar = {
FilterBar(
modifier = Modifier
.height(60.dp)
.fillMaxWidth(),
filter = filterState.filter,
filterOnClick = {
filterChange(

View File

@ -21,6 +21,13 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import me.ash.reader.R
import me.ash.reader.data.entity.ArticleWithFeed
import me.ash.reader.data.preference.*
import me.ash.reader.data.preference.ArticleListDatePreference.Companion.articleListDate
import me.ash.reader.data.preference.ArticleListDescPreference.Companion.articleListDesc
import me.ash.reader.data.preference.ArticleListFeedIconPreference.Companion.articleListFeedIcon
import me.ash.reader.data.preference.ArticleListFeedNamePreference.Companion.articleListFeedName
import me.ash.reader.data.preference.ArticleListImagePreference.Companion.articleListImage
import me.ash.reader.ui.ext.collectAsStateValue
import me.ash.reader.ui.ext.formatAsString
@Composable
@ -30,6 +37,16 @@ fun ArticleItem(
onClick: (ArticleWithFeed) -> Unit = {},
) {
val context = LocalContext.current
val articleListFeedIcon =
context.articleListFeedIcon.collectAsStateValue(initial = ArticleListFeedIconPreference.default)
val articleListFeedName =
context.articleListFeedName.collectAsStateValue(initial = ArticleListFeedNamePreference.default)
val articleListImage =
context.articleListImage.collectAsStateValue(initial = ArticleListImagePreference.default)
val articleListDesc =
context.articleListDesc.collectAsStateValue(initial = ArticleListDescPreference.default)
val articleListDate =
context.articleListDate.collectAsStateValue(initial = ArticleListDatePreference.default)
Column(
modifier = Modifier
@ -44,67 +61,84 @@ fun ArticleItem(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
Text(
modifier = Modifier
.weight(1f)
.padding(start = 30.dp),
text = articleWithFeed.feed.name,
color = MaterialTheme.colorScheme.tertiary,
style = MaterialTheme.typography.labelMedium,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
Row(
modifier = Modifier.padding(start = 6.dp),
verticalAlignment = Alignment.CenterVertically,
) {
if (articleWithFeed.article.isStarred) {
Icon(
modifier = Modifier
.size(14.dp)
.padding(end = 2.dp),
imageVector = Icons.Rounded.Star,
contentDescription = stringResource(R.string.starred),
tint = MaterialTheme.colorScheme.outline.copy(alpha = 0.7f),
// Feed name
if (articleListFeedName.value) {
Text(
modifier = Modifier
.weight(1f)
.padding(start = if (articleListFeedIcon.value) 30.dp else 0.dp),
text = articleWithFeed.feed.name,
color = MaterialTheme.colorScheme.tertiary,
style = MaterialTheme.typography.labelMedium,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
if (articleListDate.value) {
Row(
modifier = Modifier.padding(start = 6.dp),
verticalAlignment = Alignment.CenterVertically,
) {
// Starred
if (articleWithFeed.article.isStarred) {
Icon(
modifier = Modifier
.size(14.dp)
.padding(end = 2.dp),
imageVector = Icons.Rounded.Star,
contentDescription = stringResource(R.string.starred),
tint = MaterialTheme.colorScheme.outline.copy(alpha = 0.7f),
)
}
// Date
Text(
text = articleWithFeed.article.date.formatAsString(
context,
onlyHourMinute = true
),
color = MaterialTheme.colorScheme.outline.copy(alpha = 0.7f),
style = MaterialTheme.typography.labelMedium,
)
}
Text(
text = articleWithFeed.article.date.formatAsString(
context,
onlyHourMinute = true
),
color = MaterialTheme.colorScheme.outline.copy(alpha = 0.7f),
style = MaterialTheme.typography.labelMedium,
)
}
}
Row(
modifier = Modifier.fillMaxWidth(),
) {
Row(
modifier = Modifier
.size(20.dp)
.clip(CircleShape)
.background(MaterialTheme.colorScheme.outline.copy(alpha = 0.2f))
) {}
Spacer(modifier = Modifier.width(10.dp))
// Feed icon
if (articleListFeedIcon.value) {
Row(
modifier = Modifier
.size(20.dp)
.clip(CircleShape)
.background(MaterialTheme.colorScheme.outline.copy(alpha = 0.2f))
) {}
Spacer(modifier = Modifier.width(10.dp))
}
// Article
Column(
modifier = Modifier.fillMaxWidth(),
) {
// Title
Text(
text = articleWithFeed.article.title,
color = MaterialTheme.colorScheme.onSurface,
style = MaterialTheme.typography.titleMedium,
maxLines = 2,
overflow = TextOverflow.Ellipsis,
)
Text(
text = articleWithFeed.article.shortDescription,
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f),
style = MaterialTheme.typography.bodySmall,
maxLines = 2,
maxLines = if (articleListDesc.value) 2 else 4,
overflow = TextOverflow.Ellipsis,
)
// Description
if (articleListDesc.value) {
Text(
text = articleWithFeed.article.shortDescription,
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f),
style = MaterialTheme.typography.bodySmall,
maxLines = 2,
overflow = TextOverflow.Ellipsis,
)
}
}
}
}

View File

@ -13,6 +13,8 @@ import me.ash.reader.data.entity.ArticleWithFeed
@OptIn(ExperimentalFoundationApi::class)
fun LazyListScope.ArticleList(
pagingItems: LazyPagingItems<FlowItemView>,
articleListFeedIcon: Boolean,
articleListTonalElevation: Int,
onClick: (ArticleWithFeed) -> Unit = {},
) {
for (index in 0 until pagingItems.itemCount) {
@ -30,7 +32,7 @@ fun LazyListScope.ArticleList(
val separator = pagingItems[index] as FlowItemView.Date
if (separator.showSpacer) item { Spacer(modifier = Modifier.height(40.dp)) }
stickyHeader {
StickyHeader(separator.date)
StickyHeader(separator.date, articleListFeedIcon, articleListTonalElevation)
}
}
else -> {}

View File

@ -15,6 +15,7 @@ import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.stringResource
@ -26,6 +27,10 @@ import androidx.paging.compose.LazyPagingItems
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import me.ash.reader.R
import me.ash.reader.data.preference.ArticleListFeedIconPreference
import me.ash.reader.data.preference.ArticleListFeedIconPreference.Companion.articleListFeedIcon
import me.ash.reader.data.preference.ArticleListTonalElevationPreference
import me.ash.reader.data.preference.ArticleListTonalElevationPreference.Companion.articleListTonalElevation
import me.ash.reader.data.repository.SyncWorker.Companion.getIsSyncing
import me.ash.reader.ui.component.DisplayText
import me.ash.reader.ui.component.FeedbackIconButton
@ -52,6 +57,7 @@ fun FlowPage(
homeViewModel: HomeViewModel,
pagingItems: LazyPagingItems<FlowItemView>,
) {
val context = LocalContext.current
val keyboardController = LocalSoftwareKeyboardController.current
val scope = rememberCoroutineScope()
@ -64,6 +70,11 @@ fun FlowPage(
val homeViewState = homeViewModel.viewState.collectAsStateValue()
val listState = if (pagingItems.itemCount > 0) viewState.listState else rememberLazyListState()
val articleListTonalElevation =
context.articleListTonalElevation.collectAsStateValue(initial = ArticleListTonalElevationPreference.default)
val articleListFeedIcon =
context.articleListFeedIcon.collectAsStateValue(initial = ArticleListFeedIconPreference.default)
val owner = LocalLifecycleOwner.current
var isSyncing by remember { mutableStateOf(false) }
homeViewModel.syncWorkLiveData.observe(owner) {
@ -98,15 +109,17 @@ fun FlowPage(
Scaffold(
modifier = Modifier
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp))
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(articleListTonalElevation.value.dp))
.statusBarsPadding()
.navigationBarsPadding(),
containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp),
containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(articleListTonalElevation.value.dp),
topBar = {
SmallTopAppBar(
title = {},
colors = TopAppBarDefaults.smallTopAppBarColors(
containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp),
containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(
articleListTonalElevation.value.dp
),
),
navigationIcon = {
FeedbackIconButton(
@ -115,7 +128,7 @@ fun FlowPage(
tint = MaterialTheme.colorScheme.onSurface
) {
onSearch = false
if(navController.previousBackStackEntry == null) {
if (navController.previousBackStackEntry == null) {
navController.navigate(RouteName.FEEDS) {
launchSingleTop = true
}
@ -184,7 +197,7 @@ fun FlowPage(
state = listState,
) {
item {
DisplayTextHeader(filterState, isSyncing)
DisplayTextHeader(filterState, isSyncing, articleListFeedIcon.value)
AnimatedVisibility(
visible = markAsRead,
enter = fadeIn() + expandVertically(),
@ -246,6 +259,8 @@ fun FlowPage(
}
ArticleList(
pagingItems = pagingItems,
articleListFeedIcon = articleListFeedIcon.value,
articleListTonalElevation = articleListTonalElevation.value,
) {
onSearch = false
navController.navigate("${RouteName.READING}/${it.article.id}") {
@ -263,9 +278,6 @@ fun FlowPage(
},
bottomBar = {
FilterBar(
modifier = Modifier
.height(60.dp)
.fillMaxWidth(),
filter = filterState.filter,
filterOnClick = {
flowViewModel.dispatch(FlowViewAction.ScrollToItem(0))
@ -280,10 +292,11 @@ fun FlowPage(
@Composable
private fun DisplayTextHeader(
filterState: FilterState,
isSyncing: Boolean
isSyncing: Boolean,
articleListFeedIcon: Boolean,
) {
DisplayText(
modifier = Modifier.padding(start = 30.dp),
modifier = Modifier.padding(start = if (articleListFeedIcon) 30.dp else 0.dp),
text = when {
filterState.group != null -> filterState.group.name
filterState.feed != null -> filterState.feed.name

View File

@ -13,16 +13,20 @@ import androidx.compose.ui.unit.dp
import me.ash.reader.ui.ext.surfaceColorAtElevation
@Composable
fun StickyHeader(currentItemDay: String) {
fun StickyHeader(
currentItemDay: String,
articleListFeedIcon: Boolean,
articleListTonalElevation: Int,
) {
Row(
modifier = Modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp)),
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(articleListTonalElevation.dp)),
verticalAlignment = Alignment.CenterVertically
) {
Text(
modifier = Modifier
.padding(start = if (true) 54.dp else 24.dp, bottom = 4.dp),
.padding(start = if (articleListFeedIcon) 54.dp else 24.dp, bottom = 4.dp),
text = currentItemDay,
color = MaterialTheme.colorScheme.primary,
style = MaterialTheme.typography.labelLarge,

View File

@ -30,6 +30,7 @@ import kotlinx.coroutines.launch
import me.ash.reader.R
import me.ash.reader.ui.component.*
import me.ash.reader.ui.ext.*
import me.ash.reader.ui.page.common.RouteName
import me.ash.reader.ui.page.settings.SettingItem
import me.ash.reader.ui.svg.PALETTE
import me.ash.reader.ui.svg.SVGString
@ -167,8 +168,11 @@ fun ColorAndStyle(
) {}
SettingItem(
title = stringResource(R.string.flow_page),
enable = false,
onClick = {},
onClick = {
navController.navigate(RouteName.FLOW_PAGE_STYLE) {
launchSingleTop = true
}
},
) {}
SettingItem(
title = stringResource(R.string.reading_page),

View File

@ -0,0 +1,346 @@
package me.ash.reader.ui.page.settings.color.flow
import android.annotation.SuppressLint
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.ArrowBack
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController
import me.ash.reader.R
import me.ash.reader.data.entity.Article
import me.ash.reader.data.entity.ArticleWithFeed
import me.ash.reader.data.entity.Feed
import me.ash.reader.data.entity.Filter
import me.ash.reader.data.preference.*
import me.ash.reader.data.preference.ArticleListDatePreference.Companion.articleListDate
import me.ash.reader.data.preference.ArticleListDescPreference.Companion.articleListDesc
import me.ash.reader.data.preference.ArticleListFeedIconPreference.Companion.articleListFeedIcon
import me.ash.reader.data.preference.ArticleListFeedNamePreference.Companion.articleListFeedName
import me.ash.reader.data.preference.ArticleListImagePreference.Companion.articleListImage
import me.ash.reader.data.preference.ArticleListTonalElevationPreference.Companion.articleListTonalElevation
import me.ash.reader.data.preference.FilterBarFilledPreference.Companion.filterBarFilled
import me.ash.reader.data.preference.FilterBarPaddingPreference.filterBarPadding
import me.ash.reader.data.preference.FilterBarStylePreference.Companion.filterBarStyle
import me.ash.reader.data.preference.FilterBarTonalElevationPreference.Companion.filterBarTonalElevation
import me.ash.reader.ui.component.*
import me.ash.reader.ui.ext.collectAsStateValue
import me.ash.reader.ui.ext.surfaceColorAtElevation
import me.ash.reader.ui.page.home.FilterBar
import me.ash.reader.ui.page.home.flow.ArticleItem
import me.ash.reader.ui.page.settings.SettingItem
import me.ash.reader.ui.theme.palette.onLight
import java.util.*
@SuppressLint("FlowOperatorInvokedInComposition")
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun FlowPageStyle(
navController: NavHostController,
) {
val context = LocalContext.current
val scope = rememberCoroutineScope()
val filterBarStyle =
context.filterBarStyle.collectAsStateValue(initial = FilterBarStylePreference.default)
val filterBarFilled =
context.filterBarFilled.collectAsStateValue(initial = FilterBarFilledPreference.default)
val filterBarPadding =
context.filterBarPadding.collectAsStateValue(initial = FilterBarPaddingPreference.default)
val filterBarTonalElevation =
context.filterBarTonalElevation.collectAsStateValue(initial = FilterBarTonalElevationPreference.default)
val articleListFeedIcon =
context.articleListFeedIcon.collectAsStateValue(initial = ArticleListFeedIconPreference.default)
val articleListFeedName =
context.articleListFeedName.collectAsStateValue(initial = ArticleListFeedNamePreference.default)
val articleListImage =
context.articleListImage.collectAsStateValue(initial = ArticleListImagePreference.default)
val articleListDesc =
context.articleListDesc.collectAsStateValue(initial = ArticleListDescPreference.default)
val articleListDate =
context.articleListDate.collectAsStateValue(initial = ArticleListDatePreference.default)
val articleListTonalElevation =
context.articleListTonalElevation.collectAsStateValue(initial = ArticleListTonalElevationPreference.default)
var filterBarStyleDialogVisible by remember { mutableStateOf(false) }
var filterBarPaddingDialogVisible by remember { mutableStateOf(false) }
var filterBarTonalElevationDialogVisible by remember { mutableStateOf(false) }
var articleListTonalElevationDialogVisible by remember { mutableStateOf(false) }
var filterBarPaddingValue: Int? by remember { mutableStateOf(filterBarPadding) }
Scaffold(
modifier = Modifier
.background(MaterialTheme.colorScheme.surface onLight MaterialTheme.colorScheme.inverseOnSurface)
.statusBarsPadding()
.navigationBarsPadding(),
containerColor = MaterialTheme.colorScheme.surface onLight MaterialTheme.colorScheme.inverseOnSurface,
topBar = {
SmallTopAppBar(
colors = TopAppBarDefaults.smallTopAppBarColors(
containerColor = MaterialTheme.colorScheme.surface onLight MaterialTheme.colorScheme.inverseOnSurface
),
title = {},
navigationIcon = {
FeedbackIconButton(
imageVector = Icons.Rounded.ArrowBack,
contentDescription = stringResource(R.string.back),
tint = MaterialTheme.colorScheme.onSurface
) {
navController.popBackStack()
}
},
actions = {}
)
},
content = {
LazyColumn {
item {
DisplayText(text = stringResource(R.string.flow_page), desc = "")
}
item {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 24.dp)
.clip(RoundedCornerShape(24.dp))
.background(
MaterialTheme.colorScheme.inverseOnSurface
onLight MaterialTheme.colorScheme.surface.copy(0.7f)
)
.clickable { },
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically
) {
FeedsPagePreview(articleListTonalElevation)
}
Spacer(modifier = Modifier.height(24.dp))
}
item {
Subtitle(
modifier = Modifier.padding(horizontal = 24.dp),
text = "过滤栏",
)
SettingItem(
title = "样式",
desc = filterBarStyle.getDesc(context),
onClick = {
filterBarStyleDialogVisible = true
},
) {}
SettingItem(
title = "填充已选中的图标",
onClick = {
(!filterBarFilled).put(context, scope)
},
) {
Switch(activated = filterBarFilled.value) {
(!filterBarFilled).put(context, scope)
}
}
SettingItem(
title = "两端边距",
desc = "${filterBarPadding}dp",
onClick = {
filterBarPaddingDialogVisible = true
},
) {}
SettingItem(
title = "色调海拔",
desc = "${filterBarTonalElevation.value}dp",
onClick = {
filterBarTonalElevationDialogVisible = true
},
) {}
Spacer(modifier = Modifier.height(24.dp))
}
item {
Subtitle(
modifier = Modifier.padding(horizontal = 24.dp),
text = "标题栏"
)
SettingItem(
title = "“标记为已读”按钮的位置",
desc = "顶部",
onClick = {},
) {}
Spacer(modifier = Modifier.height(24.dp))
}
item {
Subtitle(
modifier = Modifier.padding(horizontal = 24.dp),
text = "文章列表"
)
SettingItem(
title = "显示订阅源图标",
onClick = {
(!articleListFeedIcon).put(context, scope)
},
) {
Switch(activated = articleListFeedIcon.value) {
(!articleListFeedIcon).put(context, scope)
}
}
SettingItem(
title = "显示订阅源名称",
onClick = {
(!articleListFeedName).put(context, scope)
},
) {
Switch(activated = articleListFeedName.value) {
(!articleListFeedName).put(context, scope)
}
}
SettingItem(
title = "显示文章插图",
onClick = {},
) {
Switch(activated = false, enable = false)
}
SettingItem(
title = "显示文章描述",
onClick = {
(!articleListDesc).put(context, scope)
},
) {
Switch(activated = articleListDesc.value) {
(!articleListDesc).put(context, scope)
}
}
SettingItem(
title = "显示文章发布时间",
onClick = {
(!articleListDate).put(context, scope)
},
) {
Switch(activated = articleListDate.value) {
(!articleListDate).put(context, scope)
}
}
SettingItem(
title = "色调海拔",
desc = "${articleListTonalElevation.value}dp",
onClick = {
articleListTonalElevationDialogVisible = true
},
) {}
Spacer(modifier = Modifier.height(24.dp))
}
}
}
)
RadioDialog(
visible = filterBarStyleDialogVisible,
title = stringResource(R.string.initial_filter),
options = FilterBarStylePreference.values.map {
RadioDialogOption(
text = it.getDesc(context),
selected = filterBarStyle == it,
) {
it.put(context, scope)
}
}
) {
filterBarStyleDialogVisible = false
}
TextFieldDialog(
visible = filterBarPaddingDialogVisible,
title = "两端边距",
value = (filterBarPaddingValue ?: "").toString(),
placeholder = stringResource(R.string.name),
onValueChange = {
filterBarPaddingValue = it.toIntOrNull()
},
onDismissRequest = {
filterBarPaddingDialogVisible = false
},
onConfirm = {
FilterBarPaddingPreference.put(context, scope, filterBarPaddingValue ?: 0)
filterBarPaddingDialogVisible = false
}
)
RadioDialog(
visible = filterBarTonalElevationDialogVisible,
title = stringResource(R.string.tonal_elevation),
options = FilterBarTonalElevationPreference.values.map {
RadioDialogOption(
text = it.getDesc(context),
selected = filterBarTonalElevation == it,
) {
it.put(context, scope)
}
}
) {
filterBarTonalElevationDialogVisible = false
}
RadioDialog(
visible = articleListTonalElevationDialogVisible,
title = stringResource(R.string.tonal_elevation),
options = ArticleListTonalElevationPreference.values.map {
RadioDialogOption(
text = it.getDesc(context),
selected = articleListTonalElevation == it,
) {
it.put(context, scope)
}
}
) {
articleListTonalElevationDialogVisible = false
}
}
@Composable
fun FeedsPagePreview(
articleListTonalElevation: ArticleListTonalElevationPreference,
) {
var filter by remember { mutableStateOf(Filter.Unread) }
Column(
modifier = Modifier
.background(
color = MaterialTheme.colorScheme.surfaceColorAtElevation(articleListTonalElevation.value.dp),
shape = RoundedCornerShape(24.dp)
)
) {
Spacer(modifier = Modifier.height(12.dp))
ArticleItem(
articleWithFeed = ArticleWithFeed(
Article(
id = "",
title = "《黎明之剑》撒花完结,白金远瞳的“希灵三部曲”值得你通宵阅读",
shortDescription = "昨日在找书的时候无意间发现,“远瞳”的《黎明之剑》突然冲上了起点热搜榜首位,去小说中查找原因,原来是这部六百多万字的作品已经完结了。四年的时间,这部小说始终占据科幻分类前三甲的位置,不得不说“远瞳”的实力的确不容小觑。",
rawDescription = "昨日在找书的时候无意间发现,“远瞳”的《黎明之剑》突然冲上了起点热搜榜首位,去小说中查找原因,原来是这部六百多万字的作品已经完结了。四年的时间,这部小说始终占据科幻分类前三甲的位置,不得不说“远瞳”的实力的确不容小觑。",
link = "",
feedId = "",
accountId = 0,
date = Date(),
),
feed = Feed(
id = "",
name = "佛门射手",
icon = "",
accountId = 0,
groupId = "",
url = "",
)
)
)
Spacer(modifier = Modifier.height(12.dp))
FilterBar(modifier = Modifier.padding(horizontal = 12.dp), filter = filter) {
filter = it
}
}
}

View File

@ -79,7 +79,7 @@
<string name="accounts">账户</string>
<string name="accounts_desc">本地、FreshRSS</string>
<string name="color_and_style">颜色和样式</string>
<string name="color_and_style_desc">主题、色彩系统、字体大小</string>
<string name="color_and_style_desc">主题、色调样式、字体大小</string>
<string name="interaction">交互</string>
<string name="interaction_desc">启动时、触感反馈</string>
<string name="languages">语言</string>

View File

@ -79,7 +79,7 @@
<string name="accounts">Accounts</string>
<string name="accounts_desc">Local, FreshRSS</string>
<string name="color_and_style">Color &amp; style</string>
<string name="color_and_style_desc">Theme, color system, font size</string>
<string name="color_and_style_desc">Theme, color style, font size</string>
<string name="interaction">Interaction</string>
<string name="interaction_desc">On start, haptic feedback</string>
<string name="languages">Languages</string>