Skip to content

Commit 73d0c75

Browse files
authored
Merge pull request #455 from android/jdk/adaptive-codelab-end
[AdaptiveUiCodelab] Use NavigationSuiteScaffold
2 parents 8adf820 + 21359ec commit 73d0c75

File tree

5 files changed

+55
-248
lines changed

5 files changed

+55
-248
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: 29 additions & 240 deletions
Original file line numberDiff line numberDiff line change
@@ -16,58 +16,21 @@
1616

1717
package com.example.reply.ui
1818

19-
import androidx.compose.animation.AnimatedVisibility
20-
import androidx.compose.foundation.background
21-
import androidx.compose.foundation.layout.Arrangement
22-
import androidx.compose.foundation.layout.Column
23-
import androidx.compose.foundation.layout.Row
24-
import androidx.compose.foundation.layout.fillMaxHeight
2519
import androidx.compose.foundation.layout.fillMaxSize
26-
import androidx.compose.foundation.layout.fillMaxWidth
27-
import androidx.compose.foundation.layout.padding
28-
import androidx.compose.foundation.layout.wrapContentWidth
29-
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
34-
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
4020
import androidx.compose.material3.Icon
41-
import androidx.compose.material3.IconButton
42-
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
47-
import androidx.compose.material3.NavigationDrawerItem
48-
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
5321
import androidx.compose.material3.Text
54-
import androidx.compose.material3.rememberDrawerState
22+
import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffold
5523
import androidx.compose.runtime.Composable
56-
import androidx.compose.runtime.rememberCoroutineScope
57-
import androidx.compose.ui.Alignment
24+
import androidx.compose.runtime.getValue
25+
import androidx.compose.runtime.mutableStateOf
26+
import androidx.compose.runtime.remember
27+
import androidx.compose.runtime.setValue
5828
import androidx.compose.ui.Modifier
59-
import androidx.compose.ui.graphics.Color
6029
import androidx.compose.ui.res.stringResource
61-
import androidx.compose.ui.tooling.preview.Preview
62-
import androidx.compose.ui.unit.dp
6330
import androidx.window.core.layout.WindowWidthSizeClass
64-
import com.example.reply.R
6531
import com.example.reply.ui.utils.DevicePosture
6632
import com.example.reply.ui.utils.ReplyContentType
67-
import com.example.reply.ui.utils.ReplyNavigationType
68-
import kotlinx.coroutines.launch
6933

70-
@OptIn(ExperimentalMaterial3Api::class)
7134
@Composable
7235
fun ReplyApp(
7336
windowSize: WindowWidthSizeClass,
@@ -81,243 +44,69 @@ fun ReplyApp(
8144
* In the state of folding device If it's half fold in BookPosture we want to avoid content
8245
* at the crease/hinge
8346
*/
84-
val navigationType: ReplyNavigationType
8547
val contentType: ReplyContentType
8648

8749
when (windowSize) {
8850
WindowWidthSizeClass.COMPACT -> {
89-
navigationType = ReplyNavigationType.BOTTOM_NAVIGATION
9051
contentType = ReplyContentType.LIST_ONLY
9152
}
9253
WindowWidthSizeClass.MEDIUM -> {
93-
navigationType = ReplyNavigationType.NAVIGATION_RAIL
9454
contentType = if (foldingDevicePosture != DevicePosture.NormalPosture) {
9555
ReplyContentType.LIST_AND_DETAIL
9656
} else {
9757
ReplyContentType.LIST_ONLY
9858
}
9959
}
10060
WindowWidthSizeClass.EXPANDED -> {
101-
navigationType = if (foldingDevicePosture is DevicePosture.BookPosture) {
102-
ReplyNavigationType.NAVIGATION_RAIL
103-
} else {
104-
ReplyNavigationType.PERMANENT_NAVIGATION_DRAWER
105-
}
10661
contentType = ReplyContentType.LIST_AND_DETAIL
10762
}
10863
else -> {
109-
navigationType = ReplyNavigationType.BOTTOM_NAVIGATION
11064
contentType = ReplyContentType.LIST_ONLY
11165
}
11266
}
11367

114-
ReplyNavigationWrapperUI(navigationType, contentType, replyHomeUIState)
68+
ReplyNavigationWrapperUI(contentType, replyHomeUIState)
11569
}
11670

117-
@OptIn(ExperimentalMaterial3Api::class)
11871
@Composable
11972
private fun ReplyNavigationWrapperUI(
120-
navigationType: ReplyNavigationType,
12173
contentType: ReplyContentType,
12274
replyHomeUIState: ReplyHomeUIState
12375
) {
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-
}
134-
}
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-
)
162-
}
76+
var selectedDestination: ReplyDestination by remember {
77+
mutableStateOf(ReplyDestination.Inbox)
16378
}
164-
}
165-
166-
@Composable
167-
fun ReplyAppContent(
168-
navigationType: ReplyNavigationType,
169-
contentType: ReplyContentType,
170-
replyHomeUIState: ReplyHomeUIState,
171-
onDrawerClicked: () -> Unit = {}
172-
) {
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),
79+
NavigationSuiteScaffold(
80+
navigationSuiteItems = {
81+
ReplyDestination.entries.forEach {
82+
item(
83+
label = { Text(stringResource(it.labelRes)) },
84+
icon = { Icon(it.icon, stringResource(it.labelRes)) },
85+
selected = it == selectedDestination,
86+
onClick = { /*TODO update selection*/ },
18787
)
188-
} else {
189-
ReplyListOnlyContent(replyHomeUIState = replyHomeUIState, modifier = Modifier.weight(1f))
190-
}
191-
192-
AnimatedVisibility(visible = navigationType == ReplyNavigationType.BOTTOM_NAVIGATION) {
193-
ReplyBottomNavigationBar()
19488
}
19589
}
90+
) {
91+
ReplyAppContent(contentType, replyHomeUIState)
19692
}
19793
}
19894

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)) }
214-
)
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)) }
256-
)
257-
}
258-
}
25995

260-
@OptIn(ExperimentalMaterial3Api::class)
26196
@Composable
262-
fun NavigationDrawerContent(
263-
selectedDestination: String,
264-
modifier: Modifier = Modifier,
265-
onDrawerClicked: () -> Unit = {}
97+
fun ReplyAppContent(
98+
contentType: ReplyContentType,
99+
replyHomeUIState: ReplyHomeUIState,
266100
) {
267-
Column(
268-
modifier
269-
.wrapContentWidth()
270-
.fillMaxHeight()
271-
.background(MaterialTheme.colorScheme.inverseOnSurface)
272-
.padding(24.dp)
273-
) {
274-
Row(
275-
modifier = modifier
276-
.fillMaxWidth()
277-
.padding(16.dp),
278-
horizontalArrangement = Arrangement.SpaceBetween,
279-
verticalAlignment = Alignment.CenterVertically
280-
) {
281-
Text(
282-
text = stringResource(id = R.string.app_name).uppercase(),
283-
style = MaterialTheme.typography.titleMedium,
284-
color = MaterialTheme.colorScheme.primary
285-
)
286-
IconButton(onClick = onDrawerClicked) {
287-
Icon(
288-
imageVector = Icons.Default.MenuOpen,
289-
contentDescription = stringResource(id = R.string.navigation_drawer)
290-
)
291-
}
292-
}
293-
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*/ }
101+
if (contentType == ReplyContentType.LIST_AND_DETAIL) {
102+
ReplyListAndDetailContent(
103+
replyHomeUIState = replyHomeUIState,
104+
modifier = Modifier.fillMaxSize(),
307105
)
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*/ }
106+
} else {
107+
ReplyListOnlyContent(
108+
replyHomeUIState = replyHomeUIState,
109+
modifier = Modifier.fillMaxSize(),
321110
)
322111
}
323112
}

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>

0 commit comments

Comments
 (0)