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

Commit 27e5e0f

Browse files
jdkorenGerrit Code Review
authored andcommitted
Merge "Make ViewModels submit Snackbar messages directly" into main
2 parents 1270570 + a5ae7be commit 27e5e0f

22 files changed

+209
-287
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import com.google.samples.apps.iosched.shared.di.ExploreArEnabledFlag
4040
import com.google.samples.apps.iosched.shared.di.MapFeatureEnabledFlag
4141
import com.google.samples.apps.iosched.shared.domain.ar.ArConstants
4242
import com.google.samples.apps.iosched.shared.result.EventObserver
43+
import com.google.samples.apps.iosched.ui.messages.SnackbarMessage
4344
import com.google.samples.apps.iosched.ui.messages.SnackbarMessageManager
4445
import com.google.samples.apps.iosched.ui.signin.SignInDialogFragment
4546
import com.google.samples.apps.iosched.ui.signin.SignOutDialogFragment

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

Lines changed: 0 additions & 116 deletions
This file was deleted.

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

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ import com.google.samples.apps.iosched.ui.feed.FeedNavigationAction.NavigateToSe
4242
import com.google.samples.apps.iosched.ui.feed.FeedNavigationAction.OpenLiveStreamAction
4343
import com.google.samples.apps.iosched.ui.feed.FeedNavigationAction.OpenSignInDialogAction
4444
import com.google.samples.apps.iosched.ui.messages.SnackbarMessageManager
45-
import com.google.samples.apps.iosched.ui.setUpSnackbar
45+
import com.google.samples.apps.iosched.ui.messages.setupSnackbarManager
4646
import com.google.samples.apps.iosched.ui.signin.SignInDialogFragment
4747
import com.google.samples.apps.iosched.ui.signin.setupProfileMenuItem
4848
import com.google.samples.apps.iosched.util.doOnApplyWindowInsets
@@ -76,8 +76,7 @@ class FeedFragment : MainNavigationFragment() {
7676
inflater: LayoutInflater,
7777
container: ViewGroup?,
7878
savedInstanceState: Bundle?
79-
): View? {
80-
79+
): View {
8180
binding = FragmentFeedBinding.inflate(
8281
inflater, container, false
8382
).apply {
@@ -131,7 +130,7 @@ class FeedFragment : MainNavigationFragment() {
131130
v.updatePaddingRelative(bottom = padding.bottom + insets.systemWindowInsetBottom)
132131
}
133132

134-
setUpSnackbar(model.snackBarMessages, binding.snackbar, snackbarMessageManager)
133+
setupSnackbarManager(snackbarMessageManager, binding.snackbar)
135134

136135
// Observe feed
137136
viewLifecycleOwner.lifecycleScope.launchWhenStarted {

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

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -40,21 +40,20 @@ import com.google.samples.apps.iosched.shared.domain.feed.LoadCurrentMomentUseCa
4040
import com.google.samples.apps.iosched.shared.domain.sessions.LoadStarredAndReservedSessionsUseCase
4141
import com.google.samples.apps.iosched.shared.domain.settings.GetTimeZoneUseCase
4242
import com.google.samples.apps.iosched.shared.result.Result
43-
import com.google.samples.apps.iosched.shared.result.Result.Loading
4443
import com.google.samples.apps.iosched.shared.result.successOr
4544
import com.google.samples.apps.iosched.shared.time.TimeProvider
4645
import com.google.samples.apps.iosched.shared.util.TimeUtils
4746
import com.google.samples.apps.iosched.shared.util.TimeUtils.ConferenceDays
4847
import com.google.samples.apps.iosched.shared.util.toEpochMilli
4948
import com.google.samples.apps.iosched.shared.util.tryOffer
50-
import com.google.samples.apps.iosched.ui.SnackbarMessage
49+
import com.google.samples.apps.iosched.ui.messages.SnackbarMessage
50+
import com.google.samples.apps.iosched.ui.messages.SnackbarMessageManager
5151
import com.google.samples.apps.iosched.ui.sessioncommon.EventActions
5252
import com.google.samples.apps.iosched.ui.sessiondetail.SessionDetailFragmentDirections
5353
import com.google.samples.apps.iosched.ui.signin.SignInViewModelDelegate
5454
import com.google.samples.apps.iosched.ui.theme.ThemedActivityDelegate
5555
import com.google.samples.apps.iosched.util.WhileViewSubscribed
5656
import dagger.hilt.android.lifecycle.HiltViewModel
57-
import kotlinx.coroutines.channels.BufferOverflow.DROP_LATEST
5857
import kotlinx.coroutines.channels.Channel
5958
import kotlinx.coroutines.flow.Flow
6059
import kotlinx.coroutines.flow.StateFlow
@@ -64,7 +63,6 @@ import kotlinx.coroutines.flow.flow
6463
import kotlinx.coroutines.flow.map
6564
import kotlinx.coroutines.flow.onEach
6665
import kotlinx.coroutines.flow.receiveAsFlow
67-
import kotlinx.coroutines.flow.shareIn
6866
import kotlinx.coroutines.flow.stateIn
6967
import org.threeten.bp.ZoneId
7068
import org.threeten.bp.ZonedDateTime
@@ -85,7 +83,8 @@ class FeedViewModel @Inject constructor(
8583
private val timeProvider: TimeProvider,
8684
private val analyticsHelper: AnalyticsHelper,
8785
private val signInViewModelDelegate: SignInViewModelDelegate,
88-
themedActivityDelegate: ThemedActivityDelegate
86+
themedActivityDelegate: ThemedActivityDelegate,
87+
private val snackbarMessageManager: SnackbarMessageManager
8988
) : ViewModel(),
9089
FeedEventListener,
9190
ThemedActivityDelegate by themedActivityDelegate,
@@ -140,7 +139,7 @@ class FeedViewModel @Inject constructor(
140139
private val conferenceState: StateFlow<ConferenceState> = getConferenceStateUseCase(Unit)
141140
.onEach {
142141
if (it is Result.Error) {
143-
_snackBarMessages.tryOffer(SnackbarMessage(R.string.feed_loading_error))
142+
snackbarMessageManager.addMessage(SnackbarMessage(R.string.feed_loading_error))
144143
}
145144
}
146145
.map { it.successOr(UPCOMING) }
@@ -160,15 +159,15 @@ class FeedViewModel @Inject constructor(
160159
emit(loadAnnouncementsUseCase(timeProvider.now()))
161160
}.onEach {
162161
if (it is Result.Error) {
163-
_snackBarMessages.tryOffer(SnackbarMessage(R.string.feed_loading_error))
162+
snackbarMessageManager.addMessage(SnackbarMessage(R.string.feed_loading_error))
164163
}
165164
}.stateIn(viewModelScope, WhileViewSubscribed, Result.Loading)
166165

167166
private val announcementsPreview: StateFlow<List<Any>> = loadAnnouncementsResult.map {
168167
val announcementsHeader = AnnouncementsHeader(
169168
showPastNotificationsButton = it.successOr(emptyList()).size > 1
170169
)
171-
if (it is Loading) {
170+
if (it is Result.Loading) {
172171
listOf(announcementsHeader, LoadingIndicator)
173172
} else {
174173
listOf(
@@ -178,12 +177,6 @@ class FeedViewModel @Inject constructor(
178177
}
179178
}.stateIn(viewModelScope, WhileViewSubscribed, emptyList())
180179

181-
// SIDE EFFECTS: Snackbar messages
182-
// Guard against too many Snackbar messages by limiting to 3, keeping the oldest.
183-
private val _snackBarMessages = Channel<SnackbarMessage>(3, DROP_LATEST)
184-
val snackBarMessages: Flow<SnackbarMessage> =
185-
_snackBarMessages.receiveAsFlow().shareIn(viewModelScope, WhileViewSubscribed)
186-
187180
private val feedSessionsContainer: Flow<FeedSessions> = loadSessionsResult
188181
.combine(timeZoneIdFlow) { sessions, timeZone ->
189182
createFeedSessionsContainer(sessions, timeZone)
@@ -196,7 +189,8 @@ class FeedViewModel @Inject constructor(
196189
signInViewModelDelegate.currentUserInfoFlow
197190
) { sessionsContainer: FeedSessions,
198191
conferenceState: ConferenceState,
199-
userInfo: AuthenticatedUserInfo? ->
192+
userInfo: AuthenticatedUserInfo?
193+
->
200194
val isSignedIn = userInfo?.isSignedIn() ?: false
201195
val isRegistered = userInfo?.isRegistered() ?: false
202196
if (conferenceState != ENDED && isSignedIn && isRegistered &&
@@ -268,7 +262,7 @@ class FeedViewModel @Inject constructor(
268262
actionTextId = actionId,
269263
userSessions = upcomingReservedSessions,
270264
timeZoneId = timeZoneId,
271-
isLoading = sessionsResult is Loading,
265+
isLoading = sessionsResult is Result.Loading,
272266
isMapFeatureEnabled = isMapEnabledByRemoteConfig
273267
)
274268
}

mobile/src/main/java/com/google/samples/apps/iosched/ui/info/EventFragment.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ import com.google.samples.apps.iosched.model.ConferenceWifiInfo
3535
import com.google.samples.apps.iosched.shared.di.AssistantAppEnabledFlag
3636
import com.google.samples.apps.iosched.shared.util.TimeUtils
3737
import com.google.samples.apps.iosched.ui.messages.SnackbarMessageManager
38-
import com.google.samples.apps.iosched.ui.setupSnackbarManager
38+
import com.google.samples.apps.iosched.ui.messages.setupSnackbarManager
3939
import com.google.samples.apps.iosched.util.doOnApplyWindowInsets
4040
import com.google.samples.apps.iosched.util.launchAndRepeatWithViewLifecycle
4141
import com.google.samples.apps.iosched.widget.FadingSnackbar
@@ -74,7 +74,7 @@ class EventFragment : Fragment() {
7474
}
7575

7676
val snackbarLayout = requireActivity().findViewById<FadingSnackbar>(R.id.snackbar)
77-
setupSnackbarManager(snackbarMessageManager, snackbarLayout) { }
77+
setupSnackbarManager(snackbarMessageManager, snackbarLayout)
7878

7979
return binding.root
8080
}

mobile/src/main/java/com/google/samples/apps/iosched/ui/info/EventInfoViewModel.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import com.google.samples.apps.iosched.shared.analytics.AnalyticsHelper
2626
import com.google.samples.apps.iosched.shared.domain.logistics.LoadWifiInfoUseCase
2727
import com.google.samples.apps.iosched.shared.result.data
2828
import com.google.samples.apps.iosched.shared.util.tryOffer
29-
import com.google.samples.apps.iosched.ui.SnackbarMessage
29+
import com.google.samples.apps.iosched.ui.messages.SnackbarMessage
3030
import com.google.samples.apps.iosched.ui.messages.SnackbarMessageManager
3131
import com.google.samples.apps.iosched.util.WhileViewSubscribed
3232
import com.google.samples.apps.iosched.util.wifi.WifiInstaller

mobile/src/main/java/com/google/samples/apps/iosched/ui/SnackbarMessage.kt renamed to mobile/src/main/java/com/google/samples/apps/iosched/ui/messages/SnackbarMessage.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
package com.google.samples.apps.iosched.ui
17+
package com.google.samples.apps.iosched.ui.messages
1818

1919
import com.google.samples.apps.iosched.model.Session
2020

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Copyright 2018 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.messages
18+
19+
import androidx.core.text.HtmlCompat
20+
import androidx.core.text.HtmlCompat.FROM_HTML_MODE_LEGACY
21+
import androidx.fragment.app.Fragment
22+
import androidx.lifecycle.lifecycleScope
23+
import com.google.samples.apps.iosched.widget.FadingSnackbar
24+
import kotlinx.coroutines.flow.collect
25+
26+
/**
27+
* An extension for Fragments that sets up a Snackbar with a [SnackbarMessageManager].
28+
*/
29+
fun Fragment.setupSnackbarManager(
30+
snackbarMessageManager: SnackbarMessageManager,
31+
fadingSnackbar: FadingSnackbar
32+
) {
33+
viewLifecycleOwner.lifecycleScope.launchWhenStarted {
34+
snackbarMessageManager.currentSnackbar.collect { message ->
35+
if (message == null) { return@collect }
36+
val messageText = HtmlCompat.fromHtml(
37+
requireContext().getString(message.messageId, message.session?.title),
38+
FROM_HTML_MODE_LEGACY
39+
)
40+
fadingSnackbar.show(
41+
messageText = messageText,
42+
actionId = message.actionId,
43+
longDuration = message.longDuration,
44+
actionClick = {
45+
snackbarMessageManager.processDismissedMessage(message)
46+
fadingSnackbar.dismiss()
47+
},
48+
// When the snackbar is dismissed, ping the snackbar message manager in case there
49+
// are pending messages.
50+
dismissListener = {
51+
snackbarMessageManager.removeMessageAndLoadNext(message)
52+
}
53+
)
54+
}
55+
}
56+
}

mobile/src/main/java/com/google/samples/apps/iosched/ui/messages/SnackbarMessageManager.kt

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,13 @@ package com.google.samples.apps.iosched.ui.messages
1919
import androidx.annotation.VisibleForTesting
2020
import com.google.samples.apps.iosched.R
2121
import com.google.samples.apps.iosched.shared.data.prefs.PreferenceStorage
22-
import com.google.samples.apps.iosched.ui.SnackbarMessage
22+
import com.google.samples.apps.iosched.shared.di.ApplicationScope
23+
import com.google.samples.apps.iosched.shared.domain.prefs.StopSnackbarActionUseCase
2324
import com.google.samples.apps.iosched.ui.messages.SnackbarMessageManager.Companion.MAX_ITEMS
25+
import kotlinx.coroutines.CoroutineScope
2426
import kotlinx.coroutines.flow.MutableStateFlow
2527
import kotlinx.coroutines.flow.StateFlow
28+
import kotlinx.coroutines.launch
2629
import timber.log.Timber
2730
import javax.inject.Inject
2831
import javax.inject.Singleton
@@ -40,7 +43,9 @@ import javax.inject.Singleton
4043
*/
4144
@Singleton
4245
open class SnackbarMessageManager @Inject constructor(
43-
private val preferenceStorage: PreferenceStorage
46+
private val preferenceStorage: PreferenceStorage,
47+
@ApplicationScope private val coroutineScope: CoroutineScope,
48+
private val stopSnackbarActionUseCase: StopSnackbarActionUseCase
4449
) {
4550
companion object {
4651
// Keep a fixed number of old items
@@ -86,6 +91,14 @@ open class SnackbarMessageManager @Inject constructor(
8691
loadNext()
8792
}
8893

94+
fun processDismissedMessage(message: SnackbarMessage) {
95+
if (message.actionId == R.string.dont_show) {
96+
coroutineScope.launch {
97+
stopSnackbarActionUseCase(true)
98+
}
99+
}
100+
}
101+
89102
private fun shouldSnackbarBeIgnored(msg: SnackbarMessage): Boolean {
90103
// TODO: This should call a suspend fun (migrate to datastore)
91104
return preferenceStorage.observableSnackbarIsStopped.value == true &&

0 commit comments

Comments
 (0)