Add initial page and initial filter settings

This commit is contained in:
Ash 2022-04-29 06:00:59 +08:00
parent e7332c606b
commit 86bd1b467c
17 changed files with 367 additions and 49 deletions

View File

@ -7,11 +7,11 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@Composable
fun BlockButtonRadios(
fun BlockRadioGroupButton(
modifier: Modifier = Modifier,
selected: Int = 0,
onSelected: (Int) -> Unit,
items: List<BlockButtonRadiosItem> = listOf(),
itemRadioGroups: List<BlockRadioGroupButtonItem> = listOf(),
) {
Column {
@ -20,11 +20,11 @@ fun BlockButtonRadios(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
items.forEachIndexed { index, item ->
itemRadioGroups.forEachIndexed { index, item ->
BlockButton(
modifier = Modifier
.weight(1f)
.padding(end = if (item == items.last()) 0.dp else 8.dp),
.padding(end = if (item == itemRadioGroups.last()) 0.dp else 8.dp),
text = item.text,
selected = selected == index,
) {
@ -34,11 +34,11 @@ fun BlockButtonRadios(
}
}
Spacer(modifier = Modifier.height(24.dp))
items[selected].content()
itemRadioGroups[selected].content()
}
}
data class BlockButtonRadiosItem(
data class BlockRadioGroupButtonItem(
val text: String,
val onClick: () -> Unit = {},
val content: @Composable () -> Unit,

View File

@ -0,0 +1,84 @@
package me.ash.reader.ui.component
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.text.style.BaselineShift
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.google.accompanist.pager.ExperimentalPagerApi
@OptIn(ExperimentalPagerApi::class, ExperimentalMaterial3Api::class)
@Composable
fun RadioDialog(
modifier: Modifier = Modifier,
visible: Boolean = false,
title: String = "",
options: List<RadioDialogOption> = emptyList(),
onDismissRequest: () -> Unit = {},
) {
Dialog(
modifier = modifier,
visible = visible,
onDismissRequest = onDismissRequest,
title = {
Text(
text = title,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
style = MaterialTheme.typography.titleLarge,
)
},
text = {
LazyColumn {
items(options) { option ->
Row(
modifier = Modifier
.fillMaxWidth()
.clip(CircleShape)
.clickable {
option.onClick()
onDismissRequest()
},
verticalAlignment = Alignment.CenterVertically,
) {
RadioButton(selected = option.selected, onClick = {
option.onClick()
onDismissRequest()
})
Text(
modifier = Modifier.padding(start = 6.dp),
text = option.text,
style = MaterialTheme.typography.bodyLarge.copy(
baselineShift = BaselineShift.None
),
color = MaterialTheme.colorScheme.onSurface,
)
}
}
}
},
confirmButton = {},
dismissButton = {},
)
}
@Immutable
data class RadioDialogOption(
val text: String = "",
val selected: Boolean = false,
val onClick: () -> Unit = {},
)

View File

@ -35,6 +35,10 @@ val Context.themeIndex: Int
get() = this.dataStore.get(DataStoreKeys.ThemeIndex) ?: 5
val Context.customPrimaryColor: String
get() = this.dataStore.get(DataStoreKeys.CustomPrimaryColor) ?: ""
val Context.initialPage: Int
get() = this.dataStore.get(DataStoreKeys.InitialPage) ?: 0
val Context.initialFilter: Int
get() = this.dataStore.get(DataStoreKeys.InitialFilter) ?: 2
suspend fun <T> DataStore<Preferences>.put(dataStoreKeys: DataStoreKeys<T>, value: T) {
this.edit {
@ -124,4 +128,14 @@ sealed class DataStoreKeys<T> {
override val key: Preferences.Key<String>
get() = stringPreferencesKey("customPrimaryColor")
}
object InitialPage : DataStoreKeys<Int>() {
override val key: Preferences.Key<Int>
get() = intPreferencesKey("initialPage")
}
object InitialFilter : DataStoreKeys<Int>() {
override val key: Preferences.Key<Int>
get() = intPreferencesKey("initialFilter")
}
}

View File

@ -13,17 +13,17 @@ import androidx.paging.compose.collectAsLazyPagingItems
import com.google.accompanist.navigation.animation.AnimatedNavHost
import com.google.accompanist.navigation.animation.rememberAnimatedNavController
import com.google.accompanist.systemuicontroller.rememberSystemUiController
import me.ash.reader.ui.ext.animatedComposable
import me.ash.reader.ui.ext.collectAsStateValue
import me.ash.reader.ui.ext.findActivity
import me.ash.reader.ui.ext.isFirstLaunch
import me.ash.reader.data.entity.Filter
import me.ash.reader.ui.ext.*
import me.ash.reader.ui.page.home.HomeViewAction
import me.ash.reader.ui.page.home.HomeViewModel
import me.ash.reader.ui.page.home.feeds.FeedsPage
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.ColorAndStyle
import me.ash.reader.ui.page.settings.SettingsPage
import me.ash.reader.ui.page.settings.TipsAndSupport
import me.ash.reader.ui.page.settings.color.ColorAndStyle
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
import me.ash.reader.ui.theme.AppTheme
import me.ash.reader.ui.theme.LocalUseDarkTheme
@ -33,12 +33,11 @@ import me.ash.reader.ui.theme.LocalUseDarkTheme
fun HomeEntry(
homeViewModel: HomeViewModel = hiltViewModel(),
) {
val context = LocalContext.current
val viewState = homeViewModel.viewState.collectAsStateValue()
val filterState = homeViewModel.filterState.collectAsStateValue()
val pagingItems = viewState.pagingData.collectAsLazyPagingItems()
AppTheme {
val context = LocalContext.current
val useDarkTheme = LocalUseDarkTheme.current
val navController = rememberAnimatedNavController()
val intent by rememberSaveable { mutableStateOf(context.findActivity()?.intent) }
@ -48,15 +47,45 @@ fun HomeEntry(
intent?.replaceExtras(null)
}
LaunchedEffect(Unit) {
when (context.initialPage) {
1 -> {
navController.navigate(RouteName.FLOW) {
launchSingleTop = true
}
}
// Other initial pages
}
homeViewModel.dispatch(
HomeViewAction.ChangeFilter(
filterState.copy(
filter = when (context.initialFilter) {
0 -> Filter.Starred
1 -> Filter.Unread
2 -> Filter.All
else -> Filter.All
}
)
)
)
}
LaunchedEffect(openArticleId) {
if (openArticleId.isNotEmpty()) {
navController.navigate(RouteName.FLOW) {
launchSingleTop = true
}
navController.navigate("${RouteName.READING}/${openArticleId}") {
popUpTo(RouteName.FEEDS)
launchSingleTop = true
}
openArticleId = ""
}
}
AppTheme {
val useDarkTheme = LocalUseDarkTheme.current
rememberSystemUiController().run {
setStatusBarColor(Color.Transparent, !useDarkTheme)
setSystemBarsColor(Color.Transparent, !useDarkTheme)
@ -90,6 +119,9 @@ fun HomeEntry(
animatedComposable(route = RouteName.COLOR_AND_STYLE) {
ColorAndStyle(navController)
}
animatedComposable(route = RouteName.INTERACTION) {
Interaction(navController)
}
animatedComposable(route = RouteName.TIPS_AND_SUPPORT) {
TipsAndSupport(navController)
}

View File

@ -7,5 +7,6 @@ object RouteName {
const val READING = "reading"
const val SETTINGS = "settings"
const val COLOR_AND_STYLE = "color_and_style"
const val INTERACTION = "interaction"
const val TIPS_AND_SUPPORT = "tips_and_support"
}

View File

@ -4,7 +4,6 @@ import androidx.lifecycle.ViewModel
import androidx.paging.*
import androidx.work.WorkManager
import com.google.accompanist.pager.ExperimentalPagerApi
import com.google.accompanist.pager.PagerState
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.*
@ -25,7 +24,7 @@ class HomeViewModel @Inject constructor(
private val stringsRepository: StringsRepository,
@ApplicationScope
private val applicationScope: CoroutineScope,
workManager: WorkManager,
private val workManager: WorkManager,
) : ViewModel() {
private val _viewState = MutableStateFlow(HomeViewState())
@ -115,7 +114,6 @@ data class FilterState(
@OptIn(ExperimentalPagerApi::class)
data class HomeViewState(
val pagerState: PagerState = PagerState(0),
val pagingData: Flow<PagingData<FlowItemView>> = emptyFlow(),
val searchContent: String = "",
)

View File

@ -132,7 +132,7 @@ fun FeedsPage(
showBadge = latestVersion.whetherNeedUpdate(currentVersion, skipVersion),
) {
navController.navigate(RouteName.SETTINGS) {
popUpTo(RouteName.FEEDS)
launchSingleTop = true
}
}
},
@ -271,7 +271,7 @@ private fun filterChange(
homeViewModel.dispatch(HomeViewAction.ChangeFilter(filterState))
if (isNavigate) {
navController.navigate(RouteName.FLOW) {
popUpTo(RouteName.FEEDS)
launchSingleTop = true
}
}
}

View File

@ -113,8 +113,14 @@ fun FlowPage(
tint = MaterialTheme.colorScheme.onSurface
) {
onSearch = false
if(navController.previousBackStackEntry == null) {
navController.navigate(RouteName.FEEDS) {
launchSingleTop = true
}
} else {
navController.popBackStack()
}
}
},
actions = {
AnimatedVisibility(
@ -241,7 +247,7 @@ fun FlowPage(
) {
onSearch = false
navController.navigate("${RouteName.READING}/${it.article.id}") {
popUpTo(RouteName.FLOW)
launchSingleTop = true
}
}
item {

View File

@ -27,6 +27,9 @@ import me.ash.reader.ui.ext.DataStoreKeys
import me.ash.reader.ui.ext.dataStore
import me.ash.reader.ui.ext.getCurrentVersion
import me.ash.reader.ui.page.common.RouteName
import me.ash.reader.ui.page.settings.tips.UpdateDialog
import me.ash.reader.ui.page.settings.tips.UpdateViewAction
import me.ash.reader.ui.page.settings.tips.UpdateViewModel
import me.ash.reader.ui.theme.palette.onLight
@SuppressLint("FlowOperatorInvokedInComposition")
@ -120,7 +123,7 @@ fun SettingsPage(
icon = Icons.Outlined.Palette,
) {
navController.navigate(RouteName.COLOR_AND_STYLE) {
popUpTo(RouteName.SETTINGS)
launchSingleTop = true
}
}
}
@ -129,8 +132,11 @@ fun SettingsPage(
title = stringResource(R.string.interaction),
desc = stringResource(R.string.interaction_desc),
icon = Icons.Outlined.TouchApp,
enable = false,
) {}
) {
navController.navigate(RouteName.INTERACTION) {
launchSingleTop = true
}
}
}
item {
SelectableSettingGroupItem(
@ -147,7 +153,7 @@ fun SettingsPage(
icon = Icons.Outlined.TipsAndUpdates,
) {
navController.navigate(RouteName.TIPS_AND_SUPPORT) {
popUpTo(RouteName.SETTINGS)
launchSingleTop = true
}
}
}

View File

@ -1,4 +1,4 @@
package me.ash.reader.ui.page.settings
package me.ash.reader.ui.page.settings.color
import android.annotation.SuppressLint
import android.os.Build
@ -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.settings.SettingItem
import me.ash.reader.ui.svg.PALETTE
import me.ash.reader.ui.svg.SVGString
import me.ash.reader.ui.theme.LocalUseDarkTheme
@ -101,11 +102,11 @@ fun ColorAndStyle(
Spacer(modifier = Modifier.height(24.dp))
}
item {
BlockButtonRadios(
BlockRadioGroupButton(
selected = radioButtonSelected,
onSelected = { radioButtonSelected = it },
items = listOf(
BlockButtonRadiosItem(
itemRadioGroups = listOf(
BlockRadioGroupButtonItem(
text = stringResource(R.string.wallpaper_colors),
onClick = {},
) {
@ -120,7 +121,7 @@ fun ColorAndStyle(
themeIndexPrefix = 5,
)
},
BlockButtonRadiosItem(
BlockRadioGroupButtonItem(
text = stringResource(R.string.basic_colors),
onClick = {},
) {

View File

@ -0,0 +1,168 @@
package me.ash.reader.ui.page.settings.interaction
import android.annotation.SuppressLint
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.ArrowBack
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import me.ash.reader.R
import me.ash.reader.ui.component.*
import me.ash.reader.ui.ext.DataStoreKeys
import me.ash.reader.ui.ext.dataStore
import me.ash.reader.ui.ext.put
import me.ash.reader.ui.page.settings.SettingItem
import me.ash.reader.ui.theme.palette.onLight
@SuppressLint("FlowOperatorInvokedInComposition")
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun Interaction(
navController: NavHostController,
) {
val context = LocalContext.current
val view = LocalView.current
val scope = rememberCoroutineScope()
var initialPageDialogVisible by remember { mutableStateOf(false) }
var initialFilterDialogVisible by remember { mutableStateOf(false) }
val initialPage = context.dataStore.data
.map { it[DataStoreKeys.InitialPage.key] ?: 0 }
.collectAsState(initial = 0).value
val initialFilter = context.dataStore.data
.map { it[DataStoreKeys.InitialFilter.key] ?: 2 }
.collectAsState(initial = 2).value
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.interaction), desc = "")
Spacer(modifier = Modifier.height(16.dp))
}
item {
Subtitle(
modifier = Modifier.padding(horizontal = 24.dp),
text = stringResource(R.string.on_start),
)
SettingItem(
title = stringResource(R.string.initial_page),
desc = when (initialPage) {
0 -> stringResource(R.string.feeds_page)
1 -> stringResource(R.string.flow_page)
else -> ""
},
onClick = {
initialPageDialogVisible = true
},
) {}
SettingItem(
title = stringResource(R.string.initial_filter),
desc = when (initialFilter) {
0 -> stringResource(R.string.starred)
1 -> stringResource(R.string.unread)
2 -> stringResource(R.string.all)
else -> ""
},
onClick = {
initialFilterDialogVisible = true
},
) {}
}
}
}
)
RadioDialog(
visible = initialPageDialogVisible,
title = stringResource(R.string.initial_page),
options = listOf(
RadioDialogOption(
text = stringResource(R.string.feeds_page),
selected = initialPage == 0,
) {
scope.launch(Dispatchers.IO) {
context.dataStore.put(DataStoreKeys.InitialPage, 0)
}
},
RadioDialogOption(
text = stringResource(R.string.flow_page),
selected = initialPage == 1,
) {
scope.launch(Dispatchers.IO) {
context.dataStore.put(DataStoreKeys.InitialPage, 1)
}
},
),
) {
initialPageDialogVisible = false
}
RadioDialog(
visible = initialFilterDialogVisible,
title = stringResource(R.string.initial_filter),
options = listOf(
RadioDialogOption(
text = stringResource(R.string.starred),
selected = initialFilter == 0,
) {
scope.launch(Dispatchers.IO) {
context.dataStore.put(DataStoreKeys.InitialFilter, 0)
}
},
RadioDialogOption(
text = stringResource(R.string.unread),
selected = initialFilter == 1,
) {
scope.launch(Dispatchers.IO) {
context.dataStore.put(DataStoreKeys.InitialFilter, 1)
}
},
RadioDialogOption(
text = stringResource(R.string.all),
selected = initialFilter == 2,
) {
scope.launch(Dispatchers.IO) {
context.dataStore.put(DataStoreKeys.InitialFilter, 2)
}
},
),
) {
initialFilterDialogVisible = false
}
}

View File

@ -1,4 +1,4 @@
package me.ash.reader.ui.page.settings
package me.ash.reader.ui.page.settings.tips
import android.content.Intent
import android.net.Uri

View File

@ -1,4 +1,4 @@
package me.ash.reader.ui.page.settings
package me.ash.reader.ui.page.settings.tips
import android.Manifest
import android.annotation.SuppressLint

View File

@ -1,4 +1,4 @@
package me.ash.reader.ui.page.settings
package me.ash.reader.ui.page.settings.tips
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope

View File

@ -102,7 +102,9 @@ fun StartupPage(
floatingActionButton = {
ExtendedFloatingActionButton(
onClick = {
navController.navigate(RouteName.FEEDS)
navController.navigate(RouteName.FEEDS) {
launchSingleTop = true
}
scope.launch {
context.dataStore.put(DataStoreKeys.IsFirstLaunch, false)
}

View File

@ -81,7 +81,7 @@
<string name="color_and_style">颜色和样式</string>
<string name="color_and_style_desc">主题、色彩系统、字体大小</string>
<string name="interaction">交互</string>
<string name="interaction_desc">布局、触感反馈</string>
<string name="interaction_desc">启动时、触感反馈</string>
<string name="languages">语言</string>
<string name="languages_desc">英语、中文</string>
<string name="tips_and_support">提示和支持</string>
@ -121,4 +121,7 @@
<string name="download_failure">下载失败</string>
<string name="rate_limit">请求速率受限</string>
<string name="help">帮助</string>
<string name="on_start">启动时</string>
<string name="initial_page">起始页面</string>
<string name="initial_filter">起始过滤条件</string>
</resources>

View File

@ -81,7 +81,7 @@
<string name="color_and_style">Color &amp; style</string>
<string name="color_and_style_desc">Theme, color system, font size</string>
<string name="interaction">Interaction</string>
<string name="interaction_desc">Layout, haptic feedback</string>
<string name="interaction_desc">On start, haptic feedback</string>
<string name="languages">Languages</string>
<string name="languages_desc">English, Chinese</string>
<string name="tips_and_support">Tips &amp; support</string>
@ -121,4 +121,7 @@
<string name="download_failure">Download failure</string>
<string name="rate_limit">The request rate is limited</string>
<string name="help">Help</string>
<string name="on_start">On Start</string>
<string name="initial_page">Initial Page</string>
<string name="initial_filter">Initial Filter</string>
</resources>