From 6d3cb1b4184d5f72cfc25253153dddfb0db5b557 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E5=B4=94=E9=B2=81=E6=AD=A3?= <13780741225@163.com>
Date: Thu, 10 Sep 2026 10:54:05 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E5=BA=95=E9=83=A8?=
=?UTF-8?q?=E5=9B=9B=20Tab=20=E5=AF=BC=E8=88=AA=E9=AA=A8=E6=9E=B6=E4=B8=8E?=
=?UTF-8?q?=E8=B7=AF=E7=94=B1=E8=A1=A8?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../placeholder/ringhealth/MainActivity.kt | 11 +-
.../core/navigation/RingHealthApp.kt | 117 ++++++++++++++++++
.../ringhealth/core/navigation/Routes.kt | 23 ++++
.../feature/detail/MetricDetailScreen.kt | 19 +++
.../ringhealth/feature/device/DeviceScreen.kt | 18 +++
.../feature/device/DeviceSettingsScreen.kt | 18 +++
.../ringhealth/feature/home/HomeScreen.kt | 22 ++++
.../feature/profile/ProfileScreen.kt | 18 +++
.../feature/workout/WorkoutHistoryScreen.kt | 18 +++
.../feature/workout/WorkoutScreen.kt | 18 +++
app/src/main/res/values/strings.xml | 8 ++
11 files changed, 282 insertions(+), 8 deletions(-)
create mode 100644 app/src/main/java/com/placeholder/ringhealth/core/navigation/RingHealthApp.kt
create mode 100644 app/src/main/java/com/placeholder/ringhealth/core/navigation/Routes.kt
create mode 100644 app/src/main/java/com/placeholder/ringhealth/feature/detail/MetricDetailScreen.kt
create mode 100644 app/src/main/java/com/placeholder/ringhealth/feature/device/DeviceScreen.kt
create mode 100644 app/src/main/java/com/placeholder/ringhealth/feature/device/DeviceSettingsScreen.kt
create mode 100644 app/src/main/java/com/placeholder/ringhealth/feature/home/HomeScreen.kt
create mode 100644 app/src/main/java/com/placeholder/ringhealth/feature/profile/ProfileScreen.kt
create mode 100644 app/src/main/java/com/placeholder/ringhealth/feature/workout/WorkoutHistoryScreen.kt
create mode 100644 app/src/main/java/com/placeholder/ringhealth/feature/workout/WorkoutScreen.kt
diff --git a/app/src/main/java/com/placeholder/ringhealth/MainActivity.kt b/app/src/main/java/com/placeholder/ringhealth/MainActivity.kt
index 724daf1..f6fef30 100644
--- a/app/src/main/java/com/placeholder/ringhealth/MainActivity.kt
+++ b/app/src/main/java/com/placeholder/ringhealth/MainActivity.kt
@@ -4,15 +4,12 @@ import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
-import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
-import androidx.compose.material3.Text
-import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.stringResource
import com.placeholder.ringhealth.core.designsystem.RingHealthTheme
+import com.placeholder.ringhealth.core.navigation.RingHealthApp
/** 唯一 Activity,承载 Compose 界面。 */
class MainActivity : ComponentActivity() {
@@ -25,10 +22,8 @@ class MainActivity : ComponentActivity() {
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background,
) {
- Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
- // 文案取自 res/values/strings.xml,不在 Kotlin 里硬编码用户可见文字
- Text(stringResource(R.string.app_name))
- }
+ // 界面骨架(底部导航 + 路由)在 core/navigation/RingHealthApp.kt
+ RingHealthApp()
}
}
}
diff --git a/app/src/main/java/com/placeholder/ringhealth/core/navigation/RingHealthApp.kt b/app/src/main/java/com/placeholder/ringhealth/core/navigation/RingHealthApp.kt
new file mode 100644
index 0000000..5bf619b
--- /dev/null
+++ b/app/src/main/java/com/placeholder/ringhealth/core/navigation/RingHealthApp.kt
@@ -0,0 +1,117 @@
+package com.placeholder.ringhealth.core.navigation
+
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.FitnessCenter
+import androidx.compose.material.icons.filled.Home
+import androidx.compose.material.icons.filled.Person
+import androidx.compose.material.icons.filled.Settings
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.NavigationBar
+import androidx.compose.material3.NavigationBarItem
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.res.stringResource
+import androidx.navigation.NavGraph.Companion.findStartDestination
+import androidx.navigation.NavHostController
+import androidx.navigation.NavType
+import androidx.navigation.compose.NavHost
+import androidx.navigation.compose.composable
+import androidx.navigation.compose.currentBackStackEntryAsState
+import androidx.navigation.compose.rememberNavController
+import androidx.navigation.navArgument
+import com.placeholder.ringhealth.R
+import com.placeholder.ringhealth.domain.model.MetricType
+import com.placeholder.ringhealth.feature.device.DeviceScreen
+import com.placeholder.ringhealth.feature.device.DeviceSettingsPlaceholder
+import com.placeholder.ringhealth.feature.detail.MetricDetailPlaceholder
+import com.placeholder.ringhealth.feature.home.HomeScreen
+import com.placeholder.ringhealth.feature.profile.ProfileScreen
+import com.placeholder.ringhealth.feature.workout.WorkoutHistoryPlaceholder
+import com.placeholder.ringhealth.feature.workout.WorkoutScreen
+
+/** 底部导航项定义。 */
+private data class BottomTab(
+ val route: String,
+ val labelRes: Int,
+ val icon: ImageVector,
+)
+
+private val BOTTOM_TABS = listOf(
+ BottomTab(Routes.HOME, R.string.tab_home, Icons.Filled.Home),
+ BottomTab(Routes.WORKOUT, R.string.tab_workout, Icons.Filled.FitnessCenter),
+ BottomTab(Routes.DEVICE, R.string.tab_device, Icons.Filled.Settings),
+ BottomTab(Routes.PROFILE, R.string.tab_profile, Icons.Filled.Person),
+)
+
+/**
+ * 应用主界面:Scaffold + 底部导航 + NavHost。
+ */
+@Composable
+fun RingHealthApp(navController: NavHostController = rememberNavController()) {
+ val backStackEntry by navController.currentBackStackEntryAsState()
+ val currentRoute = backStackEntry?.destination?.route
+
+ Scaffold(
+ containerColor = MaterialTheme.colorScheme.background,
+ bottomBar = {
+ NavigationBar(containerColor = MaterialTheme.colorScheme.surface) {
+ BOTTOM_TABS.forEach { tab ->
+ NavigationBarItem(
+ selected = currentRoute == tab.route,
+ onClick = { navController.navigateToTab(tab.route) },
+ icon = { Icon(imageVector = tab.icon, contentDescription = null) },
+ label = { Text(stringResource(tab.labelRes)) },
+ )
+ }
+ }
+ },
+ ) { innerPadding ->
+ NavHost(
+ navController = navController,
+ startDestination = Routes.HOME,
+ modifier = Modifier.padding(innerPadding),
+ ) {
+ composable(Routes.HOME) {
+ HomeScreen(onOpenDetail = { metric -> navController.navigate(Routes.detail(metric)) })
+ }
+ composable(Routes.WORKOUT) {
+ WorkoutScreen()
+ }
+ composable(Routes.DEVICE) {
+ DeviceScreen()
+ }
+ composable(Routes.PROFILE) {
+ ProfileScreen()
+ }
+ composable(Routes.DEVICE_SETTINGS) {
+ DeviceSettingsPlaceholder()
+ }
+ composable(Routes.WORKOUT_HISTORY) {
+ WorkoutHistoryPlaceholder()
+ }
+ composable(
+ route = Routes.DETAIL_PATTERN,
+ arguments = listOf(navArgument(Routes.DETAIL_ARG_METRIC) { type = NavType.StringType }),
+ ) { entry ->
+ val raw = entry.arguments?.getString(Routes.DETAIL_ARG_METRIC).orEmpty()
+ val metric = MetricType.entries.firstOrNull { it.name == raw } ?: MetricType.STEP
+ MetricDetailPlaceholder(metric = metric)
+ }
+ }
+ }
+}
+
+/** 底部 Tab 切换:保留状态、避免回退栈堆积。 */
+private fun NavHostController.navigateToTab(route: String) {
+ navigate(route) {
+ popUpTo(graph.findStartDestination().id) { saveState = true }
+ launchSingleTop = true
+ restoreState = true
+ }
+}
diff --git a/app/src/main/java/com/placeholder/ringhealth/core/navigation/Routes.kt b/app/src/main/java/com/placeholder/ringhealth/core/navigation/Routes.kt
new file mode 100644
index 0000000..87bac9c
--- /dev/null
+++ b/app/src/main/java/com/placeholder/ringhealth/core/navigation/Routes.kt
@@ -0,0 +1,23 @@
+package com.placeholder.ringhealth.core.navigation
+
+import com.placeholder.ringhealth.domain.model.MetricType
+
+/**
+ * 全局路由表。字符串集中定义,避免散落在各处。
+ */
+object Routes {
+ const val HOME = "home"
+ const val WORKOUT = "workout"
+ const val DEVICE = "device"
+ const val PROFILE = "profile"
+
+ const val DEVICE_SETTINGS = "device/settings"
+ const val WORKOUT_HISTORY = "workout/history"
+
+ /** 指标详情路由模板,参数为 MetricType.name。 */
+ const val DETAIL_PATTERN = "detail/{metric}"
+ const val DETAIL_ARG_METRIC = "metric"
+
+ /** 拼出某个指标的详情路由。 */
+ fun detail(metric: MetricType): String = "detail/${metric.name}"
+}
diff --git a/app/src/main/java/com/placeholder/ringhealth/feature/detail/MetricDetailScreen.kt b/app/src/main/java/com/placeholder/ringhealth/feature/detail/MetricDetailScreen.kt
new file mode 100644
index 0000000..62e2e6d
--- /dev/null
+++ b/app/src/main/java/com/placeholder/ringhealth/feature/detail/MetricDetailScreen.kt
@@ -0,0 +1,19 @@
+package com.placeholder.ringhealth.feature.detail
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import com.placeholder.ringhealth.R
+import com.placeholder.ringhealth.domain.model.MetricType
+
+/** 指标详情子页(占位,待 Task 12 实现)。 */
+@Composable
+fun MetricDetailPlaceholder(metric: MetricType, modifier: Modifier = Modifier) {
+ Box(modifier = modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
+ Text(stringResource(R.string.placeholder_metric_detail))
+ }
+}
diff --git a/app/src/main/java/com/placeholder/ringhealth/feature/device/DeviceScreen.kt b/app/src/main/java/com/placeholder/ringhealth/feature/device/DeviceScreen.kt
new file mode 100644
index 0000000..31f494f
--- /dev/null
+++ b/app/src/main/java/com/placeholder/ringhealth/feature/device/DeviceScreen.kt
@@ -0,0 +1,18 @@
+package com.placeholder.ringhealth.feature.device
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import com.placeholder.ringhealth.R
+
+/** 设备页(占位,待 Task 10 实现)。 */
+@Composable
+fun DeviceScreen(modifier: Modifier = Modifier) {
+ Box(modifier = modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
+ Text(stringResource(R.string.tab_device))
+ }
+}
diff --git a/app/src/main/java/com/placeholder/ringhealth/feature/device/DeviceSettingsScreen.kt b/app/src/main/java/com/placeholder/ringhealth/feature/device/DeviceSettingsScreen.kt
new file mode 100644
index 0000000..813ec9e
--- /dev/null
+++ b/app/src/main/java/com/placeholder/ringhealth/feature/device/DeviceSettingsScreen.kt
@@ -0,0 +1,18 @@
+package com.placeholder.ringhealth.feature.device
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import com.placeholder.ringhealth.R
+
+/** 设备设置子页(占位,待 Task 10 实现)。 */
+@Composable
+fun DeviceSettingsPlaceholder(modifier: Modifier = Modifier) {
+ Box(modifier = modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
+ Text(stringResource(R.string.placeholder_device_settings))
+ }
+}
diff --git a/app/src/main/java/com/placeholder/ringhealth/feature/home/HomeScreen.kt b/app/src/main/java/com/placeholder/ringhealth/feature/home/HomeScreen.kt
new file mode 100644
index 0000000..249bc6f
--- /dev/null
+++ b/app/src/main/java/com/placeholder/ringhealth/feature/home/HomeScreen.kt
@@ -0,0 +1,22 @@
+package com.placeholder.ringhealth.feature.home
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import com.placeholder.ringhealth.R
+import com.placeholder.ringhealth.domain.model.MetricType
+
+/** 健康首页(占位,待 Task 8 实现)。 */
+@Composable
+fun HomeScreen(
+ onOpenDetail: (MetricType) -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ Box(modifier = modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
+ Text(stringResource(R.string.tab_home))
+ }
+}
diff --git a/app/src/main/java/com/placeholder/ringhealth/feature/profile/ProfileScreen.kt b/app/src/main/java/com/placeholder/ringhealth/feature/profile/ProfileScreen.kt
new file mode 100644
index 0000000..1848493
--- /dev/null
+++ b/app/src/main/java/com/placeholder/ringhealth/feature/profile/ProfileScreen.kt
@@ -0,0 +1,18 @@
+package com.placeholder.ringhealth.feature.profile
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import com.placeholder.ringhealth.R
+
+/** 我的页(占位,待 Task 11 实现)。 */
+@Composable
+fun ProfileScreen(modifier: Modifier = Modifier) {
+ Box(modifier = modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
+ Text(stringResource(R.string.tab_profile))
+ }
+}
diff --git a/app/src/main/java/com/placeholder/ringhealth/feature/workout/WorkoutHistoryScreen.kt b/app/src/main/java/com/placeholder/ringhealth/feature/workout/WorkoutHistoryScreen.kt
new file mode 100644
index 0000000..eb31962
--- /dev/null
+++ b/app/src/main/java/com/placeholder/ringhealth/feature/workout/WorkoutHistoryScreen.kt
@@ -0,0 +1,18 @@
+package com.placeholder.ringhealth.feature.workout
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import com.placeholder.ringhealth.R
+
+/** 运动历史子页(占位,待 Task 9 实现)。 */
+@Composable
+fun WorkoutHistoryPlaceholder(modifier: Modifier = Modifier) {
+ Box(modifier = modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
+ Text(stringResource(R.string.placeholder_workout_history))
+ }
+}
diff --git a/app/src/main/java/com/placeholder/ringhealth/feature/workout/WorkoutScreen.kt b/app/src/main/java/com/placeholder/ringhealth/feature/workout/WorkoutScreen.kt
new file mode 100644
index 0000000..2d20585
--- /dev/null
+++ b/app/src/main/java/com/placeholder/ringhealth/feature/workout/WorkoutScreen.kt
@@ -0,0 +1,18 @@
+package com.placeholder.ringhealth.feature.workout
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import com.placeholder.ringhealth.R
+
+/** 运动页(占位,待 Task 9 实现)。 */
+@Composable
+fun WorkoutScreen(modifier: Modifier = Modifier) {
+ Box(modifier = modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
+ Text(stringResource(R.string.tab_workout))
+ }
+}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index fa4e8ff..001209e 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -6,4 +6,12 @@
正常
中等
偏高
+
+ 健康
+ 运动
+ 设备
+ 我的
+ 设备设置
+ 运动历史
+ 指标详情