feat: 部分设置和关于页面

This commit is contained in:
Super12138 2025-01-16 14:14:43 +08:00
parent c1cb9f7c3f
commit a15f78bb3e
24 changed files with 694 additions and 49 deletions

View file

@ -41,6 +41,9 @@ android {
kotlinOptions {
jvmTarget = "21"
}
aboutLibraries {
configPath = "$projectDir/licences"
}
buildFeatures {
compose = true
}

View 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"
]
}

View 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"
]
}

View 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"
]
}

View file

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

View file

@ -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)
}
}

View file

@ -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) }
}
}

View file

@ -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()
)
}
}
}

View file

@ -1,6 +0,0 @@
package cn.super12138.todo.ui.navigation
enum class ToDoScreen {
Main,
Settings
}

View file

@ -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() }
)
}
}
}

View file

@ -0,0 +1,8 @@
package cn.super12138.todo.ui.navigation
enum class TodoScreen {
Main,
SettingsMain,
SettingsAbout,
SettingsAboutLicence
}

View file

@ -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 = {

View file

@ -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

View file

@ -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
)
}
}
}

View file

@ -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()
}
}
}

View file

@ -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
)
}
}
}

View file

@ -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()
}
}
}

View file

@ -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
)
}
}

View file

@ -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
)
}

View 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
)
)

View 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)"
}

View file

@ -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>

View file

@ -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>

View file

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