feat: Allow the user to chose behaviour when tapping a tab (#955)
Previously, tapping a tab would jump to the top of the loaded content, which might trigger a load of a fresh page. Provide a preference to control this; the default is the current behaviour, the user can also choose to discard the current content and load the newest content. Fixes #939
This commit is contained in:
parent
90537da122
commit
97558667c8
|
@ -61,6 +61,7 @@ import app.pachli.core.network.model.Filter
|
|||
import app.pachli.core.network.model.Notification
|
||||
import app.pachli.core.network.model.Poll
|
||||
import app.pachli.core.network.model.Status
|
||||
import app.pachli.core.preferences.TabTapBehaviour
|
||||
import app.pachli.core.ui.ActionButtonScrollListener
|
||||
import app.pachli.core.ui.BackgroundMessage
|
||||
import app.pachli.core.ui.extensions.getErrorString
|
||||
|
@ -671,7 +672,10 @@ class NotificationsFragment :
|
|||
|
||||
override fun onReselect() {
|
||||
if (isAdded) {
|
||||
layoutManager.scrollToPosition(0)
|
||||
when (viewModel.uiState.value.tabTapBehaviour) {
|
||||
TabTapBehaviour.JUMP_TO_NEXT_PAGE -> layoutManager.scrollToPosition(0)
|
||||
TabTapBehaviour.JUMP_TO_NEWEST -> viewModel.accept(InfallibleUiAction.LoadNewest)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -41,6 +41,7 @@ import app.pachli.core.network.model.Notification
|
|||
import app.pachli.core.network.model.Poll
|
||||
import app.pachli.core.preferences.PrefKeys
|
||||
import app.pachli.core.preferences.SharedPreferencesRepository
|
||||
import app.pachli.core.preferences.TabTapBehaviour
|
||||
import app.pachli.network.FilterModel
|
||||
import app.pachli.usecase.TimelineCases
|
||||
import app.pachli.util.deserialize
|
||||
|
@ -82,6 +83,9 @@ data class UiState(
|
|||
|
||||
/** True if the FAB should be shown while scrolling */
|
||||
val showFabWhileScrolling: Boolean = true,
|
||||
|
||||
/** User's preference for behaviour when tapping a tab. */
|
||||
val tabTapBehaviour: TabTapBehaviour = TabTapBehaviour.JUMP_TO_NEXT_PAGE,
|
||||
)
|
||||
|
||||
/** Preferences the UI reacts to */
|
||||
|
@ -92,6 +96,7 @@ data class UiPrefs(
|
|||
/** Relevant preference keys. Changes to any of these trigger a display update */
|
||||
val prefKeys = setOf(
|
||||
PrefKeys.FAB_HIDE,
|
||||
PrefKeys.TAB_TAP_BEHAVIOUR,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -384,6 +389,7 @@ class NotificationsViewModel @Inject constructor(
|
|||
account.lastNotificationId = "0"
|
||||
accountManager.saveAccount(account)
|
||||
reload.getAndUpdate { it + 1 }
|
||||
repository.invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -506,10 +512,11 @@ class NotificationsViewModel @Inject constructor(
|
|||
getNotifications(filters = action.filter, initialKey = getInitialKey())
|
||||
}.cachedIn(viewModelScope)
|
||||
|
||||
uiState = combine(notificationFilter, getUiPrefs()) { filter, prefs ->
|
||||
uiState = combine(notificationFilter, getUiPrefs()) { filter, _ ->
|
||||
UiState(
|
||||
activeFilter = filter.filter,
|
||||
showFabWhileScrolling = prefs.showFabWhileScrolling,
|
||||
showFabWhileScrolling = !sharedPreferencesRepository.getBoolean(PrefKeys.FAB_HIDE, false),
|
||||
tabTapBehaviour = sharedPreferencesRepository.tabTapBehaviour,
|
||||
)
|
||||
}.stateIn(
|
||||
scope = viewModelScope,
|
||||
|
@ -557,10 +564,5 @@ class NotificationsViewModel @Inject constructor(
|
|||
*/
|
||||
private fun getUiPrefs() = sharedPreferencesRepository.changes
|
||||
.filter { UiPrefs.prefKeys.contains(it) }
|
||||
.map { toPrefs() }
|
||||
.onStart { emit(toPrefs()) }
|
||||
|
||||
private fun toPrefs() = UiPrefs(
|
||||
showFabWhileScrolling = !sharedPreferencesRepository.getBoolean(PrefKeys.FAB_HIDE, false),
|
||||
)
|
||||
.onStart { emit(null) }
|
||||
}
|
||||
|
|
|
@ -54,6 +54,7 @@ import app.pachli.core.preferences.AppTheme
|
|||
import app.pachli.core.preferences.DownloadLocation
|
||||
import app.pachli.core.preferences.PrefKeys
|
||||
import app.pachli.core.preferences.SharedPreferencesRepository
|
||||
import app.pachli.core.preferences.TabTapBehaviour
|
||||
import app.pachli.core.ui.extensions.await
|
||||
import app.pachli.core.ui.makeIcon
|
||||
import app.pachli.databinding.AccountNotificationDetailsListItemBinding
|
||||
|
@ -195,15 +196,6 @@ class PreferencesFragment : PreferenceFragmentCompat() {
|
|||
icon = makeIcon(GoogleMaterial.Icon.gmd_format_size)
|
||||
}
|
||||
|
||||
listPreference {
|
||||
setDefaultValue("top")
|
||||
setEntries(R.array.pref_main_nav_position_options)
|
||||
setEntryValues(R.array.pref_main_nav_position_values)
|
||||
key = PrefKeys.MAIN_NAV_POSITION
|
||||
setSummaryProvider { entry }
|
||||
setTitle(R.string.pref_main_nav_position)
|
||||
}
|
||||
|
||||
listPreference {
|
||||
setDefaultValue("disambiguate")
|
||||
setEntries(R.array.pref_show_self_username_names)
|
||||
|
@ -285,6 +277,24 @@ class PreferencesFragment : PreferenceFragmentCompat() {
|
|||
isSingleLineTitle = false
|
||||
}
|
||||
|
||||
switchPreference {
|
||||
setDefaultValue(false)
|
||||
key = PrefKeys.SHOW_STATS_INLINE
|
||||
setTitle(R.string.pref_title_show_stat_inline)
|
||||
isSingleLineTitle = false
|
||||
}
|
||||
}
|
||||
|
||||
preferenceCategory(app.pachli.core.preferences.R.string.pref_category_tabs) {
|
||||
listPreference {
|
||||
setDefaultValue("top")
|
||||
setEntries(R.array.pref_main_nav_position_options)
|
||||
setEntryValues(R.array.pref_main_nav_position_values)
|
||||
key = PrefKeys.MAIN_NAV_POSITION
|
||||
setSummaryProvider { entry }
|
||||
setTitle(R.string.pref_main_nav_position)
|
||||
}
|
||||
|
||||
switchPreference {
|
||||
setDefaultValue(true)
|
||||
key = PrefKeys.ENABLE_SWIPE_FOR_TABS
|
||||
|
@ -292,11 +302,10 @@ class PreferencesFragment : PreferenceFragmentCompat() {
|
|||
isSingleLineTitle = false
|
||||
}
|
||||
|
||||
switchPreference {
|
||||
setDefaultValue(false)
|
||||
key = PrefKeys.SHOW_STATS_INLINE
|
||||
setTitle(R.string.pref_title_show_stat_inline)
|
||||
isSingleLineTitle = false
|
||||
enumListPreference<TabTapBehaviour> {
|
||||
setDefaultValue(TabTapBehaviour.JUMP_TO_NEXT_PAGE)
|
||||
setTitle(app.pachli.core.preferences.R.string.pref_title_tab_tap)
|
||||
key = PrefKeys.TAB_TAP_BEHAVIOUR
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -67,6 +67,7 @@ import app.pachli.core.navigation.AttachmentViewData
|
|||
import app.pachli.core.navigation.EditFilterActivityIntent
|
||||
import app.pachli.core.network.model.Poll
|
||||
import app.pachli.core.network.model.Status
|
||||
import app.pachli.core.preferences.TabTapBehaviour
|
||||
import app.pachli.core.ui.ActionButtonScrollListener
|
||||
import app.pachli.core.ui.BackgroundMessage
|
||||
import app.pachli.core.ui.extensions.getErrorString
|
||||
|
@ -799,10 +800,16 @@ class TimelineFragment :
|
|||
|
||||
override fun onReselect() {
|
||||
if (isAdded) {
|
||||
when (viewModel.uiState.value.tabTapBehaviour) {
|
||||
TabTapBehaviour.JUMP_TO_NEXT_PAGE -> {
|
||||
binding.recyclerView.scrollToPosition(0)
|
||||
binding.recyclerView.stopScroll()
|
||||
saveVisibleId()
|
||||
}
|
||||
|
||||
TabTapBehaviour.JUMP_TO_NEWEST -> viewModel.accept(InfallibleUiAction.LoadNewest)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -53,6 +53,7 @@ import app.pachli.core.network.model.Poll
|
|||
import app.pachli.core.network.model.Status
|
||||
import app.pachli.core.preferences.PrefKeys
|
||||
import app.pachli.core.preferences.SharedPreferencesRepository
|
||||
import app.pachli.core.preferences.TabTapBehaviour
|
||||
import app.pachli.network.FilterModel
|
||||
import app.pachli.usecase.TimelineCases
|
||||
import app.pachli.viewdata.StatusViewData
|
||||
|
@ -85,6 +86,9 @@ data class UiState(
|
|||
|
||||
/** True if the timeline should be shown in reverse order (oldest first) */
|
||||
val reverseTimeline: Boolean,
|
||||
|
||||
/** User's preference for behaviour when tapping a tab. */
|
||||
val tabTapBehaviour: TabTapBehaviour = TabTapBehaviour.JUMP_TO_NEXT_PAGE,
|
||||
)
|
||||
|
||||
// TODO: Ui* classes are copied from NotificationsViewModel. Not yet sure whether these actions
|
||||
|
@ -406,13 +410,18 @@ abstract class TimelineViewModel(
|
|||
}
|
||||
}
|
||||
|
||||
val watchedPrefs = setOf(PrefKeys.FAB_HIDE, PrefKeys.LAB_REVERSE_TIMELINE)
|
||||
val watchedPrefs = setOf(
|
||||
PrefKeys.FAB_HIDE,
|
||||
PrefKeys.LAB_REVERSE_TIMELINE,
|
||||
PrefKeys.TAB_TAP_BEHAVIOUR,
|
||||
)
|
||||
uiState = sharedPreferencesRepository.changes
|
||||
.filter { watchedPrefs.contains(it) }
|
||||
.map {
|
||||
UiState(
|
||||
showFabWhileScrolling = !sharedPreferencesRepository.getBoolean(PrefKeys.FAB_HIDE, false),
|
||||
reverseTimeline = sharedPreferencesRepository.getBoolean(PrefKeys.LAB_REVERSE_TIMELINE, false),
|
||||
tabTapBehaviour = sharedPreferencesRepository.tabTapBehaviour,
|
||||
)
|
||||
}.stateIn(
|
||||
scope = viewModelScope,
|
||||
|
@ -420,6 +429,7 @@ abstract class TimelineViewModel(
|
|||
initialValue = UiState(
|
||||
showFabWhileScrolling = !sharedPreferencesRepository.getBoolean(PrefKeys.FAB_HIDE, false),
|
||||
reverseTimeline = sharedPreferencesRepository.getBoolean(PrefKeys.LAB_REVERSE_TIMELINE, false),
|
||||
tabTapBehaviour = sharedPreferencesRepository.tabTapBehaviour,
|
||||
),
|
||||
)
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ import androidx.core.content.edit
|
|||
import app.cash.turbine.test
|
||||
import app.pachli.core.network.model.Notification
|
||||
import app.pachli.core.preferences.PrefKeys
|
||||
import app.pachli.core.preferences.TabTapBehaviour
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
@ -36,6 +37,7 @@ class NotificationsViewModelTestUiState : NotificationsViewModelTestBase() {
|
|||
private val initialUiState = UiState(
|
||||
activeFilter = setOf(Notification.Type.FOLLOW),
|
||||
showFabWhileScrolling = true,
|
||||
tabTapBehaviour = TabTapBehaviour.JUMP_TO_NEXT_PAGE,
|
||||
)
|
||||
|
||||
@Test
|
||||
|
|
|
@ -110,6 +110,7 @@ object PrefKeys {
|
|||
const val USE_PREVIOUS_UNIFIED_PUSH_DISTRIBUTOR = "usePreviousUnifiedPushDistributor"
|
||||
|
||||
const val DOWNLOAD_LOCATION = "downloadLocation"
|
||||
const val TAB_TAP_BEHAVIOUR = "tabTapBehaviour"
|
||||
|
||||
/** Keys that are no longer used (e.g., the preference has been removed */
|
||||
object Deprecated {
|
||||
|
|
|
@ -53,6 +53,9 @@ class SharedPreferencesRepository @Inject constructor(
|
|||
val downloadLocation: DownloadLocation
|
||||
get() = getEnum(PrefKeys.DOWNLOAD_LOCATION, DownloadLocation.DOWNLOADS)
|
||||
|
||||
val tabTapBehaviour: TabTapBehaviour
|
||||
get() = getEnum(PrefKeys.TAB_TAP_BEHAVIOUR, TabTapBehaviour.JUMP_TO_NEXT_PAGE)
|
||||
|
||||
// Ensure the listener is retained during minification. If you do not do this the
|
||||
// field is removed and eventually garbage collected (because registering it as a
|
||||
// change listener does not create a strong reference to it) and then no more
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright 2024 Pachli Association
|
||||
*
|
||||
* This file is a part of Pachli.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Pachli is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Pachli; if not,
|
||||
* see <http://www.gnu.org/licenses>.
|
||||
*/
|
||||
|
||||
package app.pachli.core.preferences
|
||||
|
||||
/** Behaviour when the user taps on a tab. */
|
||||
enum class TabTapBehaviour(override val displayResource: Int, override val value: String? = null) :
|
||||
PreferenceEnum {
|
||||
/** Jump the user's position to the top, fetching the next page of content. */
|
||||
JUMP_TO_NEXT_PAGE(R.string.tab_tap_behaviour_jump_to_next_page),
|
||||
|
||||
/** Fetch the newest page of content and jump to that. */
|
||||
JUMP_TO_NEWEST(R.string.tab_tap_behaviour_jump_to_newest),
|
||||
}
|
|
@ -21,9 +21,15 @@
|
|||
<string name="download_location_downloads">Downloads folder</string>
|
||||
<string name="download_location_per_account">Per-account folders, in Downloads folder</string>
|
||||
<string name="download_location_per_sender">Per-sender folders, in Downloads folder</string>
|
||||
|
||||
<string name="app_theme_light">Light</string>
|
||||
<string name="app_theme_black">Black</string>
|
||||
<string name="app_theme_auto">Automatic at sunset</string>
|
||||
<string name="app_theme_dark">Dark</string>
|
||||
<string name="app_theme_system">Use system design</string>
|
||||
|
||||
<string name="pref_category_tabs">Tabs</string>
|
||||
<string name="pref_title_tab_tap">Action when tapping a tab</string>
|
||||
<string name="tab_tap_behaviour_jump_to_next_page">Jump to next page</string>
|
||||
<string name="tab_tap_behaviour_jump_to_newest">Jump to newest content</string>
|
||||
</resources>
|
||||
|
|
Loading…
Reference in New Issue