Skip to content

Commit 197edfd

Browse files
committed
fix: refresh subscriptions seamlessly
1 parent 5bb8841 commit 197edfd

7 files changed

Lines changed: 51 additions & 51 deletions

File tree

‎app/src/main/java/dev/typetype/android/feature/subscriptions/SubscriptionsFeedStatusBar.kt‎

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import dev.typetype.android.core.ui.components.RequestIdRow
2424
internal fun SubscriptionsFeedStatusBar(
2525
isRefreshing: Boolean,
2626
isServerRefreshing: Boolean,
27-
hasPendingRefresh: Boolean,
2827
errorMessage: String?,
2928
requestId: String?,
3029
hasContent: Boolean,
@@ -33,7 +32,6 @@ internal fun SubscriptionsFeedStatusBar(
3332
when {
3433
errorMessage != null && hasContent -> FailureStatus(errorMessage, requestId, onRetry)
3534
isRefreshing && hasContent -> ProgressStatus(R.string.subscriptions_checking_updates)
36-
hasPendingRefresh && hasContent -> RefreshAvailableStatus(onRetry)
3735
isServerRefreshing && hasContent -> ProgressStatus(R.string.subscriptions_rebuilding)
3836
}
3937
}
@@ -55,29 +53,6 @@ private fun ProgressStatus(label: Int) {
5553
}
5654
}
5755

58-
@Composable
59-
private fun RefreshAvailableStatus(onRefresh: () -> Unit) {
60-
Surface(
61-
color = MaterialTheme.colorScheme.secondaryContainer,
62-
contentColor = MaterialTheme.colorScheme.onSecondaryContainer,
63-
modifier = Modifier.fillMaxWidth().semantics { liveRegion = LiveRegionMode.Polite },
64-
) {
65-
Row(
66-
modifier = Modifier.fillMaxWidth().padding(start = 16.dp, end = 8.dp, top = 6.dp, bottom = 6.dp),
67-
verticalAlignment = Alignment.CenterVertically,
68-
) {
69-
Text(
70-
text = stringResource(R.string.subscriptions_update_available),
71-
style = MaterialTheme.typography.bodyMedium,
72-
modifier = Modifier.weight(1f),
73-
)
74-
TextButton(onClick = onRefresh) {
75-
Text(stringResource(R.string.subscriptions_refresh))
76-
}
77-
}
78-
}
79-
}
80-
8156
@Composable
8257
private fun FailureStatus(message: String, requestId: String?, onRetry: () -> Unit) {
8358
Surface(
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package dev.typetype.android.feature.subscriptions
2+
3+
internal enum class SubscriptionsGenerationAction { Continue, Replace, Stop }
4+
5+
internal fun subscriptionsGenerationAction(
6+
currentGeneration: Long?,
7+
observedGeneration: Long,
8+
receivedGeneration: Long,
9+
): SubscriptionsGenerationAction = when {
10+
currentGeneration != observedGeneration -> SubscriptionsGenerationAction.Stop
11+
receivedGeneration != observedGeneration -> SubscriptionsGenerationAction.Replace
12+
else -> SubscriptionsGenerationAction.Continue
13+
}

‎app/src/main/java/dev/typetype/android/feature/subscriptions/SubscriptionsScreen.kt‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,6 @@ fun SubscriptionsScreen(
7878
SubscriptionsFeedStatusBar(
7979
isRefreshing = state.isLoading,
8080
isServerRefreshing = state.isServerRefreshing,
81-
hasPendingRefresh = state.hasPendingRefresh,
8281
errorMessage = state.errorMessage,
8382
requestId = state.errorRequestId,
8483
hasContent = state.videos.isNotEmpty(),

‎app/src/main/java/dev/typetype/android/feature/subscriptions/SubscriptionsState.kt‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ data class SubscriptionsState(
99
val isLoading: Boolean = false,
1010
val isLoadingMore: Boolean = false,
1111
val isServerRefreshing: Boolean = false,
12-
val hasPendingRefresh: Boolean = false,
1312
val videos: List<Video> = emptyList(),
1413
val channels: List<SubscriptionSummary> = emptyList(),
1514
val selectedTab: SubscriptionsTab = SubscriptionsTab.Videos,

‎app/src/main/java/dev/typetype/android/feature/subscriptions/SubscriptionsViewModel.kt‎

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,6 @@ class SubscriptionsViewModel @Inject constructor(
136136
isLoading = true,
137137
isLoadingMore = false,
138138
isServerRefreshing = false,
139-
hasPendingRefresh = false,
140139
errorMessage = null,
141140
errorRequestId = null,
142141
loadMoreError = false,
@@ -161,7 +160,6 @@ class SubscriptionsViewModel @Inject constructor(
161160
videos = page.videos.distinctBy { video -> video.url },
162161
hasMore = page.hasMore,
163162
isServerRefreshing = page.refreshing,
164-
hasPendingRefresh = false,
165163
generatedAtMillis = page.generatedAtMillis,
166164
errorMessage = null,
167165
errorRequestId = null,
@@ -197,7 +195,7 @@ class SubscriptionsViewModel @Inject constructor(
197195
onFailure = { failure ->
198196
if (failure.requiresSubscriptionsPaginationRestart()) {
199197
refreshMonitorJob?.cancel()
200-
offerRefresh()
198+
requestJob = viewModelScope.launch { loadFirstPage() }
201199
} else {
202200
networkRecovery.schedule(failure, SubscriptionsRecoveryRequest.Pagination)
203201
val details = errorMapper.details(failure, R.string.subscriptions_failed)
@@ -251,12 +249,13 @@ class SubscriptionsViewModel @Inject constructor(
251249
showRefreshFailure(failure)
252250
return@launch
253251
}
254-
if (generation != expectedGeneration) return@launch
255-
if (page.generation != expectedGeneration) {
256-
offerRefresh()
257-
return@launch
258-
} else {
259-
_state.update {
252+
when (subscriptionsGenerationAction(generation, expectedGeneration, page.generation)) {
253+
SubscriptionsGenerationAction.Stop -> return@launch
254+
SubscriptionsGenerationAction.Replace -> {
255+
viewModelScope.launch { acceptFirstPage(page, hadCachedContent = true) }
256+
return@launch
257+
}
258+
SubscriptionsGenerationAction.Continue -> _state.update {
260259
it.copy(
261260
isServerRefreshing = page.refreshing,
262261
generatedAtMillis = page.generatedAtMillis,
@@ -268,19 +267,6 @@ class SubscriptionsViewModel @Inject constructor(
268267
}
269268
}
270269

271-
private fun offerRefresh() {
272-
nextCursor = null
273-
_state.update {
274-
it.copy(
275-
isLoadingMore = false,
276-
isServerRefreshing = false,
277-
hasPendingRefresh = true,
278-
hasMore = false,
279-
loadMoreError = false,
280-
)
281-
}
282-
}
283-
284270
private suspend fun persistPage(page: SubscriptionsPage, append: Boolean) {
285271
try {
286272
videoMetaRepository.cacheVideos(page.videos)

‎app/src/main/res/values/strings.xml‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,6 @@
2626
<string name="subscriptions_failed">Couldn\'t load subscriptions</string>
2727
<string name="subscriptions_checking_updates">Checking for new videos</string>
2828
<string name="subscriptions_rebuilding">Updating subscriptions in the background</string>
29-
<string name="subscriptions_update_available">New subscription videos are available</string>
30-
<string name="subscriptions_refresh">Refresh</string>
3129
<string name="subscriptions_preparing">Preparing subscriptions</string>
3230
<string name="subscriptions_empty_title">Your subscriptions will appear here</string>
3331
<string name="subscriptions_empty_description">Subscribe to channels to build a personal feed of their latest videos.</string>
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package dev.typetype.android.feature.subscriptions
2+
3+
import org.junit.Assert.assertEquals
4+
import org.junit.Test
5+
6+
class SubscriptionsGenerationActionTest {
7+
@Test
8+
fun `new server generation replaces the visible feed`() {
9+
assertEquals(
10+
SubscriptionsGenerationAction.Replace,
11+
subscriptionsGenerationAction(4L, 4L, 5L),
12+
)
13+
}
14+
15+
@Test
16+
fun `matching generation keeps monitoring`() {
17+
assertEquals(
18+
SubscriptionsGenerationAction.Continue,
19+
subscriptionsGenerationAction(4L, 4L, 4L),
20+
)
21+
}
22+
23+
@Test
24+
fun `stale monitor cannot replace a newer feed`() {
25+
assertEquals(
26+
SubscriptionsGenerationAction.Stop,
27+
subscriptionsGenerationAction(5L, 4L, 6L),
28+
)
29+
}
30+
}

0 commit comments

Comments
 (0)