Skip to content

Commit 5e37d6c

Browse files
committed
Improve compose navigation logic and do best practice
1 parent f43ee8a commit 5e37d6c

19 files changed

Lines changed: 487 additions & 448 deletions

File tree

composeApp/src/commonMain/kotlin/com/linuxcommandlibrary/app/App.kt

Lines changed: 21 additions & 398 deletions
Large diffs are not rendered by default.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.linuxcommandlibrary.app
2+
3+
sealed class NavEvent {
4+
data class ToCommand(val commandName: String) : NavEvent()
5+
data class ToBasicGroups(val categoryId: String, val categoryTitle: String) : NavEvent()
6+
data class OpenAction(val action: String) : NavEvent()
7+
}

composeApp/src/commonMain/kotlin/com/linuxcommandlibrary/app/Navigation.kt

Lines changed: 0 additions & 21 deletions
This file was deleted.

composeApp/src/commonMain/kotlin/com/linuxcommandlibrary/app/Route.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ sealed class Route {
1414
data object Tips : Route()
1515

1616
@Serializable
17-
data class BasicGroups(val categoryId: String) : Route()
17+
data class BasicGroups(val categoryId: String, val categoryTitle: String = "") : Route()
1818

1919
@Serializable
2020
data class CommandDetail(val commandName: String) : Route()
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package com.linuxcommandlibrary.app.ui.composables
2+
3+
import androidx.compose.foundation.layout.height
4+
import androidx.compose.foundation.layout.size
5+
import androidx.compose.material3.Icon
6+
import androidx.compose.material3.MaterialTheme
7+
import androidx.compose.material3.NavigationBar
8+
import androidx.compose.material3.NavigationBarItem
9+
import androidx.compose.material3.NavigationBarItemDefaults
10+
import androidx.compose.material3.Text
11+
import androidx.compose.runtime.Composable
12+
import androidx.compose.ui.Modifier
13+
import androidx.compose.ui.graphics.Color
14+
import androidx.compose.ui.input.pointer.PointerIcon
15+
import androidx.compose.ui.input.pointer.pointerHoverIcon
16+
import androidx.compose.ui.unit.dp
17+
import androidx.navigation.NavDestination
18+
import androidx.navigation.NavDestination.Companion.hasRoute
19+
import com.linuxcommandlibrary.app.Route
20+
import com.linuxcommandlibrary.app.ui.theme.LocalCustomColors
21+
22+
private data class BottomTab(
23+
val route: Route,
24+
val title: String,
25+
val icon: AppIcon,
26+
)
27+
28+
private val bottomTabs = listOf(
29+
BottomTab(Route.Basics, "Basics", AppIcon.PUZZLE),
30+
BottomTab(Route.Tips, "Tips", AppIcon.IDEA),
31+
BottomTab(Route.Commands, "Commands", AppIcon.SEARCH),
32+
)
33+
34+
@Composable
35+
fun BottomBar(
36+
currentDestination: NavDestination?,
37+
onSelectTab: (Route) -> Unit,
38+
) {
39+
val itemColors = NavigationBarItemDefaults.colors(
40+
selectedIconColor = MaterialTheme.colorScheme.primary,
41+
selectedTextColor = MaterialTheme.colorScheme.primary,
42+
unselectedIconColor = MaterialTheme.colorScheme.onSurface,
43+
unselectedTextColor = MaterialTheme.colorScheme.onSurface,
44+
indicatorColor = Color.Transparent,
45+
)
46+
47+
NavigationBar(
48+
modifier = Modifier.height(64.dp),
49+
containerColor = LocalCustomColors.current.navBarBackground,
50+
tonalElevation = 0.dp,
51+
) {
52+
bottomTabs.forEach { tab ->
53+
val painter = rememberIconPainter(tab.icon)
54+
val isSelected = when (tab.route) {
55+
Route.Basics -> currentDestination?.hasRoute<Route.Basics>() == true ||
56+
currentDestination?.hasRoute<Route.BasicGroups>() == true
57+
58+
Route.Commands -> currentDestination?.hasRoute<Route.Commands>() == true
59+
60+
Route.Tips -> currentDestination?.hasRoute<Route.Tips>() == true
61+
62+
else -> false
63+
}
64+
NavigationBarItem(
65+
modifier = Modifier.pointerHoverIcon(PointerIcon.Hand),
66+
icon = {
67+
Icon(
68+
painter = painter,
69+
contentDescription = null,
70+
modifier = Modifier.size(24.dp),
71+
)
72+
},
73+
label = { Text(tab.title) },
74+
selected = isSelected,
75+
colors = itemColors,
76+
onClick = { onSelectTab(tab.route) },
77+
)
78+
}
79+
}
80+
}

composeApp/src/commonMain/kotlin/com/linuxcommandlibrary/app/ui/composables/CommandView.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import androidx.compose.ui.text.buildAnnotatedString
2323
import androidx.compose.ui.text.withStyle
2424
import androidx.compose.ui.unit.Dp
2525
import androidx.compose.ui.unit.dp
26+
import com.linuxcommandlibrary.app.NavEvent
2627
import com.linuxcommandlibrary.shared.CommandElement
2728
import com.linuxcommandlibrary.shared.platform.ShareHandler
2829
import kotlinx.collections.immutable.ImmutableList
@@ -32,7 +33,7 @@ import org.koin.compose.koinInject
3233
fun CommandView(
3334
command: String,
3435
elements: ImmutableList<CommandElement>,
35-
onNavigate: (String) -> Unit = {},
36+
onNavigate: (NavEvent) -> Unit = {},
3637
verticalPadding: Dp = 6.dp,
3738
searchText: String = "",
3839
) {
@@ -56,7 +57,7 @@ fun CommandView(
5657
LinkAnnotation.Clickable(
5758
tag = "man:${element.man}",
5859
linkInteractionListener = {
59-
onNavigate("command?commandName=${element.man}")
60+
onNavigate(NavEvent.ToCommand(element.man))
6061
},
6162
),
6263
start,
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package com.linuxcommandlibrary.app.ui.composables
2+
3+
import androidx.compose.material3.ExperimentalMaterial3Api
4+
import androidx.compose.material3.Icon
5+
import androidx.compose.material3.IconButton
6+
import androidx.compose.material3.Text
7+
import androidx.compose.material3.TopAppBar
8+
import androidx.compose.runtime.Composable
9+
import androidx.compose.runtime.collectAsState
10+
import androidx.compose.runtime.getValue
11+
import androidx.compose.ui.Modifier
12+
import androidx.compose.ui.input.pointer.PointerIcon
13+
import androidx.compose.ui.input.pointer.pointerHoverIcon
14+
import androidx.compose.ui.semantics.contentDescription
15+
import androidx.compose.ui.semantics.semantics
16+
import androidx.compose.ui.text.style.TextOverflow
17+
import androidx.compose.ui.unit.dp
18+
import com.linuxcommandlibrary.app.platform.backIcon
19+
import com.linuxcommandlibrary.app.ui.screens.BookmarkFeedbackDialog
20+
import com.linuxcommandlibrary.app.ui.screens.commanddetail.CommandDetailViewModel
21+
22+
@OptIn(ExperimentalMaterial3Api::class)
23+
@Composable
24+
fun DetailTopBar(
25+
commandName: String,
26+
viewModel: CommandDetailViewModel,
27+
onBack: () -> Unit,
28+
) {
29+
val uiState by viewModel.state.collectAsState()
30+
val isAllExpanded = uiState.isAllExpanded()
31+
32+
val expandAllPainter = rememberIconPainter(AppIcon.EXPAND_ALL)
33+
val collapseAllPainter = rememberIconPainter(AppIcon.COLLAPSE_ALL)
34+
val bookmarkPainter = rememberIconPainter(AppIcon.BOOKMARK)
35+
val bookmarkBorderPainter = rememberIconPainter(AppIcon.BOOKMARK_BORDER)
36+
37+
TopAppBar(
38+
expandedHeight = 56.dp,
39+
title = {
40+
Text(
41+
commandName,
42+
modifier = Modifier.semantics { contentDescription = "TopAppBarTitle" },
43+
maxLines = 1,
44+
overflow = TextOverflow.Ellipsis,
45+
)
46+
},
47+
colors = appTopBarColors(),
48+
navigationIcon = {
49+
IconButton(
50+
modifier = Modifier.pointerHoverIcon(PointerIcon.Hand),
51+
onClick = onBack,
52+
) {
53+
Icon(
54+
imageVector = backIcon,
55+
contentDescription = "Back",
56+
)
57+
}
58+
},
59+
actions = {
60+
IconButton(
61+
modifier = Modifier.pointerHoverIcon(PointerIcon.Hand),
62+
onClick = { viewModel.onToggleAllExpanded() },
63+
) {
64+
val painter = if (isAllExpanded) collapseAllPainter else expandAllPainter
65+
Icon(
66+
painter = painter,
67+
contentDescription = if (isAllExpanded) "Collapse all" else "Expand all",
68+
)
69+
}
70+
IconButton(
71+
modifier = Modifier.pointerHoverIcon(PointerIcon.Hand),
72+
onClick = {
73+
if (uiState.isBookmarked) viewModel.removeBookmark() else viewModel.addBookmark()
74+
},
75+
) {
76+
val painter = if (uiState.isBookmarked) bookmarkPainter else bookmarkBorderPainter
77+
Icon(
78+
painter = painter,
79+
contentDescription = if (uiState.isBookmarked) "Remove bookmark" else "Add bookmark",
80+
)
81+
}
82+
},
83+
)
84+
85+
if (uiState.showBookmarkDialog) {
86+
BookmarkFeedbackDialog(onDismiss = { viewModel.hideBookmarkDialog() })
87+
}
88+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package com.linuxcommandlibrary.app.ui.composables
2+
3+
import androidx.compose.material.icons.Icons
4+
import androidx.compose.material.icons.filled.Info
5+
import androidx.compose.material3.ExperimentalMaterial3Api
6+
import androidx.compose.material3.Icon
7+
import androidx.compose.material3.IconButton
8+
import androidx.compose.material3.Text
9+
import androidx.compose.material3.TopAppBar
10+
import androidx.compose.material3.TopAppBarColors
11+
import androidx.compose.material3.TopAppBarDefaults
12+
import androidx.compose.runtime.Composable
13+
import androidx.compose.runtime.getValue
14+
import androidx.compose.runtime.mutableStateOf
15+
import androidx.compose.runtime.remember
16+
import androidx.compose.runtime.setValue
17+
import androidx.compose.ui.Modifier
18+
import androidx.compose.ui.input.pointer.PointerIcon
19+
import androidx.compose.ui.input.pointer.pointerHoverIcon
20+
import androidx.compose.ui.semantics.contentDescription
21+
import androidx.compose.ui.semantics.semantics
22+
import androidx.compose.ui.text.style.TextOverflow
23+
import androidx.compose.ui.unit.dp
24+
import com.linuxcommandlibrary.app.platform.backIcon
25+
import com.linuxcommandlibrary.app.ui.screens.AppInfoDialog
26+
import com.linuxcommandlibrary.app.ui.theme.LocalCustomColors
27+
28+
@OptIn(ExperimentalMaterial3Api::class)
29+
@Composable
30+
fun appTopBarColors(): TopAppBarColors = TopAppBarDefaults.topAppBarColors(
31+
containerColor = LocalCustomColors.current.topBarBackground,
32+
titleContentColor = LocalCustomColors.current.topBarContent,
33+
navigationIconContentColor = LocalCustomColors.current.topBarContent,
34+
actionIconContentColor = LocalCustomColors.current.topBarContent,
35+
)
36+
37+
@OptIn(ExperimentalMaterial3Api::class)
38+
@Composable
39+
fun GenericTopBar(
40+
title: String,
41+
showBackIcon: Boolean,
42+
onBack: () -> Unit,
43+
showAppInfoIcon: Boolean,
44+
) {
45+
var showDialog by remember { mutableStateOf(false) }
46+
47+
TopAppBar(
48+
expandedHeight = 56.dp,
49+
title = {
50+
Text(
51+
title,
52+
modifier = Modifier.semantics { contentDescription = "TopAppBarTitle" },
53+
maxLines = 1,
54+
overflow = TextOverflow.Ellipsis,
55+
)
56+
},
57+
colors = appTopBarColors(),
58+
navigationIcon = {
59+
if (showBackIcon) {
60+
IconButton(
61+
modifier = Modifier.pointerHoverIcon(PointerIcon.Hand),
62+
onClick = onBack,
63+
) {
64+
Icon(
65+
imageVector = backIcon,
66+
contentDescription = "Back",
67+
)
68+
}
69+
}
70+
},
71+
actions = {
72+
if (showAppInfoIcon) {
73+
IconButton(
74+
modifier = Modifier.pointerHoverIcon(PointerIcon.Hand),
75+
onClick = { showDialog = true },
76+
) {
77+
Icon(
78+
imageVector = Icons.Filled.Info,
79+
contentDescription = "Info",
80+
)
81+
}
82+
}
83+
},
84+
)
85+
if (showDialog) {
86+
AppInfoDialog(onDismiss = { showDialog = false })
87+
}
88+
}

0 commit comments

Comments
 (0)