feat: 复刻小程序四大主页面与底部导航(UI 优先,本地 mock)

This commit is contained in:
AndyChen1024
2026-09-09 19:57:47 +08:00
parent f240682281
commit c80fb31eb1
53 changed files with 2778 additions and 68 deletions

View File

@@ -15,9 +15,11 @@ android {
dependencies {
implementation(project(":core:designsystem"))
implementation(project(":core:ui"))
implementation(project(":core:model"))
implementation(project(":core:data"))
implementation(project(":feature:auth"))
implementation(project(":feature:home"))
implementation(project(":feature:checkin"))
implementation(project(":feature:plan"))
implementation(project(":feature:course"))
@@ -25,6 +27,7 @@ dependencies {
implementation(project(":feature:feedback"))
implementation(project(":feature:profile"))
implementation(libs.androidx.compose.material.icons.core)
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.lifecycle.runtime.ktx)

View File

@@ -1,41 +1,126 @@
package com.mingyuan.android_client
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Icon
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.NavigationBarItemDefaults
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.Color
import androidx.navigation.NavGraph.Companion.findStartDestination
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 com.mingyuan.android_client.feature.auth.AUTH_ROUTE
import com.mingyuan.android_client.feature.auth.AuthScreen
import androidx.navigation.navArgument
import com.mingyuan.android_client.core.designsystem.theme.TabActive
import com.mingyuan.android_client.core.designsystem.theme.TabBackground
import com.mingyuan.android_client.core.designsystem.theme.TabInactive
import com.mingyuan.android_client.feature.checkin.CHECKIN_ROUTE
import com.mingyuan.android_client.feature.checkin.CheckinScreen
import com.mingyuan.android_client.feature.course.COURSE_ROUTE
import com.mingyuan.android_client.feature.course.CourseScreen
import com.mingyuan.android_client.feature.feedback.FEEDBACK_ROUTE
import com.mingyuan.android_client.feature.feedback.FeedbackScreen
import com.mingyuan.android_client.feature.home.HOME_ROUTE
import com.mingyuan.android_client.feature.home.HomeScreen
import com.mingyuan.android_client.feature.plan.PLAN_ROUTE
import com.mingyuan.android_client.feature.plan.PLAN_SUBMIT_ROUTE
import com.mingyuan.android_client.feature.plan.PlanScreen
import com.mingyuan.android_client.feature.plan.PlanSubmitScreen
import com.mingyuan.android_client.feature.plan.planSubmitRoute
import com.mingyuan.android_client.feature.profile.PROFILE_ROUTE
import com.mingyuan.android_client.feature.profile.ProfileScreen
import com.mingyuan.android_client.feature.survey.SURVEY_ROUTE
import com.mingyuan.android_client.feature.survey.SurveyScreen
/**
* The single navigation graph for the app. Each feature exposes its own route constant and
* screen composable; this host wires them together (see docs/architecture.md).
* The single navigation graph for the app. Each feature exposes its own route constant and screen
* composable; this host wires them together (see docs/architecture.md).
*/
@Composable
fun AppNavHost() {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = CHECKIN_ROUTE,
) {
composable(AUTH_ROUTE) { AuthScreen() }
composable(CHECKIN_ROUTE) { CheckinScreen() }
composable(PLAN_ROUTE) { PlanScreen() }
composable(COURSE_ROUTE) { CourseScreen() }
composable(SURVEY_ROUTE) { SurveyScreen() }
composable(FEEDBACK_ROUTE) { FeedbackScreen() }
composable(PROFILE_ROUTE) { ProfileScreen() }
val backStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = backStackEntry?.destination?.route
val topLevelRoutes = TopLevelDestination.entries.map { it.route }.toSet()
val navigateToTab: (String) -> Unit = { route ->
navController.navigate(route) {
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
}
Scaffold(
containerColor = TabBackground,
bottomBar = {
if (currentRoute in topLevelRoutes) {
MingyuanBottomBar(
currentRoute = currentRoute,
onNavigate = navigateToTab,
)
}
},
) { innerPadding ->
NavHost(
navController = navController,
startDestination = HOME_ROUTE,
modifier = Modifier.padding(innerPadding),
) {
composable(HOME_ROUTE) {
HomeScreen(
onNavigateToCheckin = { navigateToTab(CHECKIN_ROUTE) },
onNavigateToPlan = { navigateToTab(PLAN_ROUTE) },
)
}
composable(CHECKIN_ROUTE) { CheckinScreen() }
composable(PLAN_ROUTE) {
PlanScreen(
onNavigateToPlanSubmit = { type, periodKey ->
navController.navigate(planSubmitRoute(type, periodKey))
},
)
}
composable(
route = PLAN_SUBMIT_ROUTE,
arguments = listOf(
navArgument("planType") { type = NavType.StringType },
navArgument("periodKey") {
type = NavType.StringType
defaultValue = ""
},
),
) {
PlanSubmitScreen(onBack = { navController.popBackStack() })
}
composable(PROFILE_ROUTE) { ProfileScreen() }
}
}
}
@Composable
private fun MingyuanBottomBar(
currentRoute: String?,
onNavigate: (String) -> Unit,
) {
NavigationBar(containerColor = TabBackground) {
TopLevelDestination.entries.forEach { destination ->
NavigationBarItem(
selected = currentRoute == destination.route,
onClick = { onNavigate(destination.route) },
icon = { Icon(destination.icon, contentDescription = destination.label) },
label = { Text(destination.label) },
colors = NavigationBarItemDefaults.colors(
selectedIconColor = TabActive,
selectedTextColor = TabActive,
unselectedIconColor = TabInactive,
unselectedTextColor = TabInactive,
indicatorColor = Color.Transparent,
),
)
}
}
}

View File

@@ -5,7 +5,9 @@ import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import com.mingyuan.android_client.core.designsystem.theme.MingyuanTheme
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

View File

@@ -0,0 +1,24 @@
package com.mingyuan.android_client
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.CheckCircle
import androidx.compose.material.icons.filled.DateRange
import androidx.compose.material.icons.filled.Home
import androidx.compose.material.icons.filled.Person
import androidx.compose.ui.graphics.vector.ImageVector
import com.mingyuan.android_client.feature.checkin.CHECKIN_ROUTE
import com.mingyuan.android_client.feature.home.HOME_ROUTE
import com.mingyuan.android_client.feature.plan.PLAN_ROUTE
import com.mingyuan.android_client.feature.profile.PROFILE_ROUTE
/** The four top-level tabs shown in the bottom navigation bar. */
enum class TopLevelDestination(
val route: String,
val label: String,
val icon: ImageVector,
) {
HOME(HOME_ROUTE, "首页", Icons.Filled.Home),
CHECKIN(CHECKIN_ROUTE, "打卡", Icons.Filled.CheckCircle),
PLAN(PLAN_ROUTE, "计划", Icons.Filled.DateRange),
PROFILE(PROFILE_ROUTE, "我的", Icons.Filled.Person),
}