feat: 新增底部四 Tab 导航骨架与路由表
This commit is contained in:
@@ -4,15 +4,12 @@ import android.os.Bundle
|
|||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import com.placeholder.ringhealth.core.designsystem.RingHealthTheme
|
import com.placeholder.ringhealth.core.designsystem.RingHealthTheme
|
||||||
|
import com.placeholder.ringhealth.core.navigation.RingHealthApp
|
||||||
|
|
||||||
/** 唯一 Activity,承载 Compose 界面。 */
|
/** 唯一 Activity,承载 Compose 界面。 */
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
@@ -25,10 +22,8 @@ class MainActivity : ComponentActivity() {
|
|||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
color = MaterialTheme.colorScheme.background,
|
color = MaterialTheme.colorScheme.background,
|
||||||
) {
|
) {
|
||||||
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
// 界面骨架(底部导航 + 路由)在 core/navigation/RingHealthApp.kt
|
||||||
// 文案取自 res/values/strings.xml,不在 Kotlin 里硬编码用户可见文字
|
RingHealthApp()
|
||||||
Text(stringResource(R.string.app_name))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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}"
|
||||||
|
}
|
||||||
@@ -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))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,4 +6,12 @@
|
|||||||
<string name="stress_normal">正常</string>
|
<string name="stress_normal">正常</string>
|
||||||
<string name="stress_medium">中等</string>
|
<string name="stress_medium">中等</string>
|
||||||
<string name="stress_high">偏高</string>
|
<string name="stress_high">偏高</string>
|
||||||
|
|
||||||
|
<string name="tab_home">健康</string>
|
||||||
|
<string name="tab_workout">运动</string>
|
||||||
|
<string name="tab_device">设备</string>
|
||||||
|
<string name="tab_profile">我的</string>
|
||||||
|
<string name="placeholder_device_settings">设备设置</string>
|
||||||
|
<string name="placeholder_workout_history">运动历史</string>
|
||||||
|
<string name="placeholder_metric_detail">指标详情</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
Reference in New Issue
Block a user