Skip to content

Commit

Permalink
feat: Experimental preference to reverse home timeline (#867)
Browse files Browse the repository at this point in the history
Add a new set of preferences, "Lab experiments", to control features
that are under investigation and may never make it into the mainstream.

Add the first experimental feature, which reverses the order of the home
timeline, so posts are shown oldest first instead of newest first.
  • Loading branch information
nikclayton authored Aug 5, 2024
1 parent 7c32a07 commit c919f86
Show file tree
Hide file tree
Showing 10 changed files with 142 additions and 84 deletions.
85 changes: 4 additions & 81 deletions app/lint-baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1090,83 +1090,6 @@
column="5"/>
</issue>

<issue
id="Overdraw"
message="Possible overdraw: Root element paints background `?android:attr/colorBackground` with a theme that also paints a background (inferred theme is `@style/AppTheme`)"
errorLine1=" android:background=&quot;?android:attr/colorBackground&quot;>"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/layout/fragment_timeline_notifications.xml"
line="23"
column="5"/>
</issue>

<issue
id="Overdraw"
message="Possible overdraw: Root element paints background `?attr/selectableItemBackground` with a theme that also paints a background (inferred theme is `@style/AppTheme`)"
errorLine1=" android:background=&quot;?attr/selectableItemBackground&quot;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/layout/item_account.xml"
line="8"
column="5"/>
</issue>

<issue
id="Overdraw"
message="Possible overdraw: Root element paints background `?attr/selectableItemBackground` with a theme that also paints a background (inferred theme is `@style/AppTheme`)"
errorLine1=" android:background=&quot;?attr/selectableItemBackground&quot;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/layout/item_draft.xml"
line="8"
column="5"/>
</issue>

<issue
id="Overdraw"
message="Possible overdraw: Root element paints background `?attr/selectableItemBackgroundBorderless` with a theme that also paints a background (inferred theme is `@style/AppTheme`)"
errorLine1=" android:background=&quot;?attr/selectableItemBackgroundBorderless&quot;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/layout/item_emoji_button.xml"
line="4"
column="5"/>
</issue>

<issue
id="Overdraw"
message="Possible overdraw: Root element paints background `?attr/selectableItemBackground` with a theme that also paints a background (inferred theme is `@style/AppTheme`)"
errorLine1=" android:background=&quot;?attr/selectableItemBackground&quot;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/layout/item_hashtag.xml"
line="16"
column="5"/>
</issue>

<issue
id="Overdraw"
message="Possible overdraw: Root element paints background `?android:colorBackground` with a theme that also paints a background (inferred theme is `@style/AppTheme`)"
errorLine1=" android:background=&quot;?android:colorBackground&quot;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/layout/item_tab_preference.xml"
line="7"
column="5"/>
</issue>

<issue
id="Overdraw"
message="Possible overdraw: Root element paints background `?attr/selectableItemBackground` with a theme that also paints a background (inferred theme is `@style/AppTheme`)"
errorLine1=" android:background=&quot;?attr/selectableItemBackground&quot;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/layout/item_tab_preference_small.xml"
line="7"
column="5"/>
</issue>

<issue
id="UnusedResources"
message="The resource `R.menu.activity_scheduled_status` appears to be unused"
Expand Down Expand Up @@ -2281,7 +2204,7 @@
errorLine2=" ~~~~~~~~">
<location
file="src/main/res/layout/item_status.xml"
line="247"
line="246"
column="6"/>
</issue>

Expand All @@ -2292,7 +2215,7 @@
errorLine2=" ~~~~~~~~">
<location
file="src/main/res/layout/item_status.xml"
line="275"
line="274"
column="6"/>
</issue>

Expand All @@ -2303,7 +2226,7 @@
errorLine2=" ~~~~~~~~">
<location
file="src/main/res/layout/item_status.xml"
line="303"
line="302"
column="6"/>
</issue>

Expand Down Expand Up @@ -2600,7 +2523,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/layout/item_status.xml"
line="237"
line="236"
column="9"/>
</issue>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* 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.components.preference

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreferenceCompat
import app.pachli.R
import app.pachli.core.preferences.PrefKeys
import app.pachli.databinding.FragmentLabPreferencesWarningBinding
import app.pachli.settings.makePreferenceScreen
import app.pachli.settings.switchPreference

class LabPreferencesFragment : PreferenceFragmentCompat() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
val defaultView = super.onCreateView(inflater, container, savedInstanceState)

// Insert a warning message as the first child in the layout.
val warningBinding = FragmentLabPreferencesWarningBinding.inflate(inflater, null, false)
(defaultView as? ViewGroup)?.addView(warningBinding.root, 0)

return defaultView
}

override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
val context = requireContext()

makePreferenceScreen {
switchPreference {
key = PrefKeys.LAB_REVERSE_TIMELINE
setTitle(R.string.pref_labs_reverse_home_timeline_title)
setSummaryProvider {
if ((it as SwitchPreferenceCompat).isChecked) {
context.getString(R.string.pref_labs_reverse_home_timeline_on_summary)
} else {
context.getString(R.string.pref_labs_reverse_home_timeline_off_summary)
}
}
isIconSpaceReserved = false
}
}
}

override fun onResume() {
super.onResume()
requireActivity().setTitle(R.string.pref_title_labs)
}

companion object {
fun newInstance() = LabPreferencesFragment()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,14 @@ class PreferencesFragment : PreferenceFragmentCompat() {
icon = makeIcon(GoogleMaterial.Icon.gmd_refresh)
}
}

preferenceCategory(R.string.pref_title_labs) {
it.icon = makeIcon(GoogleMaterial.Icon.gmd_science)
preference {
setTitle(R.string.pref_title_labs)
fragment = LabPreferencesFragment::class.qualifiedName
}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,17 @@ class TimelineFragment :
// Collect the uiState. Nothing is done with it, but if you don't collect it then
// accessing viewModel.uiState.value (e.g., to check whether the FAB should be
// hidden) always returns the initial state.
launch { viewModel.uiState.collect() }
launch {
viewModel.uiState.collect { uiState ->
if (layoutManager.reverseLayout != uiState.reverseTimeline) {
layoutManager.reverseLayout = uiState.reverseTimeline
layoutManager.stackFromEnd = uiState.reverseTimeline
// Force recyclerView to re-layout everything.
binding.recyclerView.layoutManager = null
binding.recyclerView.layoutManager = layoutManager
}
}
}

// Update status display from statusDisplayOptions. If the new options request
// relative time display collect the flow to periodically re-bind the UI.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ import timber.log.Timber
data class UiState(
/** True if the FAB should be shown while scrolling */
val showFabWhileScrolling: Boolean,

/** True if the timeline should be shown in reverse order (oldest first) */
val reverseTimeline: Boolean,
)

// TODO: Ui* classes are copied from NotificationsViewModel. Not yet sure whether these actions
Expand Down Expand Up @@ -403,16 +406,21 @@ abstract class TimelineViewModel(
}
}

val watchedPrefs = setOf(PrefKeys.FAB_HIDE, PrefKeys.LAB_REVERSE_TIMELINE)
uiState = sharedPreferencesRepository.changes
.filter { it == PrefKeys.FAB_HIDE }
.filter { watchedPrefs.contains(it) }
.map {
UiState(
showFabWhileScrolling = !sharedPreferencesRepository.getBoolean(PrefKeys.FAB_HIDE, false),
reverseTimeline = sharedPreferencesRepository.getBoolean(PrefKeys.LAB_REVERSE_TIMELINE, false),
)
}.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(stopTimeoutMillis = 5000),
initialValue = UiState(showFabWhileScrolling = !sharedPreferencesRepository.getBoolean(PrefKeys.FAB_HIDE, false)),
initialValue = UiState(
showFabWhileScrolling = !sharedPreferencesRepository.getBoolean(PrefKeys.FAB_HIDE, false),
reverseTimeline = sharedPreferencesRepository.getBoolean(PrefKeys.LAB_REVERSE_TIMELINE, false),
),
)

if (timeline is Timeline.Home) {
Expand Down
30 changes: 30 additions & 0 deletions app/src/main/res/layout/fragment_lab_preferences_warning.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ 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>.
-->

<TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/TextSizeMedium"
android:textColor="?colorError"
android:background="?colorErrorContainer"
android:paddingStart="?listPreferredItemPaddingStart"
android:paddingEnd="?listPreferredItemPaddingEnd"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:text="@string/pref_labs_warning"
tools:ignore="Overdraw" />
5 changes: 5 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -677,6 +677,11 @@
<string name="update_dialog_title">An update is available</string>
<string name="update_dialog_neutral">Don\'t remind me for this version</string>
<string name="update_dialog_negative">Never remind me</string>
<string name="pref_title_labs">Lab experiments</string>
<string name="pref_labs_warning">These are experimental features. There is no guarantee they work, or will be included in future versions. Use at your own risk.</string>
<string name="pref_labs_reverse_home_timeline_title">Reverse timeline order</string>
<string name="pref_labs_reverse_home_timeline_off_summary">Posts are ordered newest first</string>
<string name="pref_labs_reverse_home_timeline_on_summary">Posts are ordered oldest first</string>
<string name="translating">Translating…</string>
<string name="translation_provider_fmt">%1$s</string>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class CachedTimelineViewModelTestUiState : CachedTimelineViewModelTestBase() {

private val initialUiState = UiState(
showFabWhileScrolling = true,
reverseTimeline = false,
)

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class NetworkTimelineViewModelTestUiState : NetworkTimelineViewModelTestBase() {

private val initialUiState = UiState(
showFabWhileScrolling = true,
reverseTimeline = false,
)

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ object PrefKeys {
const val UPDATE_NOTIFICATION_VERSIONCODE = "updateNotificationVersioncode"
const val UPDATE_NOTIFICATION_LAST_NOTIFICATION_MS = "updateNotificationLastNotificationMs"

const val LAB_REVERSE_TIMELINE = "labReverseTimeline"

/** Keys that are no longer used (e.g., the preference has been removed */
object Deprecated {
// Empty at this time
Expand Down

0 comments on commit c919f86

Please sign in to comment.