@@ -2,39 +2,59 @@ package org.nsh07.nsh07.ui
22
33import androidx.compose.animation.AnimatedContent
44import androidx.compose.animation.Crossfade
5+ import androidx.compose.animation.core.animateFloatAsState
6+ import androidx.compose.animation.fadeIn
7+ import androidx.compose.animation.fadeOut
8+ import androidx.compose.animation.togetherWith
59import androidx.compose.foundation.layout.Row
610import androidx.compose.foundation.layout.fillMaxWidth
11+ import androidx.compose.foundation.layout.padding
712import androidx.compose.material3.Icon
13+ import androidx.compose.material3.IconButton
814import androidx.compose.material3.MaterialTheme.typography
15+ import androidx.compose.material3.ModalWideNavigationRail
916import androidx.compose.material3.Text
1017import androidx.compose.material3.WideNavigationRail
1118import androidx.compose.material3.WideNavigationRailItem
12- import androidx.compose.material3.WideNavigationRailState
1319import androidx.compose.material3.WideNavigationRailValue
1420import androidx.compose.material3.rememberWideNavigationRailState
21+ import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi
22+ import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
23+ import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass
1524import androidx.compose.runtime.Composable
25+ import androidx.compose.runtime.LaunchedEffect
1626import androidx.compose.runtime.getValue
1727import androidx.compose.runtime.mutableStateOf
1828import androidx.compose.runtime.remember
29+ import androidx.compose.runtime.rememberCoroutineScope
1930import androidx.compose.runtime.setValue
2031import androidx.compose.ui.Modifier
32+ import androidx.compose.ui.draw.rotate
33+ import androidx.compose.ui.semantics.semantics
34+ import androidx.compose.ui.semantics.stateDescription
35+ import androidx.compose.ui.unit.dp
2136import androidx.compose.ui.util.fastForEach
37+ import kotlinx.coroutines.launch
2238import nsh07.composeapp.generated.resources.Res
2339import nsh07.composeapp.generated.resources.filled_home
2440import nsh07.composeapp.generated.resources.filled_info
41+ import nsh07.composeapp.generated.resources.menu
42+ import nsh07.composeapp.generated.resources.menu_open
2543import nsh07.composeapp.generated.resources.outline_home
2644import nsh07.composeapp.generated.resources.outline_info
2745import org.jetbrains.compose.resources.painterResource
2846import org.nsh07.nsh07.ui.about.AboutScreen
2947import org.nsh07.nsh07.ui.home.HomeScreen
3048
49+ @OptIn(ExperimentalMaterial3WindowSizeClassApi ::class )
3150@Composable
3251fun AppScreen (modifier : Modifier = Modifier ) {
33- val state: WideNavigationRailState =
34- rememberWideNavigationRailState(WideNavigationRailValue .Expanded )
35-
3652 var currentPortfolioScreen by remember { mutableStateOf(PortfolioScreen .HOME ) }
3753
54+ val windowSizeClass = calculateWindowSizeClass()
55+ val state = rememberWideNavigationRailState(WideNavigationRailValue .Collapsed )
56+ val scope = rememberCoroutineScope()
57+
3858 val railItems = remember {
3959 listOf (
4060 RailItem (
@@ -53,30 +73,147 @@ fun AppScreen(modifier: Modifier = Modifier) {
5373 }
5474
5575 Row (modifier.fillMaxWidth()) {
56- WideNavigationRail (state = state) {
57- railItems.fastForEach {
58- val selected = it.portfolioScreen == currentPortfolioScreen
59- WideNavigationRailItem (
60- selected = selected,
61- onClick = { currentPortfolioScreen = it.portfolioScreen },
62- icon = {
63- Crossfade (selected) { targetState ->
64- when (targetState) {
65- true -> Icon (painterResource(it.selectedIcon), null )
66- else -> Icon (painterResource(it.unselectedIcon), null )
76+ when (windowSizeClass.widthSizeClass) {
77+ WindowWidthSizeClass .Expanded -> {
78+ LaunchedEffect (Unit ) {
79+ state.expand()
80+ }
81+ WideNavigationRail (
82+ state = state
83+ ) {
84+ railItems.fastForEach {
85+ val selected = it.portfolioScreen == currentPortfolioScreen
86+ WideNavigationRailItem (
87+ selected = selected,
88+ onClick = { currentPortfolioScreen = it.portfolioScreen },
89+ icon = {
90+ Crossfade (selected) { targetState ->
91+ when (targetState) {
92+ true -> Icon (painterResource(it.selectedIcon), null )
93+ else -> Icon (painterResource(it.unselectedIcon), null )
94+ }
95+ }
96+ },
97+ label = { Text (it.name, style = typography.labelLarge) },
98+ railExpanded = state.targetValue == WideNavigationRailValue .Expanded
99+ )
100+ }
101+ }
102+ }
103+
104+ WindowWidthSizeClass .Medium -> {
105+ WideNavigationRail (
106+ state = state,
107+ header = {
108+ val rotation by animateFloatAsState(if (state.targetValue == WideNavigationRailValue .Expanded ) 0f else 180f )
109+ IconButton (
110+ modifier =
111+ Modifier
112+ .padding(start = 24 .dp)
113+ .rotate(rotation)
114+ .semantics {
115+ stateDescription =
116+ if (state.currentValue == WideNavigationRailValue .Expanded ) {
117+ " Expanded"
118+ } else {
119+ " Collapsed"
120+ }
121+ },
122+ onClick = {
123+ scope.launch {
124+ if (state.targetValue == WideNavigationRailValue .Expanded )
125+ state.collapse()
126+ else state.expand()
127+ }
128+ },
129+ ) {
130+ Crossfade (state.targetValue) {
131+ when (it) {
132+ WideNavigationRailValue .Expanded -> Icon (
133+ painterResource(Res .drawable.menu_open),
134+ " Collapse rail"
135+ )
136+
137+ WideNavigationRailValue .Collapsed -> Icon (
138+ painterResource(Res .drawable.menu),
139+ " Expand rail"
140+ )
141+ }
67142 }
68143 }
69- },
70- label = { Text (it.name, style = typography.labelLarge) },
71- railExpanded = state.targetValue == WideNavigationRailValue .Expanded
72- )
144+ }
145+ ) {
146+ railItems.fastForEach {
147+ val selected = it.portfolioScreen == currentPortfolioScreen
148+ WideNavigationRailItem (
149+ selected = selected,
150+ onClick = {
151+ scope.launch {
152+ currentPortfolioScreen = it.portfolioScreen
153+ state.collapse()
154+ }
155+ },
156+ icon = {
157+ Crossfade (selected) { targetState ->
158+ when (targetState) {
159+ true -> Icon (painterResource(it.selectedIcon), null )
160+ else -> Icon (painterResource(it.unselectedIcon), null )
161+ }
162+ }
163+ },
164+ label = { Text (it.name, style = typography.labelLarge) },
165+ railExpanded = state.targetValue == WideNavigationRailValue .Expanded
166+ )
167+ }
168+ }
169+ }
170+
171+ WindowWidthSizeClass .Compact -> {
172+ ModalWideNavigationRail (
173+ state = state,
174+ hideOnCollapse = true
175+ ) {
176+ railItems.fastForEach {
177+ val selected = it.portfolioScreen == currentPortfolioScreen
178+ WideNavigationRailItem (
179+ selected = selected,
180+ onClick = {
181+ scope.launch {
182+ currentPortfolioScreen = it.portfolioScreen
183+ state.collapse()
184+ }
185+ },
186+ icon = {
187+ Crossfade (selected) { targetState ->
188+ when (targetState) {
189+ true -> Icon (painterResource(it.selectedIcon), null )
190+ else -> Icon (painterResource(it.unselectedIcon), null )
191+ }
192+ }
193+ },
194+ label = { Text (it.name, style = typography.labelLarge) },
195+ railExpanded = state.targetValue == WideNavigationRailValue .Expanded
196+ )
197+ }
198+ }
73199 }
74200 }
75201
76- AnimatedContent (currentPortfolioScreen) {
202+ AnimatedContent (
203+ currentPortfolioScreen,
204+ transitionSpec = { fadeIn().togetherWith(fadeOut()) }
205+ ) {
77206 when (it) {
78- PortfolioScreen .HOME -> HomeScreen ()
79- PortfolioScreen .ABOUT -> AboutScreen ()
207+ PortfolioScreen .HOME -> HomeScreen (
208+ expandRail = { scope.launch { state.expand() } },
209+ windowWidthClass = windowSizeClass.widthSizeClass
210+ )
211+
212+ PortfolioScreen .ABOUT -> AboutScreen (
213+ expandRail = { scope.launch { state.expand() } },
214+ windowWidthClass = windowSizeClass.widthSizeClass
215+ )
216+
80217 PortfolioScreen .PROJECTS -> {}
81218 }
82219 }
0 commit comments