Skip to content
This repository was archived by the owner on Jan 5, 2023. It is now read-only.

Commit d16342b

Browse files
committed
Create two-pane schedule layout
- Add SlidingPaneLayout and create ScheduleTwoPaneFragment. - Make two new navigation graphs, one for each pane. - List pane can show the Schedule and Search destinations. - Detail pane can show Session Detail and Speaker destinations. - Create a ViewModel that scopes to the ScheduleTwoPaneFragment and have the various other fragments acquire it. - Route session selection and starring through the new ViewModel. - Pull the SnackBar logic up into ScheduleTwoPaneFragment and its ViewModel. Bug: 182714988 Change-Id: I0ce4a4c51adf410774d32692ba705be596f987ab
1 parent fb3567d commit d16342b

File tree

32 files changed

+435
-508
lines changed

32 files changed

+435
-508
lines changed

buildSrc/src/main/java/Libs.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ object Libs {
8080
const val ROOM_RUNTIME = "androidx.room:room-runtime"
8181
const val RULES = "androidx.test:rules"
8282
const val RUNNER = "androidx.test:runner"
83+
const val SLIDING_PANE_LAYOUT = "androidx.slidingpanelayout:slidingpanelayout"
8384
const val THREETENABP = "com.jakewharton.threetenabp:threetenabp"
8485
const val THREETENBP = "org.threeten:threetenbp"
8586
const val TIMBER = "com.jakewharton.timber:timber"

depconstraints/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ val playCore = "1.6.5"
6464
val room = "2.2.5"
6565
val rules = "1.1.1"
6666
val runner = "1.2.0"
67+
val slidingpanelayout = "1.2.0-alpha01"
6768
val threetenabp = "1.0.5"
6869
val timber = "4.7.1"
6970
val viewpager2 = "1.0.0"
@@ -136,6 +137,7 @@ dependencies {
136137
api("${Libs.INK_PAGE_INDICATOR}:$pageIndicator")
137138
api("${Libs.RULES}:$rules")
138139
api("${Libs.RUNNER}:$runner")
140+
api("${Libs.SLIDING_PANE_LAYOUT}:$slidingpanelayout")
139141
api("${Libs.THREETENABP}:$threetenabp")
140142
api("${Libs.THREETENBP}:${Versions.THREETENBP}")
141143
api("${Libs.TIMBER}:$timber")

mobile/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ dependencies {
192192
implementation(Libs.FLEXBOX)
193193
implementation(Libs.LOTTIE)
194194
implementation(Libs.INK_PAGE_INDICATOR)
195+
implementation(Libs.SLIDING_PANE_LAYOUT)
195196

196197
// Architecture Components
197198
implementation(Libs.LIFECYCLE_LIVE_DATA_KTX)

mobile/src/androidTest/java/com/google/samples/apps/iosched/tests/ui/ScheduleTest.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import com.google.samples.apps.iosched.tests.SetPreferencesRule
3232
import com.google.samples.apps.iosched.ui.sessioncommon.SessionViewHolder
3333
import dagger.hilt.android.testing.HiltAndroidRule
3434
import dagger.hilt.android.testing.HiltAndroidTest
35+
import org.junit.Ignore
3536
import org.junit.Rule
3637
import org.junit.Test
3738
import org.junit.runner.RunWith
@@ -66,6 +67,10 @@ class ScheduleTest {
6667
}
6768

6869
@Test
70+
@Ignore(
71+
"Session Details are shown in another pane owned by a parent fragment. This test runs in " +
72+
"a test Activity that does not include the other pane, so it will never pass."
73+
)
6974
fun clickOnFirstItem_detailsShown() {
7075
onView(withId(R.id.recyclerview_schedule))
7176
.perform(RecyclerViewActions.actionOnItemAtPosition<SessionViewHolder>(0, click()))

mobile/src/main/java/com/google/samples/apps/iosched/ui/feed/FeedFragment.kt

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,6 @@ import com.google.samples.apps.iosched.model.SessionId
3434
import com.google.samples.apps.iosched.shared.analytics.AnalyticsHelper
3535
import com.google.samples.apps.iosched.ui.MainActivityViewModel
3636
import com.google.samples.apps.iosched.ui.MainNavigationFragment
37-
import com.google.samples.apps.iosched.ui.feed.FeedFragmentDirections.Companion.toSchedule
38-
import com.google.samples.apps.iosched.ui.feed.FeedFragmentDirections.Companion.toSessionDetail
3937
import com.google.samples.apps.iosched.ui.feed.FeedNavigationAction.NavigateAction
4038
import com.google.samples.apps.iosched.ui.feed.FeedNavigationAction.NavigateToScheduleAction
4139
import com.google.samples.apps.iosched.ui.feed.FeedNavigationAction.NavigateToSession
@@ -144,7 +142,8 @@ class FeedFragment : MainNavigationFragment() {
144142
model.navigationActions.collect { action ->
145143
when (action) {
146144
is NavigateAction -> findNavController().navigate(action.directions)
147-
is NavigateToScheduleAction -> openSchedule(action.showOnlyPinnedSessions)
145+
NavigateToScheduleAction ->
146+
findNavController().navigate(FeedFragmentDirections.toSchedule())
148147
is NavigateToSession -> openSessionDetail(action.sessionId)
149148
is OpenLiveStreamAction -> openLiveStreamUrl(action.url)
150149
OpenSignInDialogAction -> openSignInDialog()
@@ -154,15 +153,8 @@ class FeedFragment : MainNavigationFragment() {
154153
}
155154

156155
private fun openSessionDetail(id: SessionId) {
157-
findNavController().navigate(toSessionDetail(id))
158-
}
159-
160-
private fun openSchedule(withPinnedSessions: Boolean) {
161-
if (withPinnedSessions) {
162-
findNavController().navigate(toSchedule(showPinnedEvents = true))
163-
} else {
164-
findNavController().navigate(toSchedule(showAllEvents = true))
165-
}
156+
// TODO support opening a session detail
157+
// findNavController().navigate(toSessionDetail(id))
166158
}
167159

168160
private fun showFeedItems(recyclerView: RecyclerView, list: List<Any>?) {

mobile/src/main/java/com/google/samples/apps/iosched/ui/feed/FeedViewModel.kt

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ import com.google.samples.apps.iosched.shared.util.tryOffer
4949
import com.google.samples.apps.iosched.ui.messages.SnackbarMessage
5050
import com.google.samples.apps.iosched.ui.messages.SnackbarMessageManager
5151
import com.google.samples.apps.iosched.ui.sessioncommon.EventActions
52-
import com.google.samples.apps.iosched.ui.sessiondetail.SessionDetailFragmentDirections
5352
import com.google.samples.apps.iosched.ui.signin.SignInViewModelDelegate
5453
import com.google.samples.apps.iosched.ui.theme.ThemedActivityDelegate
5554
import com.google.samples.apps.iosched.util.WhileViewSubscribed
@@ -277,9 +276,7 @@ class FeedViewModel @Inject constructor(
277276

278277
override fun openSchedule(showOnlyPinnedSessions: Boolean) {
279278
analyticsHelper.logUiEvent("Home to Schedule", AnalyticsActions.HOME_TO_SCHEDULE)
280-
_navigationActions.tryOffer(
281-
FeedNavigationAction.NavigateToScheduleAction(showOnlyPinnedSessions)
282-
)
279+
_navigationActions.tryOffer(FeedNavigationAction.NavigateToScheduleAction)
283280
}
284281

285282
override fun onStarClicked(userSession: UserSession) {
@@ -295,7 +292,7 @@ class FeedViewModel @Inject constructor(
295292
analyticsHelper.logUiEvent(moment.title.toString(), AnalyticsActions.HOME_TO_MAP)
296293
_navigationActions.tryOffer(
297294
FeedNavigationAction.NavigateAction(
298-
SessionDetailFragmentDirections.toMap(
295+
FeedFragmentDirections.toMap(
299296
featureId = moment.featureId,
300297
startTime = moment.startTime.toEpochMilli()
301298
)
@@ -310,7 +307,7 @@ class FeedViewModel @Inject constructor(
310307

311308
override fun openMapForSession(session: Session) {
312309
analyticsHelper.logUiEvent(session.id, AnalyticsActions.HOME_TO_MAP)
313-
val directions = SessionDetailFragmentDirections.toMap(
310+
val directions = FeedFragmentDirections.toMap(
314311
featureId = session.room?.id,
315312
startTime = session.startTime.toEpochMilli()
316313
)
@@ -338,5 +335,5 @@ sealed class FeedNavigationAction {
338335
class NavigateAction(val directions: NavDirections) : FeedNavigationAction()
339336
object OpenSignInDialogAction : FeedNavigationAction()
340337
class OpenLiveStreamAction(val url: String) : FeedNavigationAction()
341-
class NavigateToScheduleAction(val showOnlyPinnedSessions: Boolean) : FeedNavigationAction()
338+
object NavigateToScheduleAction : FeedNavigationAction()
342339
}

mobile/src/main/java/com/google/samples/apps/iosched/ui/schedule/ScheduleFragment.kt

Lines changed: 4 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ import androidx.recyclerview.widget.RecyclerView.RecycledViewPool
3434
import com.google.samples.apps.iosched.R
3535
import com.google.samples.apps.iosched.databinding.FragmentScheduleBinding
3636
import com.google.samples.apps.iosched.model.ConferenceDay
37-
import com.google.samples.apps.iosched.model.SessionId
3837
import com.google.samples.apps.iosched.shared.analytics.AnalyticsActions
3938
import com.google.samples.apps.iosched.shared.analytics.AnalyticsHelper
4039
import com.google.samples.apps.iosched.shared.di.SearchScheduleEnabledFlag
@@ -43,14 +42,10 @@ import com.google.samples.apps.iosched.shared.util.TimeUtils
4342
import com.google.samples.apps.iosched.ui.MainActivityViewModel
4443
import com.google.samples.apps.iosched.ui.MainNavigationFragment
4544
import com.google.samples.apps.iosched.ui.messages.SnackbarMessageManager
46-
import com.google.samples.apps.iosched.ui.schedule.ScheduleFragmentDirections.Companion.toSearch
47-
import com.google.samples.apps.iosched.ui.schedule.ScheduleFragmentDirections.Companion.toSessionDetail
48-
import com.google.samples.apps.iosched.ui.schedule.ScheduleNavigationAction.NavigateToSession
4945
import com.google.samples.apps.iosched.ui.schedule.ScheduleNavigationAction.NavigateToSignInDialogAction
5046
import com.google.samples.apps.iosched.ui.schedule.ScheduleNavigationAction.NavigateToSignOutDialogAction
5147
import com.google.samples.apps.iosched.ui.schedule.ScheduleNavigationAction.ShowScheduleUiHints
5248
import com.google.samples.apps.iosched.ui.sessioncommon.SessionsAdapter
53-
import com.google.samples.apps.iosched.ui.messages.setupSnackbarManager
5449
import com.google.samples.apps.iosched.ui.signin.NotificationsPreferenceDialogFragment
5550
import com.google.samples.apps.iosched.ui.signin.NotificationsPreferenceDialogFragment.Companion.DIALOG_NOTIFICATIONS_PREFERENCE
5651
import com.google.samples.apps.iosched.ui.signin.SignInDialogFragment
@@ -98,6 +93,7 @@ class ScheduleFragment : MainNavigationFragment() {
9893
lateinit var snackbarMessageManager: SnackbarMessageManager
9994

10095
private val scheduleViewModel: ScheduleViewModel by viewModels()
96+
private val scheduleTwoPaneViewModel: ScheduleTwoPaneViewModel by activityViewModels()
10197
private val mainActivityViewModel: MainActivityViewModel by activityViewModels()
10298

10399
private lateinit var snackbar: FadingSnackbar
@@ -149,9 +145,6 @@ class ScheduleFragment : MainNavigationFragment() {
149145
}
150146
}
151147

152-
// Snackbar configuration
153-
setupSnackbarManager(snackbarMessageManager, snackbar)
154-
155148
binding.includeScheduleAppbar.toolbar.setupProfileMenuItem(mainActivityViewModel, this)
156149

157150
// Pad the bottom of the RecyclerView so that the content scrolls up above the nav bar
@@ -161,11 +154,11 @@ class ScheduleFragment : MainNavigationFragment() {
161154

162155
// Session list configuration
163156
sessionsAdapter = SessionsAdapter(
164-
scheduleViewModel,
157+
scheduleTwoPaneViewModel,
165158
tagViewPool,
166159
scheduleViewModel.showReservations,
167160
scheduleViewModel.timeZoneId,
168-
this
161+
viewLifecycleOwner
169162
)
170163
scheduleRecyclerView.apply {
171164
adapter = sessionsAdapter
@@ -219,7 +212,6 @@ class ScheduleFragment : MainNavigationFragment() {
219212
launch {
220213
scheduleViewModel.navigationActions.collect {
221214
when (it) {
222-
is NavigateToSession -> openSessionDetail(it.sessionId)
223215
is NavigateToSignInDialogAction -> openSignInDialog()
224216
is NavigateToSignOutDialogAction -> openSignOutDialog()
225217
is ShowScheduleUiHints -> openScheduleUiHintsDialog()
@@ -252,16 +244,6 @@ class ScheduleFragment : MainNavigationFragment() {
252244
},
253245
500
254246
)
255-
256-
// Process arguments to set initial filters
257-
arguments?.let {
258-
if (ScheduleFragmentArgs.fromBundle(it).showMySchedule) {
259-
scheduleViewModel.showMySchedule()
260-
}
261-
if (ScheduleFragmentArgs.fromBundle(it).showAllEvents) {
262-
scheduleViewModel.showAllEvents()
263-
}
264-
}
265247
}
266248
analyticsHelper.sendScreenView("Schedule", requireActivity())
267249
}
@@ -344,12 +326,8 @@ class ScheduleFragment : MainNavigationFragment() {
344326
scheduleViewModel.userHasInteracted = true
345327
}
346328

347-
private fun openSessionDetail(id: SessionId) {
348-
findNavController().navigate(toSessionDetail(id))
349-
}
350-
351329
private fun openSearch() {
352-
findNavController().navigate(toSearch())
330+
findNavController().navigate(ScheduleFragmentDirections.toSearch())
353331
}
354332

355333
private fun openSignInDialog() {

mobile/src/main/java/com/google/samples/apps/iosched/ui/schedule/ScheduleNavigationAction.kt

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,8 @@
1616

1717
package com.google.samples.apps.iosched.ui.schedule
1818

19-
import com.google.samples.apps.iosched.model.SessionId
20-
2119
sealed class ScheduleNavigationAction {
2220
object NavigateToSignInDialogAction : ScheduleNavigationAction()
2321
object NavigateToSignOutDialogAction : ScheduleNavigationAction()
24-
class NavigateToSession(val sessionId: SessionId) : ScheduleNavigationAction()
2522
object ShowScheduleUiHints : ScheduleNavigationAction()
2623
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*
2+
* Copyright 2021 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.samples.apps.iosched.ui.schedule
18+
19+
import android.os.Bundle
20+
import android.view.LayoutInflater
21+
import android.view.View
22+
import android.view.ViewGroup
23+
import androidx.fragment.app.activityViewModels
24+
import androidx.navigation.NavController
25+
import androidx.navigation.fragment.NavHostFragment
26+
import com.google.samples.apps.iosched.R
27+
import com.google.samples.apps.iosched.ScheduleDetailNavGraphDirections
28+
import com.google.samples.apps.iosched.databinding.FragmentScheduleTwoPaneBinding
29+
import com.google.samples.apps.iosched.shared.result.EventObserver
30+
import com.google.samples.apps.iosched.ui.MainNavigationFragment
31+
import com.google.samples.apps.iosched.ui.messages.SnackbarMessageManager
32+
import com.google.samples.apps.iosched.ui.messages.setupSnackbarManager
33+
import com.google.samples.apps.iosched.ui.signin.SignInDialogFragment
34+
import dagger.hilt.android.AndroidEntryPoint
35+
import javax.inject.Inject
36+
37+
@AndroidEntryPoint
38+
class ScheduleTwoPaneFragment : MainNavigationFragment() {
39+
40+
@Inject
41+
lateinit var snackbarMessageManager: SnackbarMessageManager
42+
43+
private val scheduleTwoPaneViewModel: ScheduleTwoPaneViewModel by activityViewModels()
44+
45+
private lateinit var binding: FragmentScheduleTwoPaneBinding
46+
47+
private lateinit var listPaneNavController: NavController
48+
private lateinit var detailPaneNavController: NavController
49+
50+
override fun onCreateView(
51+
inflater: LayoutInflater,
52+
container: ViewGroup?,
53+
savedInstanceState: Bundle?
54+
): View {
55+
binding = FragmentScheduleTwoPaneBinding.inflate(inflater, container, false)
56+
return binding.root
57+
}
58+
59+
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
60+
super.onViewCreated(view, savedInstanceState)
61+
62+
setupSnackbarManager(snackbarMessageManager, binding.snackbar)
63+
64+
childFragmentManager.run {
65+
listPaneNavController =
66+
(findFragmentById(R.id.list_pane) as NavHostFragment).navController
67+
detailPaneNavController =
68+
(findFragmentById(R.id.detail_pane) as NavHostFragment).navController
69+
}
70+
71+
scheduleTwoPaneViewModel.navigateToSessionAction.observe(
72+
viewLifecycleOwner,
73+
EventObserver { sessionId ->
74+
detailPaneNavController.navigate(
75+
ScheduleDetailNavGraphDirections.toSessionDetail(sessionId)
76+
)
77+
}
78+
)
79+
80+
scheduleTwoPaneViewModel.navigateToSignInDialogAction.observe(
81+
viewLifecycleOwner,
82+
EventObserver {
83+
openSignInDialog()
84+
}
85+
)
86+
}
87+
88+
// TODO convert this to a dialog destination in the nav graph
89+
private fun openSignInDialog() {
90+
val dialog = SignInDialogFragment()
91+
dialog.show(requireActivity().supportFragmentManager, SignInDialogFragment.DIALOG_SIGN_IN)
92+
}
93+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright 2021 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.samples.apps.iosched.ui.schedule
18+
19+
import androidx.lifecycle.ViewModel
20+
import com.google.samples.apps.iosched.ui.sessioncommon.EventActionsViewModelDelegate
21+
import dagger.hilt.android.lifecycle.HiltViewModel
22+
import javax.inject.Inject
23+
24+
// Note: clients should obtain this from the Activity.
25+
@HiltViewModel
26+
class ScheduleTwoPaneViewModel @Inject constructor(
27+
eventActionsViewModelDelegate: EventActionsViewModelDelegate
28+
) : ViewModel(), EventActionsViewModelDelegate by eventActionsViewModelDelegate

0 commit comments

Comments
 (0)