Skip to content

Commit 8d8f4ab

Browse files
committed
[AdaptiveUiCodelab] Use NavigationSuiteScaffold
Note this component doesn't support a navigation drawer. Keeping `NavigationDrawerContent` composable for now in case we intend to implement support for showing the drawer again.
1 parent 8adf820 commit 8d8f4ab

File tree

5 files changed

+72
-195
lines changed

5 files changed

+72
-195
lines changed

AdaptiveUiCodelab/app/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ dependencies {
7373

7474
implementation(libs.androidx.material3)
7575
implementation(libs.androidx.material3.adaptive)
76+
implementation(libs.androidx.material3.adaptive.nav.suite)
7677
implementation(libs.androidx.material.icons.extended)
7778
implementation(libs.androidx.ui.tooling.preview)
7879
androidTestImplementation(libs.androidx.ui.test.junit4)

AdaptiveUiCodelab/app/src/main/java/com/example/reply/ui/ReplyApp.kt

Lines changed: 46 additions & 187 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
package com.example.reply.ui
1818

19-
import androidx.compose.animation.AnimatedVisibility
2019
import androidx.compose.foundation.background
2120
import androidx.compose.foundation.layout.Arrangement
2221
import androidx.compose.foundation.layout.Column
@@ -27,47 +26,29 @@ import androidx.compose.foundation.layout.fillMaxWidth
2726
import androidx.compose.foundation.layout.padding
2827
import androidx.compose.foundation.layout.wrapContentWidth
2928
import androidx.compose.material.icons.Icons
30-
import androidx.compose.material.icons.filled.Article
31-
import androidx.compose.material.icons.filled.Chat
32-
import androidx.compose.material.icons.filled.Inbox
33-
import androidx.compose.material.icons.filled.Menu
3429
import androidx.compose.material.icons.filled.MenuOpen
35-
import androidx.compose.material.icons.outlined.Chat
36-
import androidx.compose.material.icons.outlined.People
37-
import androidx.compose.material.icons.outlined.Videocam
38-
import androidx.compose.material3.DrawerValue
39-
import androidx.compose.material3.ExperimentalMaterial3Api
4030
import androidx.compose.material3.Icon
4131
import androidx.compose.material3.IconButton
4232
import androidx.compose.material3.MaterialTheme
43-
import androidx.compose.material3.ModalDrawerSheet
44-
import androidx.compose.material3.ModalNavigationDrawer
45-
import androidx.compose.material3.NavigationBar
46-
import androidx.compose.material3.NavigationBarItem
4733
import androidx.compose.material3.NavigationDrawerItem
4834
import androidx.compose.material3.NavigationDrawerItemDefaults
49-
import androidx.compose.material3.NavigationRail
50-
import androidx.compose.material3.NavigationRailItem
51-
import androidx.compose.material3.PermanentDrawerSheet
52-
import androidx.compose.material3.PermanentNavigationDrawer
5335
import androidx.compose.material3.Text
54-
import androidx.compose.material3.rememberDrawerState
36+
import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffold
5537
import androidx.compose.runtime.Composable
56-
import androidx.compose.runtime.rememberCoroutineScope
38+
import androidx.compose.runtime.getValue
39+
import androidx.compose.runtime.mutableStateOf
40+
import androidx.compose.runtime.remember
41+
import androidx.compose.runtime.setValue
5742
import androidx.compose.ui.Alignment
5843
import androidx.compose.ui.Modifier
5944
import androidx.compose.ui.graphics.Color
6045
import androidx.compose.ui.res.stringResource
61-
import androidx.compose.ui.tooling.preview.Preview
6246
import androidx.compose.ui.unit.dp
6347
import androidx.window.core.layout.WindowWidthSizeClass
6448
import com.example.reply.R
6549
import com.example.reply.ui.utils.DevicePosture
6650
import com.example.reply.ui.utils.ReplyContentType
67-
import com.example.reply.ui.utils.ReplyNavigationType
68-
import kotlinx.coroutines.launch
6951

70-
@OptIn(ExperimentalMaterial3Api::class)
7152
@Composable
7253
fun ReplyApp(
7354
windowSize: WindowWidthSizeClass,
@@ -81,186 +62,76 @@ fun ReplyApp(
8162
* In the state of folding device If it's half fold in BookPosture we want to avoid content
8263
* at the crease/hinge
8364
*/
84-
val navigationType: ReplyNavigationType
8565
val contentType: ReplyContentType
8666

8767
when (windowSize) {
8868
WindowWidthSizeClass.COMPACT -> {
89-
navigationType = ReplyNavigationType.BOTTOM_NAVIGATION
9069
contentType = ReplyContentType.LIST_ONLY
9170
}
9271
WindowWidthSizeClass.MEDIUM -> {
93-
navigationType = ReplyNavigationType.NAVIGATION_RAIL
9472
contentType = if (foldingDevicePosture != DevicePosture.NormalPosture) {
9573
ReplyContentType.LIST_AND_DETAIL
9674
} else {
9775
ReplyContentType.LIST_ONLY
9876
}
9977
}
10078
WindowWidthSizeClass.EXPANDED -> {
101-
navigationType = if (foldingDevicePosture is DevicePosture.BookPosture) {
102-
ReplyNavigationType.NAVIGATION_RAIL
103-
} else {
104-
ReplyNavigationType.PERMANENT_NAVIGATION_DRAWER
105-
}
10679
contentType = ReplyContentType.LIST_AND_DETAIL
10780
}
10881
else -> {
109-
navigationType = ReplyNavigationType.BOTTOM_NAVIGATION
11082
contentType = ReplyContentType.LIST_ONLY
11183
}
11284
}
11385

114-
ReplyNavigationWrapperUI(navigationType, contentType, replyHomeUIState)
86+
ReplyNavigationWrapperUI(contentType, replyHomeUIState)
11587
}
11688

117-
@OptIn(ExperimentalMaterial3Api::class)
11889
@Composable
11990
private fun ReplyNavigationWrapperUI(
120-
navigationType: ReplyNavigationType,
12191
contentType: ReplyContentType,
12292
replyHomeUIState: ReplyHomeUIState
12393
) {
124-
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
125-
val scope = rememberCoroutineScope()
126-
val selectedDestination = ReplyDestinations.INBOX
127-
128-
if (navigationType == ReplyNavigationType.PERMANENT_NAVIGATION_DRAWER) {
129-
PermanentNavigationDrawer(
130-
drawerContent = {
131-
PermanentDrawerSheet {
132-
NavigationDrawerContent(selectedDestination)
133-
}
94+
var selectedDestination: ReplyDestination by remember {
95+
mutableStateOf(ReplyDestination.Inbox)
96+
}
97+
NavigationSuiteScaffold(
98+
navigationSuiteItems = {
99+
ReplyDestination.entries.forEach {
100+
item(
101+
label = { Text(stringResource(it.labelRes)) },
102+
icon = { Icon(it.icon, stringResource(it.labelRes)) },
103+
selected = it == selectedDestination,
104+
onClick = { /*TODO update selection*/ },
105+
)
134106
}
135-
) {
136-
ReplyAppContent(navigationType, contentType, replyHomeUIState)
137-
}
138-
} else {
139-
ModalNavigationDrawer(
140-
drawerContent = {
141-
ModalDrawerSheet {
142-
NavigationDrawerContent(
143-
selectedDestination,
144-
onDrawerClicked = {
145-
scope.launch {
146-
drawerState.close()
147-
}
148-
}
149-
)
150-
}
151-
},
152-
drawerState = drawerState
153-
) {
154-
ReplyAppContent(
155-
navigationType, contentType, replyHomeUIState,
156-
onDrawerClicked = {
157-
scope.launch {
158-
drawerState.open()
159-
}
160-
}
161-
)
162107
}
108+
) {
109+
ReplyAppContent(contentType, replyHomeUIState)
163110
}
164111
}
165112

113+
166114
@Composable
167115
fun ReplyAppContent(
168-
navigationType: ReplyNavigationType,
169116
contentType: ReplyContentType,
170117
replyHomeUIState: ReplyHomeUIState,
171-
onDrawerClicked: () -> Unit = {}
172118
) {
173-
Row(modifier = Modifier.fillMaxSize()) {
174-
AnimatedVisibility(visible = navigationType == ReplyNavigationType.NAVIGATION_RAIL) {
175-
ReplyNavigationRail(
176-
onDrawerClicked = onDrawerClicked
177-
)
178-
}
179-
Column(modifier = Modifier
180-
.fillMaxSize()
181-
.background(MaterialTheme.colorScheme.inverseOnSurface)
182-
) {
183-
if (contentType == ReplyContentType.LIST_AND_DETAIL) {
184-
ReplyListAndDetailContent(
185-
replyHomeUIState = replyHomeUIState,
186-
modifier = Modifier.weight(1f),
187-
)
188-
} else {
189-
ReplyListOnlyContent(replyHomeUIState = replyHomeUIState, modifier = Modifier.weight(1f))
190-
}
191-
192-
AnimatedVisibility(visible = navigationType == ReplyNavigationType.BOTTOM_NAVIGATION) {
193-
ReplyBottomNavigationBar()
194-
}
195-
}
196-
}
197-
}
198-
199-
@Composable
200-
@Preview
201-
fun ReplyNavigationRail(
202-
onDrawerClicked: () -> Unit = {},
203-
) {
204-
NavigationRail(modifier = Modifier.fillMaxHeight()) {
205-
NavigationRailItem(
206-
selected = false,
207-
onClick = onDrawerClicked,
208-
icon = { Icon(imageVector = Icons.Default.Menu, contentDescription = stringResource(id = R.string.navigation_drawer)) }
209-
)
210-
NavigationRailItem(
211-
selected = true,
212-
onClick = { /*TODO*/ },
213-
icon = { Icon(imageVector = Icons.Default.Inbox, contentDescription = stringResource(id = R.string.tab_inbox)) }
119+
if (contentType == ReplyContentType.LIST_AND_DETAIL) {
120+
ReplyListAndDetailContent(
121+
replyHomeUIState = replyHomeUIState,
122+
modifier = Modifier.fillMaxSize(),
214123
)
215-
NavigationRailItem(
216-
selected = false,
217-
onClick = {/*TODO*/ },
218-
icon = { Icon(imageVector = Icons.Default.Article, stringResource(id = R.string.tab_article)) }
219-
)
220-
NavigationRailItem(
221-
selected = false,
222-
onClick = { /*TODO*/ },
223-
icon = { Icon(imageVector = Icons.Outlined.Chat, stringResource(id = R.string.tab_dm)) }
224-
)
225-
NavigationRailItem(
226-
selected = false,
227-
onClick = { /*TODO*/ },
228-
icon = { Icon(imageVector = Icons.Outlined.People, stringResource(id = R.string.tab_groups)) }
229-
)
230-
}
231-
}
232-
233-
@Composable
234-
@Preview
235-
fun ReplyBottomNavigationBar() {
236-
NavigationBar(modifier = Modifier.fillMaxWidth()) {
237-
NavigationBarItem(
238-
selected = true,
239-
onClick = { /*TODO*/ },
240-
icon = { Icon(imageVector = Icons.Default.Inbox, contentDescription = stringResource(id = R.string.tab_inbox)) }
241-
)
242-
NavigationBarItem(
243-
selected = false,
244-
onClick = { /*TODO*/ },
245-
icon = { Icon(imageVector = Icons.Default.Article, contentDescription = stringResource(id = R.string.tab_inbox)) }
246-
)
247-
NavigationBarItem(
248-
selected = false,
249-
onClick = { /*TODO*/ },
250-
icon = { Icon(imageVector = Icons.Outlined.Chat, contentDescription = stringResource(id = R.string.tab_inbox)) }
251-
)
252-
NavigationBarItem(
253-
selected = false,
254-
onClick = { /*TODO*/ },
255-
icon = { Icon(imageVector = Icons.Outlined.Videocam, contentDescription = stringResource(id = R.string.tab_inbox)) }
124+
} else {
125+
ReplyListOnlyContent(
126+
replyHomeUIState = replyHomeUIState,
127+
modifier = Modifier.fillMaxSize(),
256128
)
257129
}
258130
}
259131

260-
@OptIn(ExperimentalMaterial3Api::class)
261132
@Composable
262133
fun NavigationDrawerContent(
263-
selectedDestination: String,
134+
selectedDestination: ReplyDestination,
264135
modifier: Modifier = Modifier,
265136
onDrawerClicked: () -> Unit = {}
266137
) {
@@ -291,33 +162,21 @@ fun NavigationDrawerContent(
291162
}
292163
}
293164

294-
NavigationDrawerItem(
295-
selected = selectedDestination == ReplyDestinations.INBOX,
296-
label = { Text(text = stringResource(id = R.string.tab_inbox), modifier = Modifier.padding(horizontal = 16.dp)) },
297-
icon = { Icon(imageVector = Icons.Default.Inbox, contentDescription = stringResource(id = R.string.tab_inbox)) },
298-
colors = NavigationDrawerItemDefaults.colors(unselectedContainerColor = Color.Transparent),
299-
onClick = { /*TODO*/ }
300-
)
301-
NavigationDrawerItem(
302-
selected = selectedDestination == ReplyDestinations.ARTICLES,
303-
label = { Text(text = stringResource(id = R.string.tab_article), modifier = Modifier.padding(horizontal = 16.dp)) },
304-
icon = { Icon(imageVector = Icons.Default.Article, contentDescription = stringResource(id = R.string.tab_article)) },
305-
colors = NavigationDrawerItemDefaults.colors(unselectedContainerColor = Color.Transparent),
306-
onClick = { /*TODO*/ }
307-
)
308-
NavigationDrawerItem(
309-
selected = selectedDestination == ReplyDestinations.DM,
310-
label = { Text(text = stringResource(id = R.string.tab_dm), modifier = Modifier.padding(horizontal = 16.dp)) },
311-
icon = { Icon(imageVector = Icons.Default.Chat, contentDescription = stringResource(id = R.string.tab_dm)) },
312-
colors = NavigationDrawerItemDefaults.colors(unselectedContainerColor = Color.Transparent),
313-
onClick = { /*TODO*/ }
314-
)
315-
NavigationDrawerItem(
316-
selected = selectedDestination == ReplyDestinations.GROUPS,
317-
label = { Text(text = stringResource(id = R.string.tab_groups), modifier = Modifier.padding(horizontal = 16.dp)) },
318-
icon = { Icon(imageVector = Icons.Default.Article, contentDescription = stringResource(id = R.string.tab_groups)) },
319-
colors = NavigationDrawerItemDefaults.colors(unselectedContainerColor = Color.Transparent),
320-
onClick = { /*TODO*/ }
321-
)
165+
ReplyDestination.entries.forEach {
166+
NavigationDrawerItem(
167+
selected = selectedDestination == it,
168+
label = {
169+
Text(
170+
text = stringResource(it.labelRes),
171+
modifier = Modifier.padding(horizontal = 16.dp)
172+
)
173+
},
174+
icon = { Icon(it.icon, stringResource(it.labelRes)) },
175+
colors = NavigationDrawerItemDefaults.colors(
176+
unselectedContainerColor = Color.Transparent
177+
),
178+
onClick = { /*TODO*/ },
179+
)
180+
}
322181
}
323182
}

AdaptiveUiCodelab/app/src/main/java/com/example/reply/ui/ReplyNavigation.kt

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,24 @@
1616

1717
package com.example.reply.ui
1818

19-
object ReplyDestinations {
20-
const val INBOX = "Inbox"
21-
const val ARTICLES = "Articles"
22-
const val DM = "DirectMessages"
23-
const val GROUPS = "Groups"
24-
}
19+
import androidx.annotation.StringRes
20+
import androidx.compose.material.icons.Icons
21+
import androidx.compose.material.icons.filled.Article
22+
import androidx.compose.material.icons.filled.Menu
23+
import androidx.compose.material.icons.outlined.Chat
24+
import androidx.compose.material.icons.outlined.People
25+
import androidx.compose.ui.graphics.vector.ImageVector
26+
import com.example.reply.R
27+
28+
enum class ReplyDestination(
29+
@StringRes val labelRes: Int,
30+
val icon: ImageVector,
31+
) {
32+
Inbox(R.string.tab_inbox, Icons.Default.Menu),
33+
34+
Articles(R.string.tab_article, Icons.Default.Article),
35+
36+
Messages(R.string.tab_dm, Icons.Outlined.Chat),
37+
38+
Groups(R.string.tab_groups, Icons.Outlined.People),
39+
}

AdaptiveUiCodelab/app/src/main/res/values/strings.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
<string name="navigation_drawer">Navigation Drawer</string>
44
<string name="tab_inbox">Inbox</string>
55
<string name="tab_article">Articles</string>
6-
<string name="tab_dm">Direct Messages</string>
6+
<string name="tab_dm">Messages</string>
77
<string name="tab_groups">Groups</string>
88

99
<string name="profile">Profile</string>
1010
<string name="search">Search</string>
1111
<string name="search_replies">search replies</string>
1212
<string name="reply">Reply</string>
1313
<string name="reply_all">Reply All</string>
14-
</resources>
14+
</resources>

AdaptiveUiCodelab/gradle/libs.versions.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ kotlinxCoroutinesAndroid = "1.8.0"
1111
lifecycleViewmodelCompose = "2.7.0"
1212
lifecycleRuntimeKtx = "2.7.0"
1313
material3Adaptive = "1.0.0-alpha12"
14+
material3AdaptiveNavSuite = "1.0.0-alpha07"
1415
window = "1.2.0"
1516

1617
[libraries]
@@ -24,6 +25,7 @@ androidx-lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-
2425
androidx-material-icons-extended = { module = "androidx.compose.material:material-icons-extended" }
2526
androidx-material3 = { module = "androidx.compose.material3:material3" }
2627
androidx-material3-adaptive = { module = "androidx.compose.material3.adaptive:adaptive", version.ref = "material3Adaptive" }
28+
androidx-material3-adaptive-nav-suite = { module = "androidx.compose.material3:material3-adaptive-navigation-suite", version.ref = "material3AdaptiveNavSuite" }
2729
androidx-ui-test-manifest = { module = "androidx.compose.ui:ui-test-manifest" }
2830
androidx-ui-tooling = { module = "androidx.compose.ui:ui-tooling" }
2931
androidx-ui-test-junit4 = { module = "androidx.compose.ui:ui-test-junit4" }

0 commit comments

Comments
 (0)