add filters settings

This commit is contained in:
Stefano Sansone
2024-04-25 12:18:09 +02:00
parent 121422e5ce
commit f510618263
14 changed files with 410 additions and 92 deletions

View File

@ -0,0 +1,128 @@
package app.omnivore.omnivore.core.designsystem.component
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.RepeatMode
import androidx.compose.animation.core.StartOffset
import androidx.compose.animation.core.StartOffsetType
import androidx.compose.animation.core.repeatable
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.sizeIn
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.runtime.structuralEqualityPolicy
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import kotlinx.coroutines.delay
import kotlin.time.Duration.Companion.seconds
val LocalPreferenceHighlighted = compositionLocalOf(structuralEqualityPolicy()) { false }
val LocalPreferenceMinHeight = compositionLocalOf(structuralEqualityPolicy()) { 56.dp }
@Composable
internal fun BasePreferenceWidget(
modifier: Modifier = Modifier,
title: String? = null,
subcomponent: @Composable (ColumnScope.() -> Unit)? = null,
icon: @Composable (() -> Unit)? = null,
onClick: (() -> Unit)? = null,
widget: @Composable (() -> Unit)? = null,
) {
val highlighted = LocalPreferenceHighlighted.current
val minHeight = LocalPreferenceMinHeight.current
Row(
modifier = modifier
.highlightBackground(highlighted)
.sizeIn(minHeight = minHeight)
.clickable(enabled = onClick != null, onClick = { onClick?.invoke() })
.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
) {
if (icon != null) {
Box(
modifier = Modifier.padding(start = PrefsHorizontalPadding, end = 8.dp),
content = { icon() },
)
}
Column(
modifier = Modifier
.weight(1f)
.padding(vertical = PrefsVerticalPadding),
) {
if (!title.isNullOrBlank()) {
Text(
modifier = Modifier.padding(horizontal = PrefsHorizontalPadding),
text = title,
overflow = TextOverflow.Ellipsis,
maxLines = 2,
style = MaterialTheme.typography.titleLarge,
fontSize = TitleFontSize,
)
}
subcomponent?.invoke(this)
}
if (widget != null) {
Box(
modifier = Modifier.padding(end = PrefsHorizontalPadding),
content = { widget() },
)
}
}
}
internal fun Modifier.highlightBackground(highlighted: Boolean): Modifier = composed {
var highlightFlag by remember { mutableStateOf(false) }
LaunchedEffect(Unit) {
if (highlighted) {
highlightFlag = true
delay(3.seconds)
highlightFlag = false
}
}
val highlight by animateColorAsState(
targetValue = if (highlightFlag) {
MaterialTheme.colorScheme.surfaceTint.copy(alpha = .12f)
} else {
Color.Transparent
},
animationSpec = if (highlightFlag) {
repeatable(
iterations = 5,
animation = tween(durationMillis = 200),
repeatMode = RepeatMode.Reverse,
initialStartOffset = StartOffset(
offsetMillis = 600,
offsetType = StartOffsetType.Delay,
),
)
} else {
tween(200)
},
label = "highlight",
)
Modifier.background(color = highlight)
}
internal val TrailingWidgetBuffer = 16.dp
internal val PrefsHorizontalPadding = 16.dp
internal val PrefsVerticalPadding = 16.dp
internal val TitleFontSize = 16.sp

View File

@ -0,0 +1,69 @@
package app.omnivore.omnivore.core.designsystem.component
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Preview
import androidx.compose.material3.Surface
import androidx.compose.material3.Switch
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.tooling.preview.PreviewLightDark
@Composable
fun SwitchPreferenceWidget(
modifier: Modifier = Modifier,
title: String,
subtitle: String? = null,
icon: ImageVector? = null,
checked: Boolean = false,
onCheckedChanged: (Boolean) -> Unit,
) {
TextPreferenceWidget(
modifier = modifier,
title = title,
subtitle = subtitle,
icon = icon,
widget = {
Switch(
checked = checked,
onCheckedChange = null,
modifier = Modifier.padding(start = TrailingWidgetBuffer),
)
},
onPreferenceClick = { onCheckedChanged(!checked) },
)
}
@PreviewLightDark
@Composable
private fun SwitchPreferenceWidgetPreview() {
Surface {
Column {
SwitchPreferenceWidget(
title = "Text preference with icon",
subtitle = "Text preference summary",
icon = Icons.Filled.Preview,
checked = true,
onCheckedChanged = {},
)
SwitchPreferenceWidget(
title = "Text preference",
subtitle = "Text preference summary",
checked = false,
onCheckedChanged = {},
)
SwitchPreferenceWidget(
title = "Text preference no summary",
checked = false,
onCheckedChanged = {},
)
SwitchPreferenceWidget(
title = "Another text preference no summary",
checked = false,
onCheckedChanged = {},
)
}
}
}

View File

@ -0,0 +1,79 @@
package app.omnivore.omnivore.core.designsystem.component
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Preview
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.tooling.preview.PreviewLightDark
import app.omnivore.omnivore.core.designsystem.util.secondaryItemAlpha
@Composable
fun TextPreferenceWidget(
modifier: Modifier = Modifier,
title: String? = null,
subtitle: String? = null,
icon: ImageVector? = null,
iconTint: Color = MaterialTheme.colorScheme.primary,
widget: @Composable (() -> Unit)? = null,
onPreferenceClick: (() -> Unit)? = null,
) {
BasePreferenceWidget(
modifier = modifier,
title = title,
subcomponent = if (!subtitle.isNullOrBlank()) {
{
Text(
text = subtitle,
modifier = Modifier
.padding(horizontal = PrefsHorizontalPadding)
.secondaryItemAlpha(),
style = MaterialTheme.typography.bodySmall,
maxLines = 10,
)
}
} else {
null
},
icon = if (icon != null) {
{
Icon(
imageVector = icon,
tint = iconTint,
contentDescription = null,
)
}
} else {
null
},
onClick = onPreferenceClick,
widget = widget,
)
}
@PreviewLightDark
@Composable
private fun TextPreferenceWidgetPreview() {
Surface {
Column {
TextPreferenceWidget(
title = "Text preference with icon",
subtitle = "Text preference summary",
icon = Icons.Filled.Preview,
onPreferenceClick = {},
)
TextPreferenceWidget(
title = "Text preference",
subtitle = "Text preference summary",
onPreferenceClick = {},
)
}
}
}

View File

@ -0,0 +1,3 @@
package app.omnivore.omnivore.core.designsystem.util
const val SecondaryItemAlpha = .78f

View File

@ -0,0 +1,6 @@
package app.omnivore.omnivore.core.designsystem.util
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
fun Modifier.secondaryItemAlpha(): Modifier = this.alpha(SecondaryItemAlpha)

View File

@ -1,14 +1,10 @@
package app.omnivore.omnivore.feature.profile
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
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.rememberLazyListState
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
@ -17,18 +13,15 @@ import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavHostController
import app.omnivore.omnivore.R
import app.omnivore.omnivore.core.designsystem.component.TextPreferenceWidget
import app.omnivore.omnivore.feature.auth.LoginViewModel
import app.omnivore.omnivore.navigation.Routes
internal const val RELEASE_URL = "https://github.com/omnivore-app/omnivore/releases"
@OptIn(ExperimentalMaterial3Api::class)
@Composable
internal fun SettingsScreen(
@ -67,23 +60,33 @@ fun SettingsViewContent(
) {
item {
SettingRow(title = stringResource(R.string.about_manage_account)) {
navController.navigate(Routes.Account.route)
}
TextPreferenceWidget(
title = stringResource(R.string.profile_filters),
onPreferenceClick = { navController.navigate(Routes.Filters.route) },
)
}
item { HorizontalDivider() }
item {
TextPreferenceWidget(
title = stringResource(R.string.profile_manage_account),
onPreferenceClick = { navController.navigate(Routes.Account.route) },
)
}
item {
SettingRow(
title = stringResource(R.string.about_logout)
) {
showLogoutDialog.value = true
}
TextPreferenceWidget(
title = stringResource(R.string.about_logout),
onPreferenceClick = { showLogoutDialog.value = true },
)
}
item {
SettingRow(title = stringResource(R.string.about_view_title)) {
navController.navigate(Routes.About.route)
}
TextPreferenceWidget(
title = stringResource(R.string.about_view_title),
onPreferenceClick = { navController.navigate(Routes.About.route) },
)
}
}
@ -97,46 +100,3 @@ fun SettingsViewContent(
}
}
@Composable
internal fun SettingRow(
title: String, subtitle: String? = null, onClick: (() -> Unit)?
) {
Row(
modifier = Modifier
.clickable(enabled = onClick != null, onClick = { onClick?.invoke() })
.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically
) {
Column(
modifier = Modifier
.weight(1f)
.padding(vertical = SettingsVerticalPadding)
) {
Text(
modifier = Modifier.padding(horizontal = SettingsHorizontalPadding),
text = title,
overflow = TextOverflow.Ellipsis,
maxLines = 2,
style = MaterialTheme.typography.titleLarge,
fontSize = SettingsTitleFontSize,
)
if (!subtitle.isNullOrBlank()) {
Text(
text = subtitle,
modifier = Modifier
.padding(horizontal = SettingsHorizontalPadding)
.alpha(SettingsSecondaryItemAlpha),
style = MaterialTheme.typography.bodySmall,
maxLines = 10,
)
}
}
}
}
internal val SettingsHorizontalPadding = 16.dp
internal val SettingsVerticalPadding = 16.dp
internal const val SettingsSecondaryItemAlpha = .78f
internal val SettingsTitleFontSize = 16.sp
internal const val RELEASE_URL = "https://github.com/omnivore-app/omnivore/releases"

View File

@ -27,10 +27,10 @@ import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavHostController
import app.omnivore.omnivore.BuildConfig
import app.omnivore.omnivore.R
import app.omnivore.omnivore.core.designsystem.component.TextPreferenceWidget
import app.omnivore.omnivore.core.ui.LinkIcon
import app.omnivore.omnivore.feature.profile.RELEASE_URL
import app.omnivore.omnivore.feature.profile.SettingRow
import app.omnivore.omnivore.feature.profile.ProfileViewModel
import app.omnivore.omnivore.feature.profile.RELEASE_URL
import app.omnivore.omnivore.navigation.Routes
@OptIn(ExperimentalMaterial3Api::class)
@ -68,41 +68,45 @@ internal fun AboutScreen(
}
item {
SettingRow(
TextPreferenceWidget(
title = stringResource(R.string.about_view_row_whats_new),
onClick = { uriHandler.openUri(RELEASE_URL) },
onPreferenceClick = { uriHandler.openUri(RELEASE_URL) },
)
}
item {
SettingRow(title = stringResource(R.string.about_documentation)) {
navController.navigate(Routes.Documentation.route)
}
TextPreferenceWidget(
title = stringResource(R.string.about_documentation),
onPreferenceClick = { navController.navigate(Routes.Documentation.route) },
)
}
item {
SettingRow(title = stringResource(R.string.about_feedback)) {
settingsViewModel.presentIntercom()
}
TextPreferenceWidget(
title = stringResource(R.string.about_feedback),
onPreferenceClick = { settingsViewModel.presentIntercom() },
)
}
item {
SettingRow(title = stringResource(R.string.about_privacy_policy)) {
navController.navigate(Routes.PrivacyPolicy.route)
}
TextPreferenceWidget(
title = stringResource(R.string.about_privacy_policy),
onPreferenceClick = { navController.navigate(Routes.PrivacyPolicy.route) },
)
}
item {
SettingRow(title = stringResource(R.string.about_terms_and_conditions)) {
navController.navigate(Routes.TermsAndConditions.route)
}
TextPreferenceWidget(
title = stringResource(R.string.about_terms_and_conditions),
onPreferenceClick = { navController.navigate(Routes.TermsAndConditions.route) },
)
}
item {
SettingRow(
TextPreferenceWidget(
title = stringResource(R.string.about_view_row_version),
subtitle = getVersionName(),
onClick = { },
onPreferenceClick = { },
)
}

View File

@ -20,7 +20,7 @@ import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavHostController
import app.omnivore.omnivore.R
import app.omnivore.omnivore.feature.profile.SettingRow
import app.omnivore.omnivore.core.designsystem.component.TextPreferenceWidget
import app.omnivore.omnivore.feature.profile.ProfileViewModel
@OptIn(ExperimentalMaterial3Api::class)
@ -72,11 +72,9 @@ internal fun AccountScreen(
contentPadding = contentPadding,
) {
item {
SettingRow(
TextPreferenceWidget(
title = stringResource(R.string.manage_account_action_reset_data_cache),
onClick = {
settingsViewModel.resetDataCache()
},
onPreferenceClick = { settingsViewModel.resetDataCache() },
)
}
}

View File

@ -0,0 +1,65 @@
package app.omnivore.omnivore.feature.profile.filters
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.ArrowBack
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavHostController
import app.omnivore.omnivore.R
import app.omnivore.omnivore.core.designsystem.component.SwitchPreferenceWidget
import app.omnivore.omnivore.feature.profile.ProfileViewModel
@OptIn(ExperimentalMaterial3Api::class)
@Composable
internal fun FiltersScreen(
navController: NavHostController,
settingsViewModel: ProfileViewModel = hiltViewModel()
) {
Scaffold(
topBar = {
TopAppBar(
title = { Text(stringResource(R.string.profile_filters)) },
navigationIcon = {
IconButton(onClick = { navController.navigateUp() }) {
Icon(
imageVector = Icons.AutoMirrored.Outlined.ArrowBack, contentDescription = null
)
}
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.background
),
)
},
) { contentPadding ->
LazyColumn(
contentPadding = contentPadding,
) {
item {
var checked by remember { mutableStateOf(true) }
SwitchPreferenceWidget(
title = stringResource(R.string.hide_following_tab),
checked = checked,
onCheckedChanged = { checked = it },
)
}
}
}
}

View File

@ -5,6 +5,7 @@ sealed class Routes(val route: String) {
data object Inbox : Routes("Inbox")
data object Settings : Routes("Settings")
data object About : Routes("About")
data object Filters : Routes("Filters")
data object Account : Routes("Account")
data object Search : Routes("Search")
data object Documentation : Routes("Documentation")

View File

@ -205,7 +205,7 @@
<string name="about_feedback">Feedback</string>
<string name="about_privacy_policy">Datenschutzerklärung</string>
<string name="about_terms_and_conditions">Nutzungsbedingungen</string>
<string name="about_manage_account">Konto verwalten</string>
<string name="profile_manage_account">Konto verwalten</string>
<string name="about_logout">Abmelden</string>
<!-- AddLinkSheet -->

View File

@ -205,7 +205,7 @@
<string name="about_feedback">反馈</string>
<string name="about_privacy_policy">隐私策略</string>
<string name="about_terms_and_conditions">条款和条件</string>
<string name="about_manage_account">管理帐户</string>
<string name="profile_manage_account">管理帐户</string>
<string name="about_logout">登出</string>
<!-- stock filters -->

View File

@ -204,6 +204,6 @@
<string name="about_feedback">回饋</string>
<string name="about_privacy_policy">隱私政策</string>
<string name="about_terms_and_conditions">條款和條件</string>
<string name="about_manage_account">管理帳戶</string>
<string name="profile_manage_account">管理帳戶</string>
<string name="about_logout">登出</string>
</resources>

View File

@ -201,6 +201,9 @@
<string name="logout_dialog_action_confirm">Confirm</string>
<string name="logout_dialog_action_cancel">Cancel</string>
<!-- Filters -->
<string name="hide_following_tab">Hide following tab</string>
<!-- ManageAccount -->
<string name="manage_account_title">Manage Account</string>
<string name="manage_account_action_reset_data_cache">Reset Data Cache</string>
@ -214,7 +217,9 @@
<string name="about_feedback">Feedback</string>
<string name="about_privacy_policy">Privacy Policy</string>
<string name="about_terms_and_conditions">Terms and Conditions</string>
<string name="about_manage_account">Manage Account</string>
<string name="profile_filters">Filters</string>
<string name="profile_manage_account">Manage Account</string>
<string name="about_logout">Logout</string>
<!-- About -->