Skip to content

Commit 8ff0b9c

Browse files
committed
App: side-by-side settings layout (#26)
1 parent b8e281a commit 8ff0b9c

5 files changed

Lines changed: 119 additions & 54 deletions

File tree

app/build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,9 @@ dependencies {
202202
libs.androidxFragment,
203203
libs.androidxFragmentCompose,
204204
libs.androidxWindow,
205+
libs.androidx.material3.adaptive,
206+
libs.androidx.material3.adaptive.layout,
207+
libs.androidx.material3.adaptive.navigation,
205208
libs.androidxDrawerLayout,
206209
libs.androidxSwipeRefreshLayout,
207210
libs.androidxConstraintLayout,

app/src/main/kotlin/com/madness/collision/settings/SettingsPage.kt

Lines changed: 111 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,29 +17,39 @@
1717
package com.madness.collision.settings
1818

1919
import android.content.Context
20+
import androidx.activity.ComponentActivity
21+
import androidx.activity.compose.LocalActivity
2022
import androidx.compose.animation.animateColorAsState
2123
import androidx.compose.foundation.*
2224
import androidx.compose.foundation.layout.*
2325
import androidx.compose.foundation.shape.CircleShape
2426
import androidx.compose.material3.*
27+
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
2528
import androidx.compose.runtime.*
29+
import androidx.compose.runtime.saveable.rememberSaveable
2630
import androidx.compose.ui.Alignment
2731
import androidx.compose.ui.Modifier
2832
import androidx.compose.ui.draw.clip
2933
import androidx.compose.ui.graphics.Color
3034
import androidx.compose.ui.platform.LocalContext
3135
import androidx.compose.ui.res.painterResource
3236
import androidx.compose.ui.res.stringResource
37+
import androidx.compose.ui.unit.Dp
38+
import androidx.compose.ui.unit.coerceAtMost
3339
import androidx.compose.ui.unit.dp
3440
import androidx.compose.ui.unit.sp
41+
import androidx.core.os.bundleOf
42+
import androidx.fragment.compose.AndroidFragment
43+
import androidx.lifecycle.viewmodel.compose.viewModel
44+
import androidx.window.core.layout.WindowSizeClass.Companion.WIDTH_DP_MEDIUM_LOWER_BOUND
3545
import com.madness.collision.R
3646
import com.madness.collision.settings.instant.InstantFragment
3747
import com.madness.collision.main.DevOptions
3848
import com.madness.collision.main.MainViewModel
3949
import com.madness.collision.main.showPage
4050
import com.madness.collision.pref.PrefExterior
41-
import com.madness.collision.settings.unitmgr.UnitsManagerFragment
4251
import com.madness.collision.unit.DescRetriever
52+
import com.madness.collision.unit.Unit as ModUnit
4353
import com.madness.collision.util.Page
4454
import com.madness.collision.util.dev.DarkPreview
4555
import com.madness.collision.util.dev.LayoutDirectionPreviews
@@ -50,38 +60,120 @@ fun SettingsPage(
5060
mainViewModel: MainViewModel,
5161
paddingValues: PaddingValues,
5262
showLanguages: () -> Unit,
63+
) {
64+
val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass
65+
if (windowSizeClass.isWidthAtLeastBreakpoint(WIDTH_DP_MEDIUM_LOWER_BOUND)) {
66+
BoxWithConstraints {
67+
val navWidth = (maxWidth / 3).coerceAtMost(240.dp)
68+
SideBySideLayout(navPaneWidth = navWidth, showLanguages = showLanguages, contentPadding = paddingValues)
69+
}
70+
} else {
71+
SinglePaneLayout(showLanguages = showLanguages, contentPadding = paddingValues)
72+
}
73+
}
74+
75+
private enum class NavDest {
76+
Styles, Languages, About, Instant, ApiUnit
77+
}
78+
79+
@Composable
80+
private fun SinglePaneLayout(
81+
showLanguages: () -> Unit,
82+
contentPadding: PaddingValues = PaddingValues.Zero,
83+
) {
84+
val context = LocalContext.current
85+
val activity = LocalActivity.current as? ComponentActivity
86+
SettingsNavPanel(
87+
mainViewModel = viewModel(activity!!),
88+
paddingValues = contentPadding,
89+
onSelectDest = { dest ->
90+
when (dest) {
91+
NavDest.Styles ->
92+
context.showPage<Page> {
93+
putString("fragmentClass", PrefExterior::class.qualifiedName)
94+
putInt("titleId", R.string.settings_exterior)
95+
}
96+
NavDest.Languages -> showLanguages()
97+
NavDest.About -> context.showPage<AdviceFragment>()
98+
NavDest.Instant -> context.showPage<InstantFragment>()
99+
NavDest.ApiUnit ->
100+
ModUnit.getBridge(ModUnit.UNIT_NAME_API_VIEWING)
101+
?.getSettings()?.let(context::showPage)
102+
}
103+
},
104+
)
105+
}
106+
107+
@Composable
108+
private fun SideBySideLayout(
109+
navPaneWidth: Dp,
110+
showLanguages: () -> Unit,
111+
modifier: Modifier = Modifier,
112+
contentPadding: PaddingValues = PaddingValues.Zero,
113+
) {
114+
val (navDest, setNavDest) = rememberSaveable {
115+
mutableStateOf<NavDest?>(null)
116+
}
117+
Row(modifier = modifier) {
118+
Box(
119+
modifier = Modifier
120+
.width(navPaneWidth)
121+
.padding(contentPadding)
122+
) {
123+
val activity = LocalActivity.current as? ComponentActivity
124+
SettingsNavPanel(mainViewModel = viewModel(activity!!), onSelectDest = setNavDest)
125+
}
126+
127+
VerticalDivider(thickness = 0.5.dp)
128+
129+
var lastDest by rememberSaveable { mutableStateOf<NavDest?>(null) }
130+
LaunchedEffect(navDest) { if (navDest != NavDest.Languages) lastDest = navDest }
131+
if (navDest == NavDest.Languages) LaunchedEffect(Unit) { showLanguages() }
132+
133+
when (lastDest) {
134+
NavDest.Styles -> {
135+
val n = PrefExterior::class.qualifiedName
136+
val args = bundleOf("fragmentClass" to n, "titleId" to R.string.settings_exterior)
137+
AndroidFragment<Page>(arguments = args)
138+
}
139+
NavDest.Languages -> Unit
140+
NavDest.About -> AndroidFragment<AdviceFragment>()
141+
NavDest.Instant -> AndroidFragment<InstantFragment>()
142+
NavDest.ApiUnit -> {
143+
val n = "com.madness.collision.unit.api_viewing.PrefAv"
144+
val args = bundleOf("fragmentClass" to n, "titleId" to R.string.apiViewer)
145+
AndroidFragment<Page>(arguments = args)
146+
}
147+
null -> Unit
148+
}
149+
}
150+
}
151+
152+
@Composable
153+
private fun SettingsNavPanel(
154+
mainViewModel: MainViewModel,
155+
onSelectDest: (NavDest) -> Unit,
156+
paddingValues: PaddingValues = PaddingValues.Zero,
53157
) {
54158
val context = LocalContext.current
55159
val options = remember {
56160
val builtIn = listOf(
57161
Triple(R.string.settings_exterior, R.drawable.ic_palette_24) {
58-
context.showPage<Page> {
59-
putString("fragmentClass", PrefExterior::class.qualifiedName)
60-
putInt("titleId", R.string.settings_exterior)
61-
}
162+
onSelectDest(NavDest.Styles)
62163
},
63164
Triple(R.string.Settings_Button_SwitchLanguage, R.drawable.ic_language_24) {
64-
showLanguages()
165+
onSelectDest(NavDest.Languages)
65166
},
66167
Triple(R.string.Main_TextView_Advice_Text, R.drawable.ic_info_24) {
67-
context.showPage<AdviceFragment>()
68-
},
69-
Triple(R.string.unitsManager, R.drawable.ic_extension_24) {
70-
context.showPage<UnitsManagerFragment>()
168+
onSelectDest(NavDest.About)
71169
},
72170
Triple(R.string.Main_TextView_Launcher, R.drawable.ic_flash_24) {
73-
context.showPage<InstantFragment>()
171+
onSelectDest(NavDest.Instant)
74172
},
75173
)
76-
val specialOptions = buildList {
77-
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
78-
add(Triple(R.string.app_device_controls, R.drawable.ic_devices_24) {
79-
context.showPage<DeviceControlsFragment>()
80-
})
81-
}
82-
}
83174
val unitOptions = getUnitOptions(mainViewModel, context)
84-
builtIn + specialOptions + unitOptions
175+
.map { (l, i, _) -> Triple(l, i) { onSelectDest(NavDest.ApiUnit) } }
176+
builtIn + unitOptions
85177
}
86178
Settings(options = options, paddingValues = paddingValues)
87179
}
@@ -193,7 +285,6 @@ private fun SettingsPreview() {
193285
Triple(R.string.settings_exterior, R.drawable.ic_palette_24) { },
194286
Triple(R.string.Settings_Button_SwitchLanguage, R.drawable.ic_language_24) { },
195287
Triple(R.string.Main_TextView_Advice_Text, R.drawable.ic_info_24) { },
196-
Triple(R.string.unitsManager, R.drawable.ic_extension_24) { },
197288
Triple(R.string.Main_TextView_Launcher, R.drawable.ic_flash_24) { },
198289
Triple(R.string.apiViewer, R.drawable.ic_android_24) { },
199290
)

app/src/main/res/drawable/ic_devices_24.xml

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

app/src/main/res/drawable/ic_extension_24.xml

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

gradle/libs.versions.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ androidxRoom = "2.7.2"
2323
androidxNavigation = "2.9.4"
2424
androidxCoreTesting = "2.2.0"
2525
androidxWindow = "1.4.0"
26+
androidxMaterial3Adaptive = "1.2.0-rc01"
2627

2728
androidxDrawerLayout = "1.2.0"
2829
androidxSwipeRefreshLayout = "1.1.0"
@@ -103,6 +104,10 @@ androidxAppcompat = { module = "androidx.appcompat:appcompat", version.ref = "an
103104
androidxFragment = { module = "androidx.fragment:fragment-ktx", version.ref = "androidxFragment" }
104105
androidxFragmentCompose = { module = "androidx.fragment:fragment-compose", version.ref = "androidxFragment" }
105106
androidxWindow = { module = "androidx.window:window", version.ref = "androidxWindow" }
107+
androidx-material3-adaptive = { module = "androidx.compose.material3.adaptive:adaptive", version.ref = "androidxMaterial3Adaptive" }
108+
androidx-material3-adaptive-layout = { module = "androidx.compose.material3.adaptive:adaptive-layout", version.ref = "androidxMaterial3Adaptive" }
109+
androidx-material3-adaptive-navigation = { module = "androidx.compose.material3.adaptive:adaptive-navigation", version.ref = "androidxMaterial3Adaptive" }
110+
106111
androidxDrawerLayout = { module = "androidx.drawerlayout:drawerlayout", version.ref = "androidxDrawerLayout" }
107112
androidxSwipeRefreshLayout = { module = "androidx.swiperefreshlayout:swiperefreshlayout", version.ref = "androidxSwipeRefreshLayout" }
108113
androidxConstraintLayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "androidxConstraintLayout" }

0 commit comments

Comments
 (0)