From a15f78bb3e4f7754408b5359507a9c4a3440bfec Mon Sep 17 00:00:00 2001
From: Super12138 <70494801+Super12138@users.noreply.github.com>
Date: Thu, 16 Jan 2025 14:14:43 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E9=83=A8=E5=88=86=E8=AE=BE=E7=BD=AE?=
=?UTF-8?q?=E5=92=8C=E5=85=B3=E4=BA=8E=E9=A1=B5=E9=9D=A2?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/build.gradle.kts | 3 +
app/licences/libraries/eastereggs.json | 16 +++
app/licences/libraries/mauth.json | 16 +++
app/licences/libraries/readyou.json | 16 +++
.../todo/ExampleInstrumentedTest.kt | 6 +-
.../todo/ui/activities/MainActivity.kt | 4 +-
.../todo/ui/components/Scaffolds.kt | 95 +++++++++++++
.../todo/ui/navigation/ToDoNavigation.kt | 32 -----
.../todo/ui/navigation/ToDoScreen.kt | 6 -
.../todo/ui/navigation/TodoNavigation.kt | 78 +++++++++++
.../todo/ui/navigation/TodoScreen.kt | 8 ++
.../super12138/todo/ui/pages/main/MainPage.kt | 8 +-
.../todo/ui/pages/main/ManagerFragment.kt | 1 -
.../todo/ui/pages/settings/SettingsAbout.kt | 59 ++++++++
.../ui/pages/settings/SettingsAboutLicence.kt | 36 +++++
.../todo/ui/pages/settings/SettingsMain.kt | 46 +++++++
.../components/DefaultSettingsItem.kt | 128 ++++++++++++++++++
.../settings/components/SettingsCategory.kt | 29 ++++
.../settings/components/SwitchSettingsItem.kt | 32 +++++
.../cn/super12138/todo/ui/theme/Motion.kt | 87 ++++++++++++
.../cn/super12138/todo/utils/SystemUtils.kt | 19 +++
app/src/main/res/values-zh-rCN/strings.xml | 7 +
app/src/main/res/values/strings.xml | 8 ++
.../cn/super12138/todo/ExampleUnitTest.kt | 3 +-
24 files changed, 694 insertions(+), 49 deletions(-)
create mode 100644 app/licences/libraries/eastereggs.json
create mode 100644 app/licences/libraries/mauth.json
create mode 100644 app/licences/libraries/readyou.json
create mode 100644 app/src/main/kotlin/cn/super12138/todo/ui/components/Scaffolds.kt
delete mode 100644 app/src/main/kotlin/cn/super12138/todo/ui/navigation/ToDoNavigation.kt
delete mode 100644 app/src/main/kotlin/cn/super12138/todo/ui/navigation/ToDoScreen.kt
create mode 100644 app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoNavigation.kt
create mode 100644 app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoScreen.kt
create mode 100644 app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAbout.kt
create mode 100644 app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAboutLicence.kt
create mode 100644 app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsMain.kt
create mode 100644 app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/DefaultSettingsItem.kt
create mode 100644 app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsCategory.kt
create mode 100644 app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SwitchSettingsItem.kt
create mode 100644 app/src/main/kotlin/cn/super12138/todo/ui/theme/Motion.kt
create mode 100644 app/src/main/kotlin/cn/super12138/todo/utils/SystemUtils.kt
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 0d78512..56afb9d 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -41,6 +41,9 @@ android {
kotlinOptions {
jvmTarget = "21"
}
+ aboutLibraries {
+ configPath = "$projectDir/licences"
+ }
buildFeatures {
compose = true
}
diff --git a/app/licences/libraries/eastereggs.json b/app/licences/libraries/eastereggs.json
new file mode 100644
index 0000000..249317c
--- /dev/null
+++ b/app/licences/libraries/eastereggs.json
@@ -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"
+ ]
+}
\ No newline at end of file
diff --git a/app/licences/libraries/mauth.json b/app/licences/libraries/mauth.json
new file mode 100644
index 0000000..3a58df7
--- /dev/null
+++ b/app/licences/libraries/mauth.json
@@ -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"
+ ]
+}
\ No newline at end of file
diff --git a/app/licences/libraries/readyou.json b/app/licences/libraries/readyou.json
new file mode 100644
index 0000000..3ef049f
--- /dev/null
+++ b/app/licences/libraries/readyou.json
@@ -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"
+ ]
+}
\ No newline at end of file
diff --git a/app/src/androidTest/kotlin/cn/super12138/todo/ExampleInstrumentedTest.kt b/app/src/androidTest/kotlin/cn/super12138/todo/ExampleInstrumentedTest.kt
index 491bd80..6f68032 100644
--- a/app/src/androidTest/kotlin/cn/super12138/todo/ExampleInstrumentedTest.kt
+++ b/app/src/androidTest/kotlin/cn/super12138/todo/ExampleInstrumentedTest.kt
@@ -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.
*
diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/activities/MainActivity.kt b/app/src/main/kotlin/cn/super12138/todo/ui/activities/MainActivity.kt
index ea60c08..5fa890e 100644
--- a/app/src/main/kotlin/cn/super12138/todo/ui/activities/MainActivity.kt
+++ b/app/src/main/kotlin/cn/super12138/todo/ui/activities/MainActivity.kt
@@ -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)
}
}
diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/components/Scaffolds.kt b/app/src/main/kotlin/cn/super12138/todo/ui/components/Scaffolds.kt
new file mode 100644
index 0000000..e0866fd
--- /dev/null
+++ b/app/src/main/kotlin/cn/super12138/todo/ui/components/Scaffolds.kt
@@ -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) }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/navigation/ToDoNavigation.kt b/app/src/main/kotlin/cn/super12138/todo/ui/navigation/ToDoNavigation.kt
deleted file mode 100644
index 154a023..0000000
--- a/app/src/main/kotlin/cn/super12138/todo/ui/navigation/ToDoNavigation.kt
+++ /dev/null
@@ -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()
- )
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/navigation/ToDoScreen.kt b/app/src/main/kotlin/cn/super12138/todo/ui/navigation/ToDoScreen.kt
deleted file mode 100644
index 6f927ac..0000000
--- a/app/src/main/kotlin/cn/super12138/todo/ui/navigation/ToDoScreen.kt
+++ /dev/null
@@ -1,6 +0,0 @@
-package cn.super12138.todo.ui.navigation
-
-enum class ToDoScreen {
- Main,
- Settings
-}
\ No newline at end of file
diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoNavigation.kt b/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoNavigation.kt
new file mode 100644
index 0000000..ad330f9
--- /dev/null
+++ b/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoNavigation.kt
@@ -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() }
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoScreen.kt b/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoScreen.kt
new file mode 100644
index 0000000..784a831
--- /dev/null
+++ b/app/src/main/kotlin/cn/super12138/todo/ui/navigation/TodoScreen.kt
@@ -0,0 +1,8 @@
+package cn.super12138.todo.ui.navigation
+
+enum class TodoScreen {
+ Main,
+ SettingsMain,
+ SettingsAbout,
+ SettingsAboutLicence
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt
index dcfc968..3599c1b 100644
--- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt
+++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/MainPage.kt
@@ -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 = {
diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/ManagerFragment.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/ManagerFragment.kt
index 45d8df6..1f4ff8e 100644
--- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/ManagerFragment.kt
+++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/ManagerFragment.kt
@@ -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
diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAbout.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAbout.kt
new file mode 100644
index 0000000..64e7823
--- /dev/null
+++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAbout.kt
@@ -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
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAboutLicence.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAboutLicence.kt
new file mode 100644
index 0000000..2c67406
--- /dev/null
+++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAboutLicence.kt
@@ -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()
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsMain.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsMain.kt
new file mode 100644
index 0000000..af08e61
--- /dev/null
+++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsMain.kt
@@ -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
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/DefaultSettingsItem.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/DefaultSettingsItem.kt
new file mode 100644
index 0000000..9c7da3b
--- /dev/null
+++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/DefaultSettingsItem.kt
@@ -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()
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsCategory.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsCategory.kt
new file mode 100644
index 0000000..4a51636
--- /dev/null
+++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsCategory.kt
@@ -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
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SwitchSettingsItem.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SwitchSettingsItem.kt
new file mode 100644
index 0000000..636ab5d
--- /dev/null
+++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SwitchSettingsItem.kt
@@ -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
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/theme/Motion.kt b/app/src/main/kotlin/cn/super12138/todo/ui/theme/Motion.kt
new file mode 100644
index 0000000..cdaf9c4
--- /dev/null
+++ b/app/src/main/kotlin/cn/super12138/todo/ui/theme/Motion.kt
@@ -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
+ )
+)
\ No newline at end of file
diff --git a/app/src/main/kotlin/cn/super12138/todo/utils/SystemUtils.kt b/app/src/main/kotlin/cn/super12138/todo/utils/SystemUtils.kt
new file mode 100644
index 0000000..430c9f0
--- /dev/null
+++ b/app/src/main/kotlin/cn/super12138/todo/utils/SystemUtils.kt
@@ -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)"
+}
\ No newline at end of file
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index 791e1fb..9e01901 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -32,4 +32,11 @@
确定
警告
即将删除你选择的 %s 项待办。删除后将无法恢复这些待办,确定删除?
+ 返回
+ 关于
+ 关于本应用
+ 版本
+ 开发者
+ 开放源代码许可
+ 查看应用使用的开源库及其许可
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 389e05f..c6dbcc1 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -32,4 +32,12 @@
Warning
You are about to delete the %s selected tasks. Once deleted, these tasks cannot be recovered. Are you sure you want to delete them?
/
+ Back
+ About
+ About this app
+ Super12138
+ Version
+ Developer
+ Open Source Licences
+ Check the open source libraries used by the application and their licences.
\ No newline at end of file
diff --git a/app/src/test/kotlin/cn/super12138/todo/ExampleUnitTest.kt b/app/src/test/kotlin/cn/super12138/todo/ExampleUnitTest.kt
index b9664f8..8116211 100644
--- a/app/src/test/kotlin/cn/super12138/todo/ExampleUnitTest.kt
+++ b/app/src/test/kotlin/cn/super12138/todo/ExampleUnitTest.kt
@@ -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).
*