Skip to content

Commit f3a361f

Browse files
authored
Merge pull request #456 from android/jdk/adaptive-codelab/list-detail
[AdaptiveUiCodelab] Use ListDetailPaneScaffold
2 parents 73d0c75 + b7fba2b commit f3a361f

File tree

8 files changed

+116
-89
lines changed

8 files changed

+116
-89
lines changed

AdaptiveUiCodelab/app/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,9 @@ dependencies {
7373

7474
implementation(libs.androidx.material3)
7575
implementation(libs.androidx.material3.adaptive)
76+
implementation(libs.androidx.material3.adaptive.layout)
7677
implementation(libs.androidx.material3.adaptive.nav.suite)
78+
implementation(libs.androidx.material3.adaptive.navigation)
7779
implementation(libs.androidx.material.icons.extended)
7880
implementation(libs.androidx.ui.tooling.preview)
7981
androidTestImplementation(libs.androidx.ui.test.junit4)

AdaptiveUiCodelab/app/src/main/java/com/example/reply/data/Email.kt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ data class Email(
4848
var isStarred: Boolean = false,
4949
var mailbox: MailboxType = MailboxType.INBOX,
5050
var createAt: String,
51-
val threads: List<Email> = emptyList()
51+
val replies: List<Email> = emptyList()
5252
) {
5353
val senderPreview: String = "${sender.fullName} - 4 hrs ago"
5454
val hasBody: Boolean = body.isNotBlank()
@@ -59,5 +59,3 @@ data class Email(
5959
val nonUserAccountRecipients = recipients
6060
.filterNot { LocalAccountsDataProvider.isUserAccount(it.uid) }
6161
}
62-
63-

AdaptiveUiCodelab/app/src/main/java/com/example/reply/data/local/LocalEmailsDataProvider.kt

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import com.example.reply.data.MailboxType
2727

2828
object LocalEmailsDataProvider {
2929

30-
private val threads = listOf(
30+
private val replies = listOf(
3131
Email(
3232
4L,
3333
LocalAccountsDataProvider.getContactAccountByUid(11L),
@@ -116,7 +116,7 @@ object LocalEmailsDataProvider {
116116
""".trimIndent(),
117117
createAt = "20 mins ago",
118118
isStarred = true,
119-
threads = threads,
119+
replies = replies,
120120
),
121121
Email(
122122
1L,
@@ -133,7 +133,7 @@ object LocalEmailsDataProvider {
133133
Ali
134134
""".trimIndent(),
135135
createAt = "40 mins ago",
136-
threads = threads,
136+
replies = replies,
137137
),
138138
Email(
139139
2L,
@@ -149,7 +149,7 @@ object LocalEmailsDataProvider {
149149
),
150150
true,
151151
createAt = "1 hour ago",
152-
threads = threads,
152+
replies = replies,
153153
),
154154
Email(
155155
3L,
@@ -311,4 +311,3 @@ object LocalEmailsDataProvider {
311311
"Grocery coupons"
312312
)
313313
}
314-

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

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,12 @@ class MainActivity : ComponentActivity() {
7676
val windowAdaptiveInfo = currentWindowAdaptiveInfo()
7777
val devicePosture = devicePostureFlow.collectAsState().value
7878
val uiState = viewModel.uiState.collectAsState().value
79-
ReplyApp(windowAdaptiveInfo.windowSizeClass.windowWidthSizeClass, devicePosture, uiState)
79+
ReplyApp(
80+
windowAdaptiveInfo.windowSizeClass.windowWidthSizeClass,
81+
devicePosture,
82+
uiState,
83+
viewModel::setSelectedEmail
84+
)
8085
}
8186
}
8287
}
@@ -89,7 +94,8 @@ fun ReplyAppPreview() {
8994
ReplyApp(
9095
replyHomeUIState = ReplyHomeUIState(emails = LocalEmailsDataProvider.allEmails),
9196
windowSize = WindowWidthSizeClass.COMPACT,
92-
foldingDevicePosture = DevicePosture.NormalPosture
97+
foldingDevicePosture = DevicePosture.NormalPosture,
98+
onEmailClick = {}
9399
)
94100
}
95101
}
@@ -101,7 +107,8 @@ fun ReplyAppPreviewTablet() {
101107
ReplyApp(
102108
replyHomeUIState = ReplyHomeUIState(emails = LocalEmailsDataProvider.allEmails),
103109
windowSize = WindowWidthSizeClass.MEDIUM,
104-
foldingDevicePosture = DevicePosture.NormalPosture
110+
foldingDevicePosture = DevicePosture.NormalPosture,
111+
onEmailClick = {}
105112
)
106113
}
107114
}
@@ -113,7 +120,8 @@ fun ReplyAppPreviewDesktop() {
113120
ReplyApp(
114121
replyHomeUIState = ReplyHomeUIState(emails = LocalEmailsDataProvider.allEmails),
115122
windowSize = WindowWidthSizeClass.EXPANDED,
116-
foldingDevicePosture = DevicePosture.NormalPosture
123+
foldingDevicePosture = DevicePosture.NormalPosture,
124+
onEmailClick = {}
117125
)
118126
}
119127
}

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

Lines changed: 46 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -16,62 +16,43 @@
1616

1717
package com.example.reply.ui
1818

19-
import androidx.compose.foundation.layout.fillMaxSize
19+
import androidx.activity.compose.BackHandler
2020
import androidx.compose.material3.Icon
2121
import androidx.compose.material3.Text
22+
import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
23+
import androidx.compose.material3.adaptive.layout.AnimatedPane
24+
import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffold
25+
import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldRole
26+
import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator
2227
import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffold
2328
import androidx.compose.runtime.Composable
2429
import androidx.compose.runtime.getValue
2530
import androidx.compose.runtime.mutableStateOf
2631
import androidx.compose.runtime.remember
2732
import androidx.compose.runtime.setValue
28-
import androidx.compose.ui.Modifier
2933
import androidx.compose.ui.res.stringResource
3034
import androidx.window.core.layout.WindowWidthSizeClass
35+
import com.example.reply.data.Email
3136
import com.example.reply.ui.utils.DevicePosture
32-
import com.example.reply.ui.utils.ReplyContentType
3337

3438
@Composable
3539
fun ReplyApp(
3640
windowSize: WindowWidthSizeClass,
3741
foldingDevicePosture: DevicePosture,
38-
replyHomeUIState: ReplyHomeUIState
42+
replyHomeUIState: ReplyHomeUIState,
43+
onEmailClick: (Email) -> Unit,
3944
) {
40-
/**
41-
* This will help us select type of navigation and content type depending on window size and
42-
* fold state of the device.
43-
*
44-
* In the state of folding device If it's half fold in BookPosture we want to avoid content
45-
* at the crease/hinge
46-
*/
47-
val contentType: ReplyContentType
48-
49-
when (windowSize) {
50-
WindowWidthSizeClass.COMPACT -> {
51-
contentType = ReplyContentType.LIST_ONLY
52-
}
53-
WindowWidthSizeClass.MEDIUM -> {
54-
contentType = if (foldingDevicePosture != DevicePosture.NormalPosture) {
55-
ReplyContentType.LIST_AND_DETAIL
56-
} else {
57-
ReplyContentType.LIST_ONLY
58-
}
59-
}
60-
WindowWidthSizeClass.EXPANDED -> {
61-
contentType = ReplyContentType.LIST_AND_DETAIL
62-
}
63-
else -> {
64-
contentType = ReplyContentType.LIST_ONLY
65-
}
45+
ReplyNavigationWrapperUI {
46+
ReplyAppContent(
47+
replyHomeUIState = replyHomeUIState,
48+
onEmailClick = onEmailClick
49+
)
6650
}
67-
68-
ReplyNavigationWrapperUI(contentType, replyHomeUIState)
6951
}
7052

7153
@Composable
7254
private fun ReplyNavigationWrapperUI(
73-
contentType: ReplyContentType,
74-
replyHomeUIState: ReplyHomeUIState
55+
content: @Composable () -> Unit = {}
7556
) {
7657
var selectedDestination: ReplyDestination by remember {
7758
mutableStateOf(ReplyDestination.Inbox)
@@ -88,25 +69,44 @@ private fun ReplyNavigationWrapperUI(
8869
}
8970
}
9071
) {
91-
ReplyAppContent(contentType, replyHomeUIState)
72+
content()
9273
}
9374
}
9475

9576

77+
@OptIn(ExperimentalMaterial3AdaptiveApi::class)
9678
@Composable
9779
fun ReplyAppContent(
98-
contentType: ReplyContentType,
9980
replyHomeUIState: ReplyHomeUIState,
81+
onEmailClick: (Email) -> Unit,
10082
) {
101-
if (contentType == ReplyContentType.LIST_AND_DETAIL) {
102-
ReplyListAndDetailContent(
103-
replyHomeUIState = replyHomeUIState,
104-
modifier = Modifier.fillMaxSize(),
105-
)
106-
} else {
107-
ReplyListOnlyContent(
108-
replyHomeUIState = replyHomeUIState,
109-
modifier = Modifier.fillMaxSize(),
110-
)
83+
val selectedEmail = replyHomeUIState.selectedEmail
84+
val navigator = rememberListDetailPaneScaffoldNavigator<Long>()
85+
86+
BackHandler(navigator.canNavigateBack()) {
87+
navigator.navigateBack()
11188
}
89+
90+
ListDetailPaneScaffold(
91+
directive = navigator.scaffoldDirective,
92+
value = navigator.scaffoldValue,
93+
listPane = {
94+
AnimatedPane {
95+
ReplyListPane(
96+
replyHomeUIState = replyHomeUIState,
97+
onEmailClick = { email ->
98+
onEmailClick(email)
99+
navigator.navigateTo(ListDetailPaneScaffoldRole.Detail, email.id)
100+
}
101+
)
102+
}
103+
},
104+
detailPane = {
105+
AnimatedPane {
106+
if (selectedEmail != null) {
107+
ReplyDetailPane(selectedEmail)
108+
}
109+
}
110+
}
111+
)
112112
}

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

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import com.example.reply.data.EmailsRepositoryImpl
2424
import kotlinx.coroutines.flow.MutableStateFlow
2525
import kotlinx.coroutines.flow.StateFlow
2626
import kotlinx.coroutines.flow.catch
27+
import kotlinx.coroutines.flow.update
2728
import kotlinx.coroutines.launch
2829

2930
class ReplyHomeViewModel(private val emailsRepository: EmailsRepository = EmailsRepositoryImpl()): ViewModel() {
@@ -43,14 +44,26 @@ class ReplyHomeViewModel(private val emailsRepository: EmailsRepository = Emails
4344
_uiState.value = ReplyHomeUIState(error = ex.message)
4445
}
4546
.collect { emails ->
46-
_uiState.value = ReplyHomeUIState(emails = emails)
47+
// If nothing is selected, initially select the first element.
48+
val currentSelected = _uiState.value.selectedEmail
49+
_uiState.value = ReplyHomeUIState(
50+
emails = emails,
51+
selectedEmail = currentSelected ?: emails.firstOrNull()
52+
)
4753
}
4854
}
4955
}
56+
57+
fun setSelectedEmail(email: Email) {
58+
_uiState.update {
59+
it.copy(selectedEmail = email)
60+
}
61+
}
5062
}
5163

5264
data class ReplyHomeUIState(
5365
val emails : List<Email> = emptyList(),
66+
val selectedEmail: Email? = null,
5467
val loading: Boolean = false,
5568
val error: String? = null
56-
)
69+
)

0 commit comments

Comments
 (0)