Add a menu option to load the newest notifications (#3708)

- Create a flow with new items (arbitrary ints) when a reload from the top should happen
- Combine this flow with notificationFilter, so changes to either of them trigger a reload
- Provide a menu item in NotificationsFragment to initiate the reload
- Handle the action in the view model
This commit is contained in:
Nik Clayton 2023-06-11 20:04:49 +02:00 committed by GitHub
parent 4cddd2c5e6
commit 5755801e6f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 36 additions and 3 deletions

View File

@ -454,6 +454,10 @@ class NotificationsFragment :
onRefresh()
true
}
R.id.load_newest -> {
viewModel.accept(InfallibleUiAction.LoadNewest)
true
}
else -> false
}
}

View File

@ -59,6 +59,7 @@ import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.getAndUpdate
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
@ -123,6 +124,12 @@ sealed class InfallibleUiAction : UiAction() {
* can do.
*/
data class SaveVisibleId(val visibleId: String) : InfallibleUiAction()
/** Ignore the saved reading position, load the page with the newest items */
// Resets the account's `lastNotificationId`, which can't fail, which is why this is
// infallible. Reloading the data may fail, but that's handled by the paging system /
// adapter refresh logic.
object LoadNewest : InfallibleUiAction()
}
/** Actions the user can trigger on an individual notification. These may fail. */
@ -300,6 +307,9 @@ class NotificationsViewModel @Inject constructor(
/** Flow of user actions received from the UI */
private val uiAction = MutableSharedFlow<UiAction>()
/** Flow that can be used to trigger a full reload */
private val reload = MutableStateFlow(0)
/** Flow of successful action results */
// Note: This is a SharedFlow instead of a StateFlow because success state does not need to be
// retained. A message is shown once to a user and then dismissed. Re-collecting the flow
@ -342,6 +352,18 @@ class NotificationsViewModel @Inject constructor(
)
}
// Reset the last notification ID to "0" to fetch the newest notifications, and
// increment `reload` to trigger creation of a new PagingSource.
viewModelScope.launch {
uiAction
.filterIsInstance<InfallibleUiAction.LoadNewest>()
.collectLatest {
account.lastNotificationId = "0"
accountManager.saveAccount(account)
reload.getAndUpdate { it + 1 }
}
}
// Save the visible notification ID
viewModelScope.launch {
uiAction
@ -465,11 +487,12 @@ class NotificationsViewModel @Inject constructor(
}
}
pagingData = notificationFilter
// Re-fetch notifications if either of `notificationFilter` or `reload` flows have
// new items.
pagingData = combine(notificationFilter, reload) { action, _ -> action }
.flatMapLatest { action ->
getNotifications(filters = action.filter, initialKey = getInitialKey())
}
.cachedIn(viewModelScope)
}.cachedIn(viewModelScope)
uiState = combine(notificationFilter, getUiPrefs()) { filter, prefs ->
UiState(

View File

@ -22,4 +22,9 @@
android:id="@+id/action_refresh"
android:title="@string/action_refresh"
app:showAsAction="never" />
<item
android:id="@+id/load_newest"
android:title="@string/load_newest_notifications"
app:showAsAction="never" />
</menu>

View File

@ -810,4 +810,5 @@
you follow.\n\nTo explore accounts you can either discover them in one of the other timelines.
For example the local timeline of your instance [iconics gmd_group]. Or you can search them
by name [iconics gmd_search]; for example search for Tusky to find our Mastodon account.</string>
<string name="load_newest_notifications">Load newest notifications</string>
</resources>