feat: 部分设置和关于页面
This commit is contained in:
parent
c1cb9f7c3f
commit
a15f78bb3e
24 changed files with 694 additions and 49 deletions
|
|
@ -41,6 +41,9 @@ android {
|
|||
kotlinOptions {
|
||||
jvmTarget = "21"
|
||||
}
|
||||
aboutLibraries {
|
||||
configPath = "$projectDir/licences"
|
||||
}
|
||||
buildFeatures {
|
||||
compose = true
|
||||
}
|
||||
|
|
|
|||
16
app/licences/libraries/eastereggs.json
Normal file
16
app/licences/libraries/eastereggs.json
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"uniqueId": "com.dede.android_eggs:3.4.0",
|
||||
"artifactVersion": "3.4.0",
|
||||
"name": "AndroidEasterEggs",
|
||||
"developers": [
|
||||
{
|
||||
"name": "hushenghao"
|
||||
}
|
||||
],
|
||||
"description": "Collections the Android release Easter Egg",
|
||||
"website": "https://github.com/hushenghao/AndroidEasterEggs",
|
||||
"tag": "custom",
|
||||
"licenses": [
|
||||
"Apache-2.0"
|
||||
]
|
||||
}
|
||||
16
app/licences/libraries/mauth.json
Normal file
16
app/licences/libraries/mauth.json
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"uniqueId": "com.xinto.mauth:0.9.0",
|
||||
"artifactVersion": "0.9.0",
|
||||
"name": "Mauth",
|
||||
"developers": [
|
||||
{
|
||||
"name": "X1nto"
|
||||
}
|
||||
],
|
||||
"description": "A Material You Two-factor Authentication app",
|
||||
"website": "https://github.com/X1nto/Mauth/",
|
||||
"tag": "custom",
|
||||
"licenses": [
|
||||
"GPL-3.0-or-later"
|
||||
]
|
||||
}
|
||||
16
app/licences/libraries/readyou.json
Normal file
16
app/licences/libraries/readyou.json
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"uniqueId": "me.ash.reader:0.11.1",
|
||||
"artifactVersion": "0.11.1",
|
||||
"name": "Read You",
|
||||
"developers": [
|
||||
{
|
||||
"name": "Ashinch"
|
||||
}
|
||||
],
|
||||
"description": "An Android RSS reader presented in Material You style.",
|
||||
"website": "https://github.com/Ashinch/ReadYou/",
|
||||
"tag": "custom",
|
||||
"licenses": [
|
||||
"GPL-3.0-or-later"
|
||||
]
|
||||
}
|
||||
|
|
@ -1,13 +1,11 @@
|
|||
package cn.super12138.todo
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import androidx.compose.material3.MaterialTheme
|
|||
import androidx.compose.material3.Surface
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import cn.super12138.todo.ui.components.Konfetti
|
||||
import cn.super12138.todo.ui.navigation.ToDoNavigation
|
||||
import cn.super12138.todo.ui.navigation.TodoNavigation
|
||||
import cn.super12138.todo.ui.theme.ToDoTheme
|
||||
import cn.super12138.todo.ui.viewmodels.MainViewModel
|
||||
|
||||
|
|
@ -21,7 +21,7 @@ class MainActivity : ComponentActivity() {
|
|||
Surface(color = MaterialTheme.colorScheme.background) {
|
||||
val mainViewModel: MainViewModel = viewModel()
|
||||
val showConfetti = mainViewModel.showConfetti
|
||||
ToDoNavigation(viewModel = mainViewModel)
|
||||
TodoNavigation(viewModel = mainViewModel)
|
||||
Konfetti(state = showConfetti)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,95 @@
|
|||
package cn.super12138.todo.ui.components
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.safeContent
|
||||
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.LargeTopAppBar
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.ScaffoldDefaults
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import cn.super12138.todo.R
|
||||
|
||||
/**
|
||||
* 带有顶部大标题栏的通用脚手架
|
||||
* * 内容默认由 Box 容器包裹;实际使用时推荐配合 Column 或 Row
|
||||
*
|
||||
* @param title 标题文本
|
||||
* @param scrollBehavior 滚动行为,用于支持页面内容滚动时标题栏的压缩效果
|
||||
* @param onBack 当返回按钮被按下时的操作
|
||||
* @param contentWindowInsets 内容边距,通常用于将内容和系统状态栏等隔开;可以使用 `WindowInsets.safeContent`
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun LargeTopAppBarScaffold(
|
||||
title: String,
|
||||
scrollBehavior: TopAppBarScrollBehavior,
|
||||
onBack: () -> Unit,
|
||||
snackbarHost: @Composable () -> Unit = {},
|
||||
contentWindowInsets: WindowInsets = WindowInsets.safeContent,
|
||||
modifier: Modifier = Modifier,
|
||||
content: @Composable (PaddingValues) -> Unit
|
||||
) {
|
||||
LargeTopAppBarScaffold(
|
||||
title = {
|
||||
Text(
|
||||
text = title,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
},
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onBack) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Outlined.ArrowBack,
|
||||
contentDescription = stringResource(R.string.action_back)
|
||||
)
|
||||
}
|
||||
},
|
||||
scrollBehavior = scrollBehavior
|
||||
) { innerPadding ->
|
||||
Box { content(innerPadding) }
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun LargeTopAppBarScaffold(
|
||||
title: @Composable () -> Unit = {},
|
||||
navigationIcon: @Composable () -> Unit = {},
|
||||
actions: @Composable RowScope.() -> Unit = {},
|
||||
scrollBehavior: TopAppBarScrollBehavior,
|
||||
snackbarHost: @Composable () -> Unit = {},
|
||||
contentWindowInsets: WindowInsets = ScaffoldDefaults.contentWindowInsets,
|
||||
modifier: Modifier = Modifier,
|
||||
content: @Composable (PaddingValues) -> Unit
|
||||
) {
|
||||
Scaffold(
|
||||
modifier = modifier
|
||||
.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
topBar = {
|
||||
LargeTopAppBar(
|
||||
title = title,
|
||||
navigationIcon = navigationIcon,
|
||||
actions = actions,
|
||||
scrollBehavior = scrollBehavior
|
||||
)
|
||||
},
|
||||
snackbarHost = snackbarHost,
|
||||
contentWindowInsets = contentWindowInsets
|
||||
) { innerPadding ->
|
||||
Box { content(innerPadding) }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
package cn.super12138.todo.ui.navigation
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import cn.super12138.todo.ui.pages.main.MainPage
|
||||
import cn.super12138.todo.ui.viewmodels.MainViewModel
|
||||
|
||||
@Composable
|
||||
fun ToDoNavigation(
|
||||
navController: NavHostController = rememberNavController(),
|
||||
startDestination: String = ToDoScreen.Main.name,
|
||||
viewModel: MainViewModel,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
NavHost(
|
||||
navController = navController,
|
||||
startDestination = startDestination,
|
||||
modifier = modifier
|
||||
) {
|
||||
composable(ToDoScreen.Main.name) {
|
||||
MainPage(
|
||||
viewModel = viewModel,
|
||||
modifier = Modifier.fillMaxSize()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
package cn.super12138.todo.ui.navigation
|
||||
|
||||
enum class ToDoScreen {
|
||||
Main,
|
||||
Settings
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
package cn.super12138.todo.ui.navigation
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import cn.super12138.todo.ui.pages.main.MainPage
|
||||
import cn.super12138.todo.ui.pages.settings.SettingsAbout
|
||||
import cn.super12138.todo.ui.pages.settings.SettingsAboutLicence
|
||||
import cn.super12138.todo.ui.pages.settings.SettingsMain
|
||||
import cn.super12138.todo.ui.theme.materialSharedAxisXIn
|
||||
import cn.super12138.todo.ui.theme.materialSharedAxisXOut
|
||||
import cn.super12138.todo.ui.viewmodels.MainViewModel
|
||||
|
||||
private const val INITIAL_OFFSET_FACTOR = 0.10f
|
||||
|
||||
@Composable
|
||||
fun TodoNavigation(
|
||||
navController: NavHostController = rememberNavController(),
|
||||
startDestination: String = TodoScreen.Main.name,
|
||||
viewModel: MainViewModel,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
NavHost(
|
||||
navController = navController,
|
||||
startDestination = startDestination,
|
||||
enterTransition = {
|
||||
materialSharedAxisXIn(
|
||||
initialOffsetX = { (it * INITIAL_OFFSET_FACTOR).toInt() }
|
||||
)
|
||||
},
|
||||
exitTransition = {
|
||||
materialSharedAxisXOut(
|
||||
targetOffsetX = { -(it * INITIAL_OFFSET_FACTOR).toInt() }
|
||||
)
|
||||
},
|
||||
popEnterTransition = {
|
||||
materialSharedAxisXIn(
|
||||
initialOffsetX = { -(it * INITIAL_OFFSET_FACTOR).toInt() }
|
||||
)
|
||||
},
|
||||
popExitTransition = {
|
||||
materialSharedAxisXOut(
|
||||
targetOffsetX = { (it * INITIAL_OFFSET_FACTOR).toInt() }
|
||||
)
|
||||
},
|
||||
modifier = modifier
|
||||
) {
|
||||
composable(TodoScreen.Main.name) {
|
||||
MainPage(
|
||||
viewModel = viewModel,
|
||||
toSettingsPage = { navController.navigate(TodoScreen.SettingsMain.name) }
|
||||
)
|
||||
}
|
||||
|
||||
composable(TodoScreen.SettingsMain.name) {
|
||||
SettingsMain(
|
||||
onNavigateUp = { navController.navigateUp() },
|
||||
toAboutPage = { navController.navigate(TodoScreen.SettingsAbout.name) }
|
||||
)
|
||||
}
|
||||
|
||||
composable(TodoScreen.SettingsAbout.name) {
|
||||
SettingsAbout(
|
||||
onNavigateUp = { navController.navigateUp() },
|
||||
toLicencePage = { navController.navigate(TodoScreen.SettingsAboutLicence.name) }
|
||||
)
|
||||
}
|
||||
|
||||
composable(TodoScreen.SettingsAboutLicence.name) {
|
||||
SettingsAboutLicence(
|
||||
onNavigateUp = { navController.navigateUp() }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package cn.super12138.todo.ui.navigation
|
||||
|
||||
enum class TodoScreen {
|
||||
Main,
|
||||
SettingsMain,
|
||||
SettingsAbout,
|
||||
SettingsAboutLicence
|
||||
}
|
||||
|
|
@ -36,7 +36,11 @@ import kotlinx.coroutines.launch
|
|||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun MainPage(viewModel: MainViewModel, modifier: Modifier = Modifier) {
|
||||
fun MainPage(
|
||||
viewModel: MainViewModel,
|
||||
toSettingsPage: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val toDoList = viewModel.toDos.collectAsState(initial = emptyList())
|
||||
val listState = rememberLazyListState()
|
||||
val isExpanded by remember {
|
||||
|
|
@ -77,7 +81,7 @@ fun MainPage(viewModel: MainViewModel, modifier: Modifier = Modifier) {
|
|||
onCancelSelect = { viewModel.clearAllTodoSelection() },
|
||||
onSelectAll = { viewModel.toggleAllSelected() },
|
||||
onDeleteSelectedTodo = { showDeleteConfirmDialog = true },
|
||||
toSettingsPage = {}
|
||||
toSettingsPage = toSettingsPage
|
||||
)
|
||||
},
|
||||
floatingActionButton = {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import androidx.compose.foundation.layout.Column
|
|||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.safeContentPadding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.foundation.lazy.items
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
package cn.super12138.todo.ui.pages.settings
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import cn.super12138.todo.R
|
||||
import cn.super12138.todo.ui.components.LargeTopAppBarScaffold
|
||||
import cn.super12138.todo.ui.pages.settings.components.DefaultSettingsItem
|
||||
import cn.super12138.todo.utils.getAppVersion
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun SettingsAbout(
|
||||
onNavigateUp: () -> Unit,
|
||||
toLicencePage: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
|
||||
LargeTopAppBarScaffold(
|
||||
title = stringResource(R.string.pref_about),
|
||||
onBack = onNavigateUp,
|
||||
scrollBehavior = scrollBehavior,
|
||||
modifier = modifier
|
||||
.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
) { innerPadding ->
|
||||
val context = LocalContext.current
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(innerPadding)
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
DefaultSettingsItem(
|
||||
title = stringResource(R.string.pref_app_version),
|
||||
description = getAppVersion(context),
|
||||
enableClick = false
|
||||
)
|
||||
DefaultSettingsItem(
|
||||
title = stringResource(R.string.pref_developer),
|
||||
description = stringResource(R.string.developer_name),
|
||||
enableClick = false
|
||||
)
|
||||
DefaultSettingsItem(
|
||||
title = stringResource(R.string.pref_licence),
|
||||
description = stringResource(R.string.pref_licence_desc),
|
||||
onClick = toLicencePage
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
package cn.super12138.todo.ui.pages.settings
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import cn.super12138.todo.R
|
||||
import cn.super12138.todo.ui.components.LargeTopAppBarScaffold
|
||||
import com.mikepenz.aboutlibraries.ui.compose.m3.LibrariesContainer
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun SettingsAboutLicence(
|
||||
onNavigateUp: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
|
||||
LargeTopAppBarScaffold(
|
||||
title = stringResource(R.string.pref_licence),
|
||||
scrollBehavior = scrollBehavior,
|
||||
onBack = onNavigateUp,
|
||||
modifier = modifier
|
||||
) { innerPadding ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(innerPadding)
|
||||
) {
|
||||
LibrariesContainer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
package cn.super12138.todo.ui.pages.settings
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Info
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import cn.super12138.todo.R
|
||||
import cn.super12138.todo.ui.components.LargeTopAppBarScaffold
|
||||
import cn.super12138.todo.ui.pages.settings.components.DefaultSettingsItem
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun SettingsMain(
|
||||
onNavigateUp: () -> Unit,
|
||||
toAboutPage: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
|
||||
LargeTopAppBarScaffold(
|
||||
title = stringResource(R.string.page_settings),
|
||||
scrollBehavior = scrollBehavior,
|
||||
onBack = onNavigateUp
|
||||
) { innerPadding ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(innerPadding)
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
DefaultSettingsItem(
|
||||
leadingIcon = Icons.Outlined.Info,
|
||||
title = stringResource(R.string.pref_about),
|
||||
description = stringResource(R.string.pref_about_desc),
|
||||
onClick = toAboutPage
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
package cn.super12138.todo.ui.pages.settings.components
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
@Composable
|
||||
fun DefaultSettingsItem(
|
||||
leadingIcon: ImageVector? = null,
|
||||
title: String,
|
||||
description: String? = null,
|
||||
enableClick: Boolean = true,
|
||||
onClick: () -> Unit = {},
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
DefaultSettingsItem(
|
||||
leadingIcon = leadingIcon,
|
||||
title = title,
|
||||
description = description,
|
||||
trailingContent = null,
|
||||
enableClick = enableClick,
|
||||
onClick = onClick,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DefaultSettingsItem(
|
||||
leadingIcon: ImageVector? = null,
|
||||
title: String,
|
||||
description: String? = null,
|
||||
trailingContent: (@Composable () -> Unit)? = null,
|
||||
enableClick: Boolean = true,
|
||||
onClick: () -> Unit = {},
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
DefaultSettingsItem(
|
||||
leadingIcon = {
|
||||
leadingIcon?.let {
|
||||
Icon(
|
||||
imageVector = it,
|
||||
contentDescription = title,
|
||||
tint = MaterialTheme.colorScheme.onSurface,
|
||||
modifier = Modifier.padding(end = 24.dp),
|
||||
)
|
||||
}
|
||||
},
|
||||
title = title,
|
||||
description = description,
|
||||
trailingContent = trailingContent,
|
||||
enableClick = enableClick,
|
||||
onClick = onClick,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DefaultSettingsItem(
|
||||
leadingIcon: (@Composable () -> Unit)? = null,
|
||||
title: String,
|
||||
description: String? = null,
|
||||
trailingContent: (@Composable () -> Unit)? = null,
|
||||
shape: Shape = MaterialTheme.shapes.large,
|
||||
enableClick: Boolean = true,
|
||||
onClick: () -> Unit = {},
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.clip(shape)
|
||||
.clickable(
|
||||
enabled = enableClick,
|
||||
onClick = onClick // TODO: HapticFeedback
|
||||
)
|
||||
.padding(horizontal = 24.dp, vertical = 20.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
leadingIcon?.let {
|
||||
it()
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
) {
|
||||
Text(
|
||||
text = title,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = MaterialTheme.typography.titleLarge.copy(
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
fontSize = 20.sp
|
||||
)
|
||||
)
|
||||
description?.let {
|
||||
Text(
|
||||
text = it,
|
||||
// maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = MaterialTheme.typography.bodyMedium.copy(
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
trailingContent?.let {
|
||||
it()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package cn.super12138.todo.ui.pages.settings.components
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@Composable
|
||||
fun SettingsCategory(
|
||||
title: String,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 12.dp, start = 24.dp, end = 24.dp)
|
||||
) {
|
||||
Text(
|
||||
text = title,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
fontWeight = FontWeight.Medium
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package cn.super12138.todo.ui.pages.settings.components
|
||||
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
|
||||
@Composable
|
||||
fun SwitchSettingsItem(
|
||||
leadingIcon: ImageVector? = null,
|
||||
title: String,
|
||||
description: String? = null,
|
||||
checked: Boolean,
|
||||
onCheckedChange: (Boolean) -> Unit,
|
||||
onClick: () -> Unit = {},
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
DefaultSettingsItem(
|
||||
leadingIcon = leadingIcon,
|
||||
title = title,
|
||||
description = description,
|
||||
trailingContent = {
|
||||
Switch(
|
||||
checked = checked,
|
||||
onCheckedChange = onCheckedChange,
|
||||
modifier = modifier,
|
||||
)
|
||||
},
|
||||
onClick = onClick,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
87
app/src/main/kotlin/cn/super12138/todo/ui/theme/Motion.kt
Normal file
87
app/src/main/kotlin/cn/super12138/todo/ui/theme/Motion.kt
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
package cn.super12138.todo.ui.theme
|
||||
|
||||
import androidx.compose.animation.ContentTransform
|
||||
import androidx.compose.animation.EnterTransition
|
||||
import androidx.compose.animation.ExitTransition
|
||||
import androidx.compose.animation.core.FastOutLinearInEasing
|
||||
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||
import androidx.compose.animation.core.LinearOutSlowInEasing
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.slideInHorizontally
|
||||
import androidx.compose.animation.slideOutHorizontally
|
||||
|
||||
/**
|
||||
* 来自:https://github.com/Ashinch/ReadYou/blob/main/app/src/main/java/me/ash/reader/ui/motion/MaterialSharedAxis.kt
|
||||
*/
|
||||
|
||||
private const val ProgressThreshold = 0.35f
|
||||
|
||||
private val Int.ForOutgoing: Int
|
||||
get() = (this * ProgressThreshold).toInt()
|
||||
|
||||
private val Int.ForIncoming: Int
|
||||
get() = this - this.ForOutgoing
|
||||
|
||||
|
||||
private const val DefaultMotionDuration: Int = 300
|
||||
|
||||
/**
|
||||
* [materialSharedAxisX] allows to switch a layout with shared X-axis transition.
|
||||
*
|
||||
*/
|
||||
fun materialSharedAxisX(
|
||||
initialOffsetX: (fullWidth: Int) -> Int,
|
||||
targetOffsetX: (fullWidth: Int) -> Int,
|
||||
durationMillis: Int = DefaultMotionDuration,
|
||||
): ContentTransform = ContentTransform(
|
||||
materialSharedAxisXIn(
|
||||
initialOffsetX = initialOffsetX,
|
||||
durationMillis = durationMillis
|
||||
), materialSharedAxisXOut(
|
||||
targetOffsetX = targetOffsetX,
|
||||
durationMillis = durationMillis
|
||||
)
|
||||
)
|
||||
|
||||
/**
|
||||
* [materialSharedAxisXIn] allows to switch a layout with shared X-axis enter transition.
|
||||
*/
|
||||
fun materialSharedAxisXIn(
|
||||
initialOffsetX: (fullWidth: Int) -> Int,
|
||||
durationMillis: Int = DefaultMotionDuration,
|
||||
): EnterTransition = slideInHorizontally(
|
||||
animationSpec = tween(
|
||||
durationMillis = durationMillis,
|
||||
easing = FastOutSlowInEasing
|
||||
),
|
||||
initialOffsetX = initialOffsetX
|
||||
) + fadeIn(
|
||||
animationSpec = tween(
|
||||
durationMillis = durationMillis.ForIncoming,
|
||||
delayMillis = durationMillis.ForOutgoing,
|
||||
easing = LinearOutSlowInEasing
|
||||
)
|
||||
)
|
||||
|
||||
/**
|
||||
* [materialSharedAxisXOut] allows to switch a layout with shared X-axis exit transition.
|
||||
*
|
||||
*/
|
||||
fun materialSharedAxisXOut(
|
||||
targetOffsetX: (fullWidth: Int) -> Int,
|
||||
durationMillis: Int = DefaultMotionDuration,
|
||||
): ExitTransition = slideOutHorizontally(
|
||||
animationSpec = tween(
|
||||
durationMillis = durationMillis,
|
||||
easing = FastOutSlowInEasing
|
||||
),
|
||||
targetOffsetX = targetOffsetX
|
||||
) + fadeOut(
|
||||
animationSpec = tween(
|
||||
durationMillis = durationMillis.ForOutgoing,
|
||||
delayMillis = 0,
|
||||
easing = FastOutLinearInEasing
|
||||
)
|
||||
)
|
||||
19
app/src/main/kotlin/cn/super12138/todo/utils/SystemUtils.kt
Normal file
19
app/src/main/kotlin/cn/super12138/todo/utils/SystemUtils.kt
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
package cn.super12138.todo.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
|
||||
/**
|
||||
* 获取应用版本号
|
||||
* @return 版本名称(版本代码)
|
||||
*/
|
||||
fun getAppVersion(context: Context): String {
|
||||
val pkgInfo = context.packageManager.getPackageInfo(context.packageName, 0)
|
||||
val verName = pkgInfo.versionName
|
||||
val verCode = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
pkgInfo.longVersionCode.toInt()
|
||||
} else {
|
||||
pkgInfo.versionCode
|
||||
}
|
||||
return "$verName ($verCode)"
|
||||
}
|
||||
|
|
@ -32,4 +32,11 @@
|
|||
<string name="action_confirm">确定</string>
|
||||
<string name="title_warning">警告</string>
|
||||
<string name="tip_delete_task">即将删除你选择的 %s 项待办。删除后将无法恢复这些待办,确定删除?</string>
|
||||
<string name="action_back">返回</string>
|
||||
<string name="pref_about">关于</string>
|
||||
<string name="pref_about_desc">关于本应用</string>
|
||||
<string name="pref_app_version">版本</string>
|
||||
<string name="pref_developer">开发者</string>
|
||||
<string name="pref_licence">开放源代码许可</string>
|
||||
<string name="pref_licence_desc">查看应用使用的开源库及其许可</string>
|
||||
</resources>
|
||||
|
|
@ -32,4 +32,12 @@
|
|||
<string name="title_warning">Warning</string>
|
||||
<string name="tip_delete_task">You are about to delete the %s selected tasks. Once deleted, these tasks cannot be recovered. Are you sure you want to delete them?</string>
|
||||
<string name="placeholder_divider" translatable="false">/</string>
|
||||
<string name="action_back">Back</string>
|
||||
<string name="pref_about">About</string>
|
||||
<string name="pref_about_desc">About this app</string>
|
||||
<string name="developer_name" translatable="false">Super12138</string>
|
||||
<string name="pref_app_version">Version</string>
|
||||
<string name="pref_developer">Developer</string>
|
||||
<string name="pref_licence">Open Source Licences</string>
|
||||
<string name="pref_licence_desc">Check the open source libraries used by the application and their licences.</string>
|
||||
</resources>
|
||||
|
|
@ -1,9 +1,8 @@
|
|||
package cn.super12138.todo
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
|
|
|
|||
Loading…
Reference in a new issue