Kaynağa Gözat

#2 抽象为通用组件

ValiZhang 11 ay önce
ebeveyn
işleme
e4c3cd836b

+ 3 - 0
.idea/deploymentTargetSelector.xml

@@ -13,6 +13,9 @@
         </DropdownSelection>
         <DialogSelection />
       </SelectionState>
+      <SelectionState runConfigName="PreviewBottomNavBar">
+        <option name="selectionMode" value="DROPDOWN" />
+      </SelectionState>
     </selectionStates>
   </component>
 </project>

+ 1 - 0
app/build.gradle.kts

@@ -50,6 +50,7 @@ dependencies {
     implementation(libs.androidx.ui.tooling.preview)
     implementation(libs.androidx.material3)
     implementation(libs.androidx.navigation.compose.android)
+    implementation(libs.androidx.material.icons.extended)
     testImplementation(libs.junit)
     androidTestImplementation(libs.androidx.junit)
     androidTestImplementation(libs.androidx.espresso.core)

+ 30 - 122
app/src/main/java/com/lostc/japp/MainActivity.kt

@@ -1,36 +1,23 @@
 package com.lostc.japp
 
 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.foundation.layout.padding
+import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Surface
 import androidx.compose.material3.Text
-import androidx.compose.material3.NavigationBar
-import androidx.compose.material3.NavigationBarDefaults
-import androidx.compose.material3.NavigationBarItem
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.tooling.preview.Preview
-import androidx.compose.ui.Alignment
-import androidx.navigation.compose.NavHost
-import androidx.navigation.compose.composable
-import androidx.compose.material3.Icon
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Home
-import androidx.compose.material.icons.filled.Notifications
-import androidx.compose.material.icons.filled.Place
 import androidx.navigation.compose.rememberNavController
-import androidx.compose.runtime.mutableIntStateOf
-import androidx.compose.runtime.saveable.rememberSaveable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.graphics.vector.ImageVector
-import androidx.navigation.NavHostController
+import com.lostc.japp.interfaces.Navigator
+import com.lostc.japp.interfaces.RealNavigator
+import com.lostc.japp.navigation.NavGraph
+import com.lostc.japp.ui.components.BottomNavBar
 import com.lostc.japp.ui.theme.JAPPTheme
 
 class MainActivity : ComponentActivity() {
@@ -39,127 +26,48 @@ class MainActivity : ComponentActivity() {
         enableEdgeToEdge()
         setContent {
             JAPPTheme {
-                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
-                    NavigationBarExample(
-                        modifier = Modifier.padding(innerPadding)
-                    )
+                Surface(
+                    modifier = Modifier.fillMaxSize(),
+                    color = MaterialTheme.colorScheme.background
+                ) {
+                    MainApp()
                 }
             }
         }
     }
 }
 
-@Composable
-fun Greeting(name: String, modifier: Modifier = Modifier) {
-    Text(
-        text = "Hello $name!",
-        modifier = modifier
-    )
-}
-
-
-// ====
-
-enum class Destination(
-    val route: String,
-    val label: String,
-    val icon: ImageVector,
-    val contentDescription: String
-) {
-    SONGS("songs", "Songs", Icons.Default.Notifications, "Songs"),
-    ALBUM("album", "Album", Icons.Default.Home, "Album"),
-    PLAYLISTS("playlist", "Playlist", Icons.Default.Place, "Playlist")
-}
 
 @Composable
-fun AppNavHost(
-    navController: NavHostController,
-    startDestination: Destination,
+fun MainApp(
+    navigator: Navigator = RealNavigator(rememberNavController()),
     modifier: Modifier = Modifier
 ) {
-    NavHost(
-        navController,
-        startDestination = startDestination.route
-    ) {
-        Destination.entries.forEach { destination ->
-            composable(destination.route) {
-                when (destination) {
-                    Destination.SONGS -> SongsScreen()
-                    Destination.ALBUM -> AlbumScreen()
-                    Destination.PLAYLISTS -> PlaylistScreen()
-                }
-            }
-        }
-    }
-}
-
-@Composable
-fun SongsScreen(modifier: Modifier = Modifier) {
-    Box(
-        modifier = Modifier.fillMaxSize(),
-        contentAlignment = Alignment.Center
-    ) {
-        Text("Songs Screen")
-    }
-}
-
-@Composable
-fun AlbumScreen(modifier: Modifier = Modifier) {
-    Box(
-        modifier = Modifier.fillMaxSize(),
-        contentAlignment = Alignment.Center
-    ) {
-        Text("Album Screen")
-    }
-}
+    println("NavController initialized: $navigator") // 调试日志
 
-@Composable
-fun PlaylistScreen(modifier: Modifier = Modifier) {
-    Box(
-        modifier = Modifier.fillMaxSize(),
-        contentAlignment = Alignment.Center
-    ) {
-        Text("Playlist Screen")
+    Scaffold(
+        bottomBar = {
+            BottomNavBar(navigator = navigator)
+        },
+        modifier = modifier.fillMaxSize()
+    ) { paddingValues ->
+        NavGraph(
+            navigator = navigator,
+            modifier = Modifier.padding(paddingValues)
+        )
     }
 }
 
 
 @Composable
-fun NavigationBarExample(modifier: Modifier = Modifier) {
-    val navController = rememberNavController()
-    val startDestination = Destination.SONGS
-    var selectedDestination by rememberSaveable { mutableIntStateOf(startDestination.ordinal) }
-
-    Scaffold(
-        modifier = modifier,
-        bottomBar = {
-            NavigationBar(windowInsets = NavigationBarDefaults.windowInsets) {
-                Destination.entries.forEachIndexed { index, destination ->
-                    NavigationBarItem(
-                        selected = selectedDestination == index,
-                        onClick = {
-                            navController.navigate(route = destination.route)
-                            selectedDestination = index
-                        },
-                        icon = {
-                            Icon(
-                                destination.icon,
-                                contentDescription = destination.contentDescription
-                            )
-                        },
-                        label = { Text(destination.label) }
-                    )
-                }
-            }
-        }
-    ) { contentPadding ->
-        AppNavHost(navController, startDestination, modifier = Modifier.padding(contentPadding))
-    }
+fun Greeting(name: String, modifier: Modifier = Modifier) {
+    Text(
+        text = "Hello $name!",
+        modifier = modifier
+    )
 }
 
-// ===
-
-@Preview(showBackground = true)
+@Preview
 @Composable
 fun GreetingPreview() {
     JAPPTheme {

+ 6 - 0
app/src/main/java/com/lostc/japp/interfaces/Navigator.kt

@@ -0,0 +1,6 @@
+package com.lostc.japp.interfaces
+
+interface Navigator {
+    fun navigateTo(route: String)
+    val currentRoute: String?
+}

+ 31 - 0
app/src/main/java/com/lostc/japp/interfaces/RealNavigator.kt

@@ -0,0 +1,31 @@
+package com.lostc.japp.interfaces
+
+
+import androidx.navigation.NavHostController
+import androidx.navigation.NavGraph.Companion.findStartDestination
+
+class RealNavigator(
+    val navController: NavHostController
+) : Navigator {
+    override fun navigateTo(route: String) {
+        navController.navigate(route) {
+            popUpTo(navController.graph.findStartDestination().id) {
+                saveState = true
+            }
+            launchSingleTop = true
+            restoreState = true
+        }
+    }
+
+    override val currentRoute: String?
+        get() = navController.currentBackStackEntry?.destination?.route
+}
+
+// 预览导航实现
+object PreviewNavigator : Navigator {
+    override fun navigateTo(route: String) {
+        println("[Preview] Navigating to $route")
+    }
+
+    override val currentRoute: String? = "home"
+}

+ 2 - 0
app/src/main/java/com/lostc/japp/navigation/Destinations.kt

@@ -0,0 +1,2 @@
+package com.lostc.japp.navigation
+

+ 41 - 0
app/src/main/java/com/lostc/japp/navigation/NavGraph.kt

@@ -0,0 +1,41 @@
+package com.lostc.japp.navigation
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.Alignment
+import androidx.navigation.compose.NavHost
+import androidx.navigation.compose.composable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.material3.Text
+import com.lostc.japp.interfaces.Navigator
+import com.lostc.japp.interfaces.RealNavigator
+import com.lostc.japp.ui.features.card.CardListScreen
+import com.lostc.japp.ui.features.challenge.ChallengeListScreen
+import com.lostc.japp.ui.features.home.HomeScreen
+import com.lostc.japp.ui.features.profile.ProfileScreen
+
+@Composable
+fun NavGraph(
+    navigator: Navigator,
+    modifier: Modifier = Modifier
+) {
+    if (navigator is RealNavigator) {
+        NavHost(
+            navController = navigator.navController,
+            startDestination = Screen.Home.route,
+            modifier = modifier.fillMaxSize()
+        ) {
+            composable(Screen.Home.route) { HomeScreen() }
+            composable(Screen.Card.route) { CardListScreen() }
+            composable(Screen.Challenge.route) { ChallengeListScreen() }
+            composable(Screen.Profile.route) { ProfileScreen() }
+        }
+    } else {
+        Box(
+            modifier = modifier.fillMaxSize(),
+            contentAlignment = Alignment.Center
+        ) {
+            Text("Navigation Preview")
+        }
+    }
+}

+ 45 - 0
app/src/main/java/com/lostc/japp/navigation/Routes.kt

@@ -0,0 +1,45 @@
+package com.lostc.japp.navigation
+
+import androidx.compose.runtime.Composable
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.EmojiEvents
+import androidx.compose.material.icons.filled.Home
+import androidx.compose.material.icons.filled.Person
+import androidx.compose.material.icons.filled.TypeSpecimen
+import androidx.compose.material3.Icon
+
+sealed class Screen(
+    val route: String,
+    val title: String,
+    val icon: @Composable () -> Unit
+) {
+    data object Home : Screen(
+        route = "home",
+        title = "Home",
+        icon = { Icon(Icons.Filled.Home, null) }
+    )
+
+    data object Card : Screen(
+        route = "card",
+        title = "卡片",
+        icon = { Icon(Icons.Filled.TypeSpecimen, null) }
+    )
+
+    data object Challenge : Screen(
+        route = "challenge",
+        title = "挑战",
+        icon = { Icon(Icons.Filled.EmojiEvents, null) }
+    )
+
+    data object Profile : Screen(
+        route = "profile/{userId}",
+        title = "我的",
+        icon = { Icon(Icons.Filled.Person, null) }
+    ) {
+        fun createRoute(userId: String) = "profile/$userId"
+    }
+
+    companion object {
+        val allScreens = listOf(Home, Card, Challenge, Profile)
+    }
+}

+ 45 - 0
app/src/main/java/com/lostc/japp/ui/components/BottomNavBar.kt

@@ -0,0 +1,45 @@
+package com.lostc.japp.ui.components
+
+import androidx.compose.runtime.Composable
+import androidx.navigation.NavHostController
+import androidx.navigation.NavGraph.Companion.findStartDestination
+import androidx.compose.material3.NavigationBar
+import androidx.compose.material3.NavigationBarItem
+import androidx.compose.material3.Text
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.NavigationBarDefaults
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import com.lostc.japp.interfaces.Navigator
+import com.lostc.japp.interfaces.PreviewNavigator
+import com.lostc.japp.navigation.Screen
+
+
+@Composable
+fun BottomNavBar(
+    navigator: Navigator,
+    items: List<Screen> = Screen.allScreens,
+    modifier: Modifier = Modifier
+) {
+    NavigationBar(modifier = modifier) {
+        items.forEach { screen ->
+            NavigationBarItem(
+                icon = screen.icon,
+                label = { Text(screen.title) },
+                selected = navigator.currentRoute?.contains(screen.route.split("/")[0]) == true,
+                onClick = { navigator.navigateTo(screen.route) },
+                alwaysShowLabel = true
+            )
+        }
+    }
+}
+
+
+@Preview
+@Composable
+fun PreviewBottomNavBar() {
+    MaterialTheme {
+        BottomNavBar(navigator = PreviewNavigator)
+    }
+}

+ 40 - 0
app/src/main/java/com/lostc/japp/ui/features/card/CardScreen.kt

@@ -0,0 +1,40 @@
+package com.lostc.japp.ui.features.card
+
+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
+
+
+@Composable
+fun CardCategoryScreen(modifier: Modifier = Modifier) {
+    Box(
+        modifier = Modifier.fillMaxSize(),
+        contentAlignment = Alignment.Center
+    ) {
+        Text("Songs Screen")
+    }
+}
+
+@Composable
+fun CardListScreen(modifier: Modifier = Modifier) {
+    Box(
+        modifier = Modifier.fillMaxSize(),
+        contentAlignment = Alignment.Center
+    ) {
+        Text("Card List Screen")
+    }
+}
+
+
+@Composable
+fun CardDetailScreen(modifier: Modifier = Modifier) {
+    Box(
+        modifier = Modifier.fillMaxSize(),
+        contentAlignment = Alignment.Center
+    ) {
+        Text("Ca Screen")
+    }
+}

+ 18 - 0
app/src/main/java/com/lostc/japp/ui/features/challenge/ChallengeScreen.kt

@@ -0,0 +1,18 @@
+package com.lostc.japp.ui.features.challenge
+
+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
+
+@Composable
+fun ChallengeListScreen(modifier: Modifier = Modifier) {
+    Box(
+        modifier = Modifier.fillMaxSize(),
+        contentAlignment = Alignment.Center
+    ) {
+        Text("Challenge Screen")
+    }
+}

+ 18 - 0
app/src/main/java/com/lostc/japp/ui/features/home/HomeScreen.kt

@@ -0,0 +1,18 @@
+package com.lostc.japp.ui.features.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
+
+@Composable
+fun HomeScreen(modifier: Modifier = Modifier) {
+    Box(
+        modifier = Modifier.fillMaxSize(),
+        contentAlignment = Alignment.Center
+    ) {
+        Text("Home Screen")
+    }
+}

+ 18 - 0
app/src/main/java/com/lostc/japp/ui/features/profile/ProfileScreen.kt

@@ -0,0 +1,18 @@
+package com.lostc.japp.ui.features.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
+
+@Composable
+fun ProfileScreen(modifier: Modifier = Modifier) {
+    Box(
+        modifier = Modifier.fillMaxSize(),
+        contentAlignment = Alignment.Center
+    ) {
+        Text("Profiles Screen")
+    }
+}

+ 1 - 0
gradle/libs.versions.toml

@@ -28,6 +28,7 @@ androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit
 androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
 androidx-navigation-compose-jvmstubs = { group = "androidx.navigation", name = "navigation-compose-jvmstubs", version.ref = "navigationComposeJvmstubs" }
 androidx-navigation-compose-android = { group = "androidx.navigation", name = "navigation-compose-android", version.ref = "navigationComposeAndroid" }
+androidx-material-icons-extended = {  group = "androidx.compose.material", name = "material-icons-extended"}
 
 [plugins]
 android-application = { id = "com.android.application", version.ref = "agp" }