feat: 关于页面与开源许可声明

- 新增 AboutScreen:应用图标、名称、版本、开源许可入口
- 新增 LicensesScreen 与 Licenses 数据源,展示第三方依赖许可证
- App 内页面导航(Main/About/Licenses)
- 双平台 getAppIconUri() 及 app_icon.png 资源
- 菜单"关于"项接入 AboutScreen 跳转
- iOS Info.plist 补充 CFBundleShortVersionString / CFBundleVersion

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
xfy 2026-05-19 17:31:26 +08:00
parent e93d575f02
commit 43579b2866
12 changed files with 291 additions and 7 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

View File

@ -4,5 +4,9 @@
<dict>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
</dict>
</plist>

View File

@ -8,4 +8,6 @@ class AndroidPlatform : Platform {
actual fun getPlatform(): Platform = AndroidPlatform()
actual fun getGifUri(gifFile: String): String = "file:///android_asset/gifs/$gifFile"
actual fun getGifUri(gifFile: String): String = "file:///android_asset/gifs/$gifFile"
actual fun getAppIconUri(): String = "file:///android_asset/app_icon.png"

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

View File

@ -5,18 +5,40 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import plus.rua.project.ui.AboutScreen
import plus.rua.project.ui.CalendarMonthView
import plus.rua.project.ui.LicensesScreen
private enum class Screen { Main, About, Licenses }
/**
* 应用入口 Composable根据系统主题切换明暗 ColorScheme 并包裹 CalendarMonthView
* 应用入口 Composable根据系统主题切换明暗 ColorScheme 管理页面导航
*/
@Composable
@Preview(name = "Calendar App")
fun App() {
var currentScreen by remember { mutableStateOf(Screen.Main) }
val colorScheme = if (isSystemInDarkTheme()) darkColorScheme() else lightColorScheme()
MaterialTheme(colorScheme = colorScheme) {
CalendarMonthView(modifier = Modifier)
when (currentScreen) {
Screen.Main -> CalendarMonthView(
modifier = Modifier,
onNavigateToAbout = { currentScreen = Screen.About }
)
Screen.About -> AboutScreen(
onBack = { currentScreen = Screen.Main },
onNavigateToLicenses = { currentScreen = Screen.Licenses }
)
Screen.Licenses -> LicensesScreen(
onBack = { currentScreen = Screen.About }
)
}
}
}

View File

@ -0,0 +1,9 @@
package plus.rua.project
/**
* 应用常量信息
*/
object AppInfo {
const val NAME = "鸭鸭日历"
const val VERSION = "1.0"
}

View File

@ -12,4 +12,6 @@ expect fun getPlatform(): Platform
* @param gifFile GIF 文件名 "001.gif"
* @return 平台特定的资源 URI
*/
expect fun getGifUri(gifFile: String): String
expect fun getGifUri(gifFile: String): String
expect fun getAppIconUri(): String

View File

@ -0,0 +1,114 @@
package plus.rua.project.ui
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.unit.dp
import com.github.panpf.sketch.AsyncImage
import plus.rua.project.AppInfo
import plus.rua.project.getAppIconUri
/**
* 关于页面展示应用图标名称版本号及开源许可入口
*
* @param onBack 返回回调
* @param onNavigateToLicenses 跳转到开源许可页面回调
* @param modifier 布局修饰符
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AboutScreen(
onBack: () -> Unit,
onNavigateToLicenses: () -> Unit,
modifier: Modifier = Modifier
) {
Scaffold(
topBar = {
TopAppBar(
title = { Text("关于鸭鸭日历") },
navigationIcon = {
IconButton(onClick = onBack) {
Canvas(modifier = Modifier.size(24.dp)) {
val strokeWidth = 2.dp.toPx()
drawLine(
color = Color.White,
start = Offset(size.width * 0.75f, size.height * 0.25f),
end = Offset(size.width * 0.25f, size.height * 0.5f),
strokeWidth = strokeWidth,
cap = StrokeCap.Round
)
drawLine(
color = Color.White,
start = Offset(size.width * 0.25f, size.height * 0.5f),
end = Offset(size.width * 0.75f, size.height * 0.75f),
strokeWidth = strokeWidth,
cap = StrokeCap.Round
)
}
}
}
)
},
modifier = modifier
) { innerPadding ->
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxSize()
.padding(innerPadding)
.padding(horizontal = 24.dp)
) {
Spacer(modifier = Modifier.height(48.dp))
val appIconUri = remember { getAppIconUri() }
AsyncImage(
uri = appIconUri,
contentDescription = "应用图标",
modifier = Modifier
.size(80.dp)
.clip(RoundedCornerShape(16.dp))
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = AppInfo.NAME,
style = MaterialTheme.typography.titleLarge
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "版本:${AppInfo.VERSION}",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
Spacer(modifier = Modifier.height(48.dp))
TextButton(onClick = onNavigateToLicenses) {
Text("开放源代码许可")
}
}
}
}

View File

@ -78,7 +78,8 @@ import kotlin.time.Clock
*/
@Composable
fun CalendarMonthView(
modifier: Modifier = Modifier
modifier: Modifier = Modifier,
onNavigateToAbout: () -> Unit = {}
) {
val coroutineScope = rememberCoroutineScope()
val viewModel = remember { CalendarViewModel(coroutineScope) }
@ -468,7 +469,10 @@ fun CalendarMonthView(
MenuItem(
text = "关于",
selected = false,
onClick = { /* TODO: 后续接入设置页 */ }
onClick = {
isMenuExpanded = false
onNavigateToAbout()
}
)
}
}

View File

@ -0,0 +1,27 @@
package plus.rua.project.ui
/**
* 许可证条目数据
*
* @param library 库名称
* @param license 许可证名称
*/
data class LicenseItem(
val library: String,
val license: String
)
/**
* 项目使用的第三方库及其许可证列表
*/
val licenses = listOf(
LicenseItem("Kotlin", "Apache-2.0"),
LicenseItem("Compose Multiplatform", "Apache-2.0"),
LicenseItem("Material 3", "Apache-2.0"),
LicenseItem("kotlinx-datetime", "Apache-2.0"),
LicenseItem("tyme4kt", "MIT"),
LicenseItem("Sketch", "Apache-2.0"),
LicenseItem("AndroidX Activity", "Apache-2.0"),
LicenseItem("AndroidX Lifecycle", "Apache-2.0"),
LicenseItem("JUnit", "EPL-1.0"),
)

View File

@ -0,0 +1,98 @@
package plus.rua.project.ui
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.IconButton
import androidx.compose.material3.ListItem
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.unit.dp
/**
* 开放源代码许可页面展示项目使用的第三方库及其许可证
*
* @param onBack 返回回调
* @param modifier 布局修饰符
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun LicensesScreen(
onBack: () -> Unit,
modifier: Modifier = Modifier
) {
Scaffold(
topBar = {
TopAppBar(
title = { Text("开放源代码许可") },
navigationIcon = {
IconButton(onClick = onBack) {
Canvas(modifier = Modifier.size(24.dp)) {
val strokeWidth = 2.dp.toPx()
drawLine(
color = Color.White,
start = Offset(size.width * 0.75f, size.height * 0.25f),
end = Offset(size.width * 0.25f, size.height * 0.5f),
strokeWidth = strokeWidth,
cap = StrokeCap.Round
)
drawLine(
color = Color.White,
start = Offset(size.width * 0.25f, size.height * 0.5f),
end = Offset(size.width * 0.75f, size.height * 0.75f),
strokeWidth = strokeWidth,
cap = StrokeCap.Round
)
}
}
}
)
},
modifier = modifier
) { innerPadding ->
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(innerPadding)
) {
items(licenses) { item ->
Column {
ListItem(
modifier = Modifier.clickable { },
headlineContent = {
Text(
text = item.library,
style = MaterialTheme.typography.bodyLarge
)
},
trailingContent = {
Text(
text = item.license,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
)
HorizontalDivider(
modifier = Modifier.padding(horizontal = 16.dp),
color = MaterialTheme.colorScheme.outlineVariant
)
}
}
}
}
}

View File

@ -9,4 +9,6 @@ class IOSPlatform : Platform {
actual fun getPlatform(): Platform = IOSPlatform()
actual fun getGifUri(gifFile: String): String = "compose.resource://files/$gifFile"
actual fun getGifUri(gifFile: String): String = "compose.resource://files/$gifFile"
actual fun getAppIconUri(): String = "compose.resource://files/app_icon.png"