@@ -18,25 +18,36 @@ package com.google.samples.apps.nowinandroid.ui.interests2pane
18
18
19
19
import androidx.activity.compose.BackHandler
20
20
import androidx.annotation.Keep
21
+ import androidx.compose.foundation.interaction.MutableInteractionSource
22
+ import androidx.compose.foundation.layout.Box
23
+ import androidx.compose.material3.LocalMinimumInteractiveComponentSize
24
+ import androidx.compose.material3.VerticalDragHandle
21
25
import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
22
26
import androidx.compose.material3.adaptive.WindowAdaptiveInfo
23
27
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
24
28
import androidx.compose.material3.adaptive.layout.AnimatedPane
25
29
import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffold
26
30
import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldRole
27
31
import androidx.compose.material3.adaptive.layout.PaneAdaptedValue
32
+ import androidx.compose.material3.adaptive.layout.PaneExpansionAnchor
28
33
import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldDestinationItem
29
34
import androidx.compose.material3.adaptive.layout.calculatePaneScaffoldDirective
35
+ import androidx.compose.material3.adaptive.layout.rememberPaneExpansionState
30
36
import androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator
31
37
import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator
32
38
import androidx.compose.runtime.Composable
33
39
import androidx.compose.runtime.getValue
34
40
import androidx.compose.runtime.key
35
41
import androidx.compose.runtime.mutableStateOf
36
42
import androidx.compose.runtime.remember
43
+ import androidx.compose.runtime.rememberCoroutineScope
37
44
import androidx.compose.runtime.saveable.Saver
38
45
import androidx.compose.runtime.saveable.rememberSaveable
39
46
import androidx.compose.runtime.setValue
47
+ import androidx.compose.ui.Modifier
48
+ import androidx.compose.ui.draw.clipToBounds
49
+ import androidx.compose.ui.layout.layout
50
+ import androidx.compose.ui.unit.dp
40
51
import androidx.hilt.navigation.compose.hiltViewModel
41
52
import androidx.lifecycle.compose.collectAsStateWithLifecycle
42
53
import androidx.navigation.NavGraphBuilder
@@ -49,8 +60,10 @@ import com.google.samples.apps.nowinandroid.feature.topic.TopicDetailPlaceholder
49
60
import com.google.samples.apps.nowinandroid.feature.topic.navigation.TopicRoute
50
61
import com.google.samples.apps.nowinandroid.feature.topic.navigation.navigateToTopic
51
62
import com.google.samples.apps.nowinandroid.feature.topic.navigation.topicScreen
63
+ import kotlinx.coroutines.launch
52
64
import kotlinx.serialization.Serializable
53
65
import java.util.UUID
66
+ import kotlin.math.max
54
67
55
68
@Serializable internal object TopicPlaceholderRoute
56
69
@@ -93,8 +106,20 @@ internal fun InterestsListDetailScreen(
93
106
},
94
107
),
95
108
)
109
+ val coroutineScope = rememberCoroutineScope()
110
+
111
+ val paneExpansionState = rememberPaneExpansionState(
112
+ anchors = listOf (
113
+ PaneExpansionAnchor .Proportion (0f ),
114
+ PaneExpansionAnchor .Proportion (0.5f ),
115
+ PaneExpansionAnchor .Proportion (1f ),
116
+ ),
117
+ )
118
+
96
119
BackHandler (listDetailNavigator.canNavigateBack()) {
97
- listDetailNavigator.navigateBack()
120
+ coroutineScope.launch {
121
+ listDetailNavigator.navigateBack()
122
+ }
98
123
}
99
124
100
125
var nestedNavHostStartRoute by remember {
@@ -123,40 +148,99 @@ internal fun InterestsListDetailScreen(
123
148
nestedNavHostStartRoute = TopicRoute (id = topicId)
124
149
nestedNavKey = UUID .randomUUID()
125
150
}
126
- listDetailNavigator.navigateTo(ListDetailPaneScaffoldRole .Detail )
151
+ coroutineScope.launch {
152
+ listDetailNavigator.navigateTo(ListDetailPaneScaffoldRole .Detail )
153
+ }
127
154
}
128
155
156
+ val mutableInteractionSource = remember { MutableInteractionSource () }
157
+ val minPaneWidth = 300 .dp
158
+
129
159
ListDetailPaneScaffold (
130
160
value = listDetailNavigator.scaffoldValue,
131
161
directive = listDetailNavigator.scaffoldDirective,
132
162
listPane = {
133
163
AnimatedPane {
134
- InterestsRoute (
135
- onTopicClick = ::onTopicClickShowDetailPane,
136
- highlightSelectedTopic = listDetailNavigator.isDetailPaneVisible(),
137
- )
164
+ Box (
165
+ modifier = Modifier .clipToBounds()
166
+ .layout { measurable, constraints ->
167
+ val width = max(minPaneWidth.roundToPx(), constraints.maxWidth)
168
+ val placeable = measurable.measure(
169
+ constraints.copy(
170
+ minWidth = minPaneWidth.roundToPx(),
171
+ maxWidth = width,
172
+ ),
173
+ )
174
+ layout(constraints.maxWidth, placeable.height) {
175
+ placeable.placeRelative(
176
+ x = 0 ,
177
+ y = 0 ,
178
+ )
179
+ }
180
+ },
181
+ ) {
182
+ InterestsRoute (
183
+ onTopicClick = ::onTopicClickShowDetailPane,
184
+ highlightSelectedTopic = listDetailNavigator.isDetailPaneVisible(),
185
+ )
186
+ }
138
187
}
139
188
},
140
189
detailPane = {
141
190
AnimatedPane {
142
- key(nestedNavKey) {
143
- NavHost (
144
- navController = nestedNavController,
145
- startDestination = nestedNavHostStartRoute,
146
- route = DetailPaneNavHostRoute ::class ,
147
- ) {
148
- topicScreen(
149
- showBackButton = ! listDetailNavigator.isListPaneVisible(),
150
- onBackClick = listDetailNavigator::navigateBack,
151
- onTopicClick = ::onTopicClickShowDetailPane,
152
- )
153
- composable<TopicPlaceholderRoute > {
154
- TopicDetailPlaceholder ()
191
+ Box (
192
+ modifier = Modifier .clipToBounds()
193
+ .layout { measurable, constraints ->
194
+ val width = max(minPaneWidth.roundToPx(), constraints.maxWidth)
195
+ val placeable = measurable.measure(
196
+ constraints.copy(
197
+ minWidth = minPaneWidth.roundToPx(),
198
+ maxWidth = width,
199
+ ),
200
+ )
201
+ layout(constraints.maxWidth, placeable.height) {
202
+ placeable.placeRelative(
203
+ x = constraints.maxWidth -
204
+ max(constraints.maxWidth, placeable.width),
205
+ y = 0 ,
206
+ )
207
+ }
208
+ },
209
+ ) {
210
+ key(nestedNavKey) {
211
+ NavHost (
212
+ navController = nestedNavController,
213
+ startDestination = nestedNavHostStartRoute,
214
+ route = DetailPaneNavHostRoute ::class ,
215
+ ) {
216
+ topicScreen(
217
+ showBackButton = ! listDetailNavigator.isListPaneVisible(),
218
+ onBackClick = {
219
+ coroutineScope.launch {
220
+ listDetailNavigator.navigateBack()
221
+ }
222
+ },
223
+ onTopicClick = ::onTopicClickShowDetailPane,
224
+ )
225
+ composable<TopicPlaceholderRoute > {
226
+ TopicDetailPlaceholder ()
227
+ }
155
228
}
156
229
}
157
230
}
158
231
}
159
232
},
233
+ paneExpansionState = paneExpansionState,
234
+ paneExpansionDragHandle = {
235
+ VerticalDragHandle (
236
+ modifier = Modifier .paneExpansionDraggable(
237
+ state = paneExpansionState,
238
+ minTouchTargetSize = LocalMinimumInteractiveComponentSize .current,
239
+ interactionSource = mutableInteractionSource,
240
+ ),
241
+ interactionSource = mutableInteractionSource,
242
+ )
243
+ },
160
244
)
161
245
}
162
246
0 commit comments