Skip to content

Commit 1d6f2e9

Browse files
committed
feature: added support for landscape orientation
1 parent 3f89925 commit 1d6f2e9

16 files changed

Lines changed: 843 additions & 169 deletions

File tree

app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/AppNavHost.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import androidx.compose.animation.fadeOut
55
import androidx.compose.animation.scaleIn
66
import androidx.compose.animation.scaleOut
77
import androidx.compose.foundation.layout.PaddingValues
8-
import androidx.compose.foundation.layout.padding
98
import androidx.compose.runtime.Composable
109
import androidx.compose.ui.Modifier
1110
import androidx.compose.ui.platform.LocalView
@@ -24,12 +23,13 @@ import com.androidvip.sysctlgui.ui.user.UserParamsScreen
2423

2524
@Composable
2625
internal fun AppNavHost(
27-
innerPadding: PaddingValues,
26+
modifier: Modifier = Modifier,
27+
innerPadding: PaddingValues, // Handled in the outer scope, this is for inner scaffolds
2828
navController: NavHostController,
2929
startDestination: UiRoute = UiRoute.BrowseParams
3030
) {
3131
NavHost(
32-
modifier = Modifier.padding(innerPadding),
32+
modifier = modifier,
3333
navController = navController,
3434
startDestination = startDestination,
3535
enterTransition = { scaleIn(initialScale = 0.9f) + fadeIn() },

app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainActivity.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ class MainActivity : ComponentActivity() {
4040
updateEdgeToEdgeConfiguration(forceDark)
4141
}
4242

43-
// TODO: Landscape Support
4443
SysctlGuiTheme(
4544
darkTheme = forceDark || isSystemInDarkTheme(),
4645
contrastLevel = themeState.contrastLevel,

app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainNavBar.kt

Lines changed: 3 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,14 @@
11
package com.androidvip.sysctlgui.ui.main
22

33
import androidx.compose.animation.AnimatedContent
4-
import androidx.compose.material.icons.Icons
5-
import androidx.compose.material.icons.outlined.Build
6-
import androidx.compose.material.icons.outlined.FavoriteBorder
7-
import androidx.compose.material.icons.outlined.Home
8-
import androidx.compose.material.icons.outlined.Settings
9-
import androidx.compose.material.icons.rounded.Build
10-
import androidx.compose.material.icons.rounded.Favorite
11-
import androidx.compose.material.icons.rounded.Home
12-
import androidx.compose.material.icons.rounded.Settings
134
import androidx.compose.material3.Icon
145
import androidx.compose.material3.NavigationBar
156
import androidx.compose.material3.NavigationBarItem
167
import androidx.compose.material3.Text
178
import androidx.compose.runtime.Composable
189
import androidx.compose.runtime.getValue
1910
import androidx.compose.runtime.remember
20-
import androidx.compose.ui.res.stringResource
11+
import androidx.compose.ui.platform.LocalContext
2112
import androidx.compose.ui.text.style.TextOverflow
2213
import androidx.compose.ui.tooling.preview.PreviewDynamicColors
2314
import androidx.compose.ui.tooling.preview.PreviewLightDark
@@ -27,45 +18,12 @@ import androidx.navigation.NavGraph.Companion.findStartDestination
2718
import androidx.navigation.NavHostController
2819
import androidx.navigation.compose.currentBackStackEntryAsState
2920
import androidx.navigation.compose.rememberNavController
30-
import com.androidvip.sysctlgui.R
31-
import com.androidvip.sysctlgui.core.navigation.TopLevelRoute
32-
import com.androidvip.sysctlgui.core.navigation.UiRoute
3321
import com.androidvip.sysctlgui.design.theme.SysctlGuiTheme
3422

3523
@Composable
3624
internal fun MainNavBar(navController: NavHostController = rememberNavController()) {
37-
val browseParamsTitle = stringResource(R.string.browse)
38-
val presetsTitle = stringResource(R.string.presets)
39-
val favoritesTitle = stringResource(R.string.favorites)
40-
val settingsTitle = stringResource(R.string.settings)
41-
val topLevelRoutes = remember {
42-
listOf(
43-
TopLevelRoute(
44-
name = browseParamsTitle,
45-
route = UiRoute.BrowseParams,
46-
selectedIcon = Icons.Rounded.Home,
47-
unselectedIcon = Icons.Outlined.Home
48-
),
49-
TopLevelRoute(
50-
name = presetsTitle,
51-
route = UiRoute.Presets,
52-
selectedIcon = Icons.Rounded.Build,
53-
unselectedIcon = Icons.Outlined.Build
54-
),
55-
TopLevelRoute(
56-
name = favoritesTitle,
57-
route = UiRoute.Favorites,
58-
selectedIcon = Icons.Rounded.Favorite,
59-
unselectedIcon = Icons.Outlined.FavoriteBorder
60-
),
61-
TopLevelRoute(
62-
name = settingsTitle,
63-
route = UiRoute.Settings,
64-
selectedIcon = Icons.Rounded.Settings,
65-
unselectedIcon = Icons.Outlined.Settings
66-
)
67-
)
68-
}
25+
val context = LocalContext.current
26+
val topLevelRoutes = remember { TopLevelRouteProvider(context) }
6927

7028
NavigationBar {
7129
val navBackStackEntry by navController.currentBackStackEntryAsState()
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package com.androidvip.sysctlgui.ui.main
2+
3+
import androidx.compose.animation.AnimatedContent
4+
import androidx.compose.material3.Icon
5+
import androidx.compose.material3.NavigationRail
6+
import androidx.compose.material3.NavigationRailItem
7+
import androidx.compose.material3.Text
8+
import androidx.compose.runtime.Composable
9+
import androidx.compose.runtime.getValue
10+
import androidx.compose.runtime.remember
11+
import androidx.compose.ui.platform.LocalContext
12+
import androidx.compose.ui.text.style.TextOverflow
13+
import androidx.compose.ui.tooling.preview.PreviewLightDark
14+
import androidx.navigation.NavDestination.Companion.hasRoute
15+
import androidx.navigation.NavDestination.Companion.hierarchy
16+
import androidx.navigation.NavGraph.Companion.findStartDestination
17+
import androidx.navigation.NavHostController
18+
import androidx.navigation.compose.currentBackStackEntryAsState
19+
import androidx.navigation.compose.rememberNavController
20+
import com.androidvip.sysctlgui.design.theme.SysctlGuiTheme
21+
22+
@Composable
23+
internal fun MainNavRail(navController: NavHostController = rememberNavController()) {
24+
val context = LocalContext.current
25+
val topLevelRoutes = remember { TopLevelRouteProvider(context) }
26+
27+
NavigationRail {
28+
val navBackStackEntry by navController.currentBackStackEntryAsState()
29+
val currentDestination = navBackStackEntry?.destination
30+
31+
topLevelRoutes.forEach { route ->
32+
val selected = currentDestination
33+
?.hierarchy
34+
?.any { it.hasRoute(route.route::class) } == true
35+
36+
NavigationRailItem(
37+
icon = {
38+
AnimatedContent(targetState = selected) { selectedState ->
39+
Icon(
40+
imageVector = if (selectedState) {
41+
route.selectedIcon
42+
} else {
43+
route.unselectedIcon
44+
},
45+
contentDescription = route.name,
46+
)
47+
}
48+
},
49+
label = {
50+
Text(
51+
text = route.name,
52+
maxLines = 1,
53+
overflow = TextOverflow.Ellipsis
54+
)
55+
},
56+
selected = selected,
57+
onClick = {
58+
navController.navigate(route.route) {
59+
popUpTo(navController.graph.findStartDestination().id) {
60+
saveState = true
61+
}
62+
launchSingleTop = true
63+
restoreState = true
64+
}
65+
}
66+
)
67+
}
68+
}
69+
}
70+
71+
@Composable
72+
@PreviewLightDark
73+
private fun MainNavbarPreview() {
74+
SysctlGuiTheme(dynamicColor = true) {
75+
MainNavRail()
76+
}
77+
}

app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainScreen.kt

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,18 @@ package com.androidvip.sysctlgui.ui.main
22

33
import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
44
import androidx.compose.animation.AnimatedVisibility
5+
import androidx.compose.animation.expandHorizontally
56
import androidx.compose.animation.expandVertically
67
import androidx.compose.animation.fadeIn
78
import androidx.compose.animation.fadeOut
9+
import androidx.compose.animation.shrinkHorizontally
810
import androidx.compose.animation.shrinkVertically
11+
import androidx.compose.animation.slideInHorizontally
912
import androidx.compose.animation.slideInVertically
13+
import androidx.compose.animation.slideOutHorizontally
1014
import androidx.compose.animation.slideOutVertically
15+
import androidx.compose.foundation.layout.Row
16+
import androidx.compose.foundation.layout.padding
1117
import androidx.compose.material3.ExperimentalMaterial3Api
1218
import androidx.compose.material3.Scaffold
1319
import androidx.compose.material3.SnackbarDuration
@@ -17,13 +23,15 @@ import androidx.compose.runtime.Composable
1723
import androidx.compose.runtime.LaunchedEffect
1824
import androidx.compose.runtime.getValue
1925
import androidx.compose.runtime.remember
26+
import androidx.compose.ui.Modifier
2027
import androidx.compose.ui.tooling.preview.PreviewDynamicColors
2128
import androidx.compose.ui.tooling.preview.PreviewLightDark
2229
import androidx.lifecycle.compose.collectAsStateWithLifecycle
2330
import androidx.navigation.NavHostController
2431
import androidx.navigation.compose.rememberNavController
2532
import com.androidvip.sysctlgui.core.navigation.UiRoute
2633
import com.androidvip.sysctlgui.design.theme.SysctlGuiTheme
34+
import com.androidvip.sysctlgui.design.utils.isLandscape
2735
import org.koin.androidx.compose.koinViewModel
2836

2937
@OptIn(ExperimentalMaterial3Api::class)
@@ -60,6 +68,7 @@ private fun MainScreenContent(
6068
startDestination: UiRoute = UiRoute.BrowseParams
6169
) {
6270
val onBackPressedDispatcherOwner = LocalOnBackPressedDispatcherOwner.current
71+
val isLandscape = isLandscape()
6372

6473
Scaffold(
6574
topBar = {
@@ -82,7 +91,7 @@ private fun MainScreenContent(
8291
},
8392
bottomBar = {
8493
AnimatedVisibility(
85-
visible = state.showNavBar,
94+
visible = state.showNavBar && !isLandscape,
8695
enter = expandVertically() + slideInVertically { it / 2 } + fadeIn(),
8796
exit = shrinkVertically() + slideOutVertically { it / 2 } + fadeOut(),
8897
label = "BottomBar"
@@ -94,11 +103,25 @@ private fun MainScreenContent(
94103
SnackbarHost(snackbarHostState)
95104
},
96105
content = { innerPadding ->
97-
AppNavHost(
98-
innerPadding = innerPadding,
99-
navController = navController,
100-
startDestination = startDestination
101-
)
106+
Row(Modifier.padding(innerPadding)) {
107+
if (isLandscape) {
108+
AnimatedVisibility(
109+
visible = state.showNavBar,
110+
enter = expandHorizontally() + slideInHorizontally { it / 2 } + fadeIn(),
111+
exit = shrinkHorizontally() + slideOutHorizontally { it / 2 } + fadeOut(),
112+
label = "NavRail"
113+
) {
114+
MainNavRail(navController = navController)
115+
}
116+
}
117+
118+
AppNavHost(
119+
modifier = Modifier.weight(1f),
120+
innerPadding = innerPadding,
121+
navController = navController,
122+
startDestination = startDestination
123+
)
124+
}
102125
}
103126
)
104127
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.androidvip.sysctlgui.ui.main
2+
3+
import android.content.Context
4+
import androidx.compose.material.icons.Icons
5+
import androidx.compose.material.icons.outlined.Build
6+
import androidx.compose.material.icons.outlined.FavoriteBorder
7+
import androidx.compose.material.icons.outlined.Home
8+
import androidx.compose.material.icons.outlined.Settings
9+
import androidx.compose.material.icons.rounded.Build
10+
import androidx.compose.material.icons.rounded.Favorite
11+
import androidx.compose.material.icons.rounded.Home
12+
import androidx.compose.material.icons.rounded.Settings
13+
import com.androidvip.sysctlgui.R
14+
import com.androidvip.sysctlgui.core.navigation.TopLevelRoute
15+
import com.androidvip.sysctlgui.core.navigation.UiRoute
16+
17+
object TopLevelRouteProvider {
18+
operator fun invoke(context: Context): List<TopLevelRoute<out UiRoute>> {
19+
return listOf(
20+
TopLevelRoute(
21+
name = context.getString(R.string.browse),
22+
route = UiRoute.BrowseParams,
23+
selectedIcon = Icons.Rounded.Home,
24+
unselectedIcon = Icons.Outlined.Home
25+
),
26+
TopLevelRoute(
27+
name = context.getString(R.string.presets),
28+
route = UiRoute.Presets,
29+
selectedIcon = Icons.Rounded.Build,
30+
unselectedIcon = Icons.Outlined.Build
31+
),
32+
TopLevelRoute(
33+
name = context.getString(R.string.favorites),
34+
route = UiRoute.Favorites,
35+
selectedIcon = Icons.Rounded.Favorite,
36+
unselectedIcon = Icons.Outlined.FavoriteBorder
37+
),
38+
TopLevelRoute(
39+
name = context.getString(R.string.settings),
40+
route = UiRoute.Settings,
41+
selectedIcon = Icons.Rounded.Settings,
42+
unselectedIcon = Icons.Outlined.Settings
43+
)
44+
)
45+
}
46+
}

app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamBrowseScreen.kt

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,11 @@ import androidx.compose.foundation.layout.fillMaxSize
1818
import androidx.compose.foundation.layout.fillMaxWidth
1919
import androidx.compose.foundation.layout.height
2020
import androidx.compose.foundation.layout.padding
21-
import androidx.compose.foundation.lazy.LazyColumn
22-
import androidx.compose.foundation.lazy.rememberLazyListState
21+
import androidx.compose.foundation.lazy.grid.GridCells
22+
import androidx.compose.foundation.lazy.grid.GridItemSpan
23+
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
24+
import androidx.compose.foundation.lazy.grid.items
25+
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
2326
import androidx.compose.material.ExperimentalMaterialApi
2427
import androidx.compose.material.pullrefresh.PullRefreshIndicator
2528
import androidx.compose.material.pullrefresh.pullRefresh
@@ -50,11 +53,13 @@ import androidx.compose.ui.semantics.semantics
5053
import androidx.compose.ui.text.TextStyle
5154
import androidx.compose.ui.text.style.TextDecoration
5255
import androidx.compose.ui.text.style.TextOverflow
56+
import androidx.compose.ui.tooling.preview.Preview
5357
import androidx.compose.ui.tooling.preview.PreviewLightDark
5458
import androidx.compose.ui.unit.dp
5559
import androidx.lifecycle.compose.collectAsStateWithLifecycle
5660
import com.androidvip.sysctlgui.R
5761
import com.androidvip.sysctlgui.design.theme.SysctlGuiTheme
62+
import com.androidvip.sysctlgui.design.utils.isLandscape
5863
import com.androidvip.sysctlgui.domain.models.KernelParam
5964
import com.androidvip.sysctlgui.domain.models.ParamDocumentation
6065
import com.androidvip.sysctlgui.models.UiKernelParam
@@ -151,7 +156,7 @@ private fun ParamBrowseScreenContent(
151156
isRefreshing: Boolean,
152157
onRefresh: () -> Unit
153158
) {
154-
val listState = rememberLazyListState()
159+
val listState = rememberLazyGridState()
155160
val pullRefreshState =
156161
rememberPullRefreshState(refreshing = isRefreshing, onRefresh = onRefresh)
157162
var headerVisible by remember { mutableStateOf(backEnabled) }
@@ -196,25 +201,32 @@ private fun ParamBrowseScreenContent(
196201
.pullRefresh(pullRefreshState),
197202
) {
198203
val spacerHeight by animateDpAsState(if (finalHeaderVisible) 56.dp else 0.dp)
199-
LazyColumn(
204+
val columns = if (isLandscape()) 2 else 1
205+
LazyVerticalGrid(
206+
columns = GridCells.Fixed(columns),
200207
state = listState,
201208
modifier = Modifier.fillMaxSize()
202209
) {
203-
item { Spacer(modifier = Modifier.height(spacerHeight)) }
210+
item(span = { GridItemSpan(columns) }) {
211+
Spacer(modifier = Modifier.height(spacerHeight))
212+
}
204213

205214
items(
206-
count = params.size,
207-
key = { index -> params[index].name }
208-
) { index ->
215+
items = params,
216+
key = { param -> param.name }
217+
) { param ->
209218
ParamFileRow(
210219
modifier = Modifier.animateItem(),
211-
param = params[index],
220+
param = param,
221+
showFavoriteIcon = true,
212222
onParamClicked = onParamClicked,
213223
)
214224
}
215225

216226
if (documentation != null) {
217-
item { Spacer(modifier = Modifier.height(56.dp)) }
227+
item(span = { GridItemSpan(columns) }) {
228+
Spacer(modifier = Modifier.height(56.dp))
229+
}
218230
}
219231
}
220232

@@ -302,6 +314,7 @@ private fun InfoItem(
302314

303315
@Composable
304316
@PreviewLightDark
317+
@Preview(device = "spec:parent=pixel_5,orientation=landscape")
305318
internal fun ParamBrowseScreenContentPreview() {
306319
fun mapFilesToParams(files: Array<File>?): List<UiKernelParam> {
307320
return files?.map { file ->

0 commit comments

Comments
 (0)