feat(app): add About screen and mark 1.0 release #14
@@ -20,7 +20,7 @@ android {
|
||||
minSdk = 26
|
||||
targetSdk = 36
|
||||
versionCode = 1
|
||||
versionName = "0.1.0"
|
||||
versionName = "1.0.0"
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import com.flagship.abox.manager.domain.Username
|
||||
import com.flagship.abox.manager.ui.login.LoginRoute
|
||||
import com.flagship.abox.manager.ui.login.LoginViewModel
|
||||
import com.flagship.abox.manager.ui.navigation.AuthenticatedNavigation
|
||||
import com.flagship.abox.manager.ui.about.AboutScreen
|
||||
import com.flagship.abox.manager.ui.preferences.SettingsRoute
|
||||
import com.flagship.abox.manager.ui.preferences.SettingsExperienceState
|
||||
import com.flagship.abox.manager.ui.preferences.SettingsViewModel
|
||||
@@ -49,6 +50,7 @@ fun ABoxManagerApp(
|
||||
val screenKey = sessionState.screenKey()
|
||||
var showAccountDialog by rememberSaveable(screenKey) { mutableStateOf(false) }
|
||||
var showSettings by rememberSaveable(screenKey) { mutableStateOf(false) }
|
||||
var showAbout by rememberSaveable(screenKey) { mutableStateOf(false) }
|
||||
val settingsExperienceState = rememberSaveable(screenKey, saver = SettingsExperienceState.Saver) {
|
||||
SettingsExperienceState()
|
||||
}
|
||||
@@ -87,9 +89,11 @@ fun ABoxManagerApp(
|
||||
onSessionAction = sessionActionsViewModel::dispatch,
|
||||
showAccountDialog = showAccountDialog,
|
||||
showSettings = showSettings,
|
||||
showAbout = showAbout,
|
||||
settingsExperienceState = settingsExperienceState,
|
||||
onShowAccountDialogChange = { showAccountDialog = it },
|
||||
onShowSettingsChange = { showSettings = it },
|
||||
onShowAboutChange = { showAbout = it },
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -104,9 +108,11 @@ private fun AuthenticatedNavHost(
|
||||
onSessionAction: (SessionUiAction) -> Unit,
|
||||
showAccountDialog: Boolean,
|
||||
showSettings: Boolean,
|
||||
showAbout: Boolean,
|
||||
settingsExperienceState: SettingsExperienceState,
|
||||
onShowAccountDialogChange: (Boolean) -> Unit,
|
||||
onShowSettingsChange: (Boolean) -> Unit,
|
||||
onShowAboutChange: (Boolean) -> Unit,
|
||||
) {
|
||||
Box(Modifier.fillMaxSize()) {
|
||||
AuthenticatedNavigation(
|
||||
@@ -127,6 +133,11 @@ private fun AuthenticatedNavHost(
|
||||
)
|
||||
}
|
||||
}
|
||||
if (showAbout) {
|
||||
ABoxBackLayer(onBack = { onShowAboutChange(false) }) {
|
||||
AboutScreen(onBack = { onShowAboutChange(false) })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (showAccountDialog) {
|
||||
@@ -139,6 +150,10 @@ private fun AuthenticatedNavHost(
|
||||
settingsExperienceState.showTheme(returnToHome = false)
|
||||
onShowSettingsChange(true)
|
||||
},
|
||||
onOpenAbout = {
|
||||
onShowAccountDialogChange(false)
|
||||
onShowAboutChange(true)
|
||||
},
|
||||
onSwitchProvider = {
|
||||
onShowAccountDialogChange(false)
|
||||
onSessionAction(SessionUiAction.SwitchProvider)
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
package com.flagship.abox.manager.ui.about
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
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.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.flagship.abox.manager.R
|
||||
import com.flagship.abox.manager.designsystem.ABoxIconButton
|
||||
import com.flagship.abox.manager.designsystem.icon.ABoxIconKey
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun AboutScreen(
|
||||
onBack: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val packageInfo = remember(context.packageName) {
|
||||
runCatching {
|
||||
context.packageManager.getPackageInfo(context.packageName, 0)
|
||||
}.getOrNull()
|
||||
}
|
||||
val versionName = packageInfo?.versionName ?: stringResource(R.string.about_unknown)
|
||||
val versionCode = packageInfo?.let {
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) it.longVersionCode else {
|
||||
@Suppress("DEPRECATION")
|
||||
it.versionCode.toLong()
|
||||
}
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
modifier = modifier.fillMaxSize(),
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text(stringResource(R.string.about_title)) },
|
||||
navigationIcon = {
|
||||
ABoxIconButton(
|
||||
key = ABoxIconKey.BACK,
|
||||
contentDescription = stringResource(R.string.action_back),
|
||||
onClick = onBack,
|
||||
)
|
||||
},
|
||||
)
|
||||
},
|
||||
) { padding ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(padding)
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(horizontal = 20.dp, vertical = 24.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(24.dp),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(10.dp),
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(R.drawable.ic_abox_logo_master),
|
||||
contentDescription = stringResource(R.string.app_name),
|
||||
modifier = Modifier.size(88.dp),
|
||||
)
|
||||
Text(
|
||||
stringResource(R.string.app_name),
|
||||
style = MaterialTheme.typography.headlineMedium,
|
||||
fontWeight = FontWeight.Bold,
|
||||
)
|
||||
Text(
|
||||
stringResource(R.string.about_tagline),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
|
||||
AboutSection(stringResource(R.string.about_app_info)) {
|
||||
AboutValueRow(stringResource(R.string.about_version), "$versionName (${versionCode ?: stringResource(R.string.about_unknown)})")
|
||||
AboutValueRow(stringResource(R.string.about_package), context.packageName)
|
||||
AboutValueRow(stringResource(R.string.about_platform), stringResource(R.string.about_platform_value))
|
||||
}
|
||||
|
||||
AboutSection(stringResource(R.string.about_product)) {
|
||||
Text(
|
||||
stringResource(R.string.about_product_desc),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
|
||||
AboutSection(stringResource(R.string.about_team)) {
|
||||
TeamMember("KremeCN", stringResource(R.string.about_role_foundation))
|
||||
TeamMember("alZerNest", stringResource(R.string.about_role_devices))
|
||||
TeamMember("Aurum", stringResource(R.string.about_role_environment))
|
||||
TeamMember("Moler", stringResource(R.string.about_role_infrared))
|
||||
TeamMember("tzh", stringResource(R.string.about_role_scenes))
|
||||
}
|
||||
|
||||
Text(
|
||||
stringResource(R.string.about_copyright),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
Spacer(Modifier.height(12.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AboutSection(
|
||||
title: String,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(10.dp)) {
|
||||
Text(title, style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold)
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth().padding(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(14.dp),
|
||||
) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AboutValueRow(label: String, value: String) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(label, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
Text(value, fontWeight = FontWeight.Medium, modifier = Modifier.padding(start = 16.dp))
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TeamMember(name: String, role: String) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(name, fontWeight = FontWeight.SemiBold)
|
||||
Text(
|
||||
role,
|
||||
modifier = Modifier.padding(start = 16.dp),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
}
|
||||
+9
@@ -15,6 +15,7 @@ import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.Logout
|
||||
import androidx.compose.material.icons.outlined.CloudDone
|
||||
import androidx.compose.material.icons.outlined.Palette
|
||||
import androidx.compose.material.icons.outlined.Info
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
@@ -46,6 +47,7 @@ fun AccountProviderDialog(
|
||||
providerName: String,
|
||||
onDismissRequest: () -> Unit,
|
||||
onOpenSettings: () -> Unit,
|
||||
onOpenAbout: () -> Unit,
|
||||
onSwitchProvider: () -> Unit,
|
||||
onEndSession: () -> Unit,
|
||||
onForgetCredentials: () -> Unit,
|
||||
@@ -113,6 +115,13 @@ fun AccountProviderDialog(
|
||||
tag = "account_dialog_open_settings",
|
||||
onClick = onOpenSettings,
|
||||
)
|
||||
AccountSheetAction(
|
||||
icon = Icons.Outlined.Info,
|
||||
label = stringResource(R.string.account_about),
|
||||
description = stringResource(R.string.account_about_desc),
|
||||
tag = "account_dialog_open_about",
|
||||
onClick = onOpenAbout,
|
||||
)
|
||||
AccountSheetAction(
|
||||
icon = Icons.AutoMirrored.Outlined.Logout,
|
||||
label = stringResource(R.string.account_logout),
|
||||
|
||||
@@ -348,6 +348,25 @@
|
||||
<string name="account_logout">退出当前会话</string>
|
||||
<string name="account_logout_desc">结束当前连接并返回登录流程</string>
|
||||
<string name="account_logout_confirm">确定退出当前会话吗?</string>
|
||||
<string name="account_about">关于</string>
|
||||
<string name="account_about_desc">App 信息、版本与开发团队</string>
|
||||
<string name="about_title">关于</string>
|
||||
<string name="about_tagline">面向 Android 的 ABox 智能家居管理应用</string>
|
||||
<string name="about_app_info">App 信息</string>
|
||||
<string name="about_version">版本</string>
|
||||
<string name="about_package">应用包名</string>
|
||||
<string name="about_platform">平台</string>
|
||||
<string name="about_platform_value">Android 手机与平板</string>
|
||||
<string name="about_product">产品介绍</string>
|
||||
<string name="about_product_desc">ABox 家庭通过 ABSDK 连接 ABox 网关,提供设备控制、红外遥控器、情景、自动化与活动记录。本地体验提供独立的模拟环境,不使用 ABox 凭据。</string>
|
||||
<string name="about_team">开发团队</string>
|
||||
<string name="about_role_foundation">项目架构与全模块开发</string>
|
||||
<string name="about_role_devices">设备目录与导航</string>
|
||||
<string name="about_role_environment">插座与环境传感器</string>
|
||||
<string name="about_role_infrared">红外遥控器</string>
|
||||
<string name="about_role_scenes">情景与应用集成</string>
|
||||
<string name="about_copyright">由 Flagship 开发团队共同构建。</string>
|
||||
<string name="about_unknown">未知</string>
|
||||
<string name="scene_create_sheet_title">新建指令</string>
|
||||
<string name="scene_create_sheet_description">选择指令的启动方式</string>
|
||||
<string name="scene_create_manual_description">手动点击运行,适合常用的一键设备组合。</string>
|
||||
|
||||
@@ -348,6 +348,25 @@
|
||||
<string name="account_logout">Sign out</string>
|
||||
<string name="account_logout_desc">End the current connection and return to sign-in</string>
|
||||
<string name="account_logout_confirm">Sign out of the current session?</string>
|
||||
<string name="account_about">About</string>
|
||||
<string name="account_about_desc">App information, version, and development team</string>
|
||||
<string name="about_title">About</string>
|
||||
<string name="about_tagline">ABox smart home management for Android</string>
|
||||
<string name="about_app_info">App information</string>
|
||||
<string name="about_version">Version</string>
|
||||
<string name="about_package">Package</string>
|
||||
<string name="about_platform">Platform</string>
|
||||
<string name="about_platform_value">Android phones and tablets</string>
|
||||
<string name="about_product">Product</string>
|
||||
<string name="about_product_desc">ABox Home connects to an ABox gateway through ABSDK and provides device control, infrared remotes, scenes, automation, and activity history. Local Experience offers an isolated simulation environment without using ABox credentials.</string>
|
||||
<string name="about_team">Development team</string>
|
||||
<string name="about_role_foundation">Project architecture and development across all modules</string>
|
||||
<string name="about_role_devices">Device catalog and navigation</string>
|
||||
<string name="about_role_environment">Socket and environment sensors</string>
|
||||
<string name="about_role_infrared">Infrared remotes</string>
|
||||
<string name="about_role_scenes">Scenes and integration</string>
|
||||
<string name="about_copyright">Developed by Flagship.</string>
|
||||
<string name="about_unknown">Unknown</string>
|
||||
<string name="scene_create_sheet_title">New command</string>
|
||||
<string name="scene_create_sheet_description">Choose how the command starts</string>
|
||||
<string name="scene_create_manual_description">Run manually for frequently used device combinations.</string>
|
||||
|
||||
Reference in New Issue
Block a user