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). *