feat: Add accessibility options for tab justification and content (#1035)
Provide two new lab preferences for controlling the layout and content of main navigation tabs. Tabs can now be justfied to start, end, or fully (if room). Start/end justification may put the tabs closer to the user's fingers, depending on how they hold the device. Fully justified uses the full width of the tab bar (if the tabs don't require scrolling). The content can be set to one of: - Icon only (previous behaviour) - Text only - Icon with text beside - Icon with text below Fixes #336
This commit is contained in:
parent
8fe2850229
commit
2234c4c782
|
@ -112,6 +112,9 @@ import app.pachli.core.network.model.Account
|
|||
import app.pachli.core.network.model.Notification
|
||||
import app.pachli.core.preferences.MainNavigationPosition
|
||||
import app.pachli.core.preferences.PrefKeys.FONT_FAMILY
|
||||
import app.pachli.core.preferences.TabAlignment
|
||||
import app.pachli.core.preferences.TabContents
|
||||
import app.pachli.core.ui.AlignableTabLayoutAlignment
|
||||
import app.pachli.core.ui.extensions.reduceSwipeSensitivity
|
||||
import app.pachli.core.ui.makeIcon
|
||||
import app.pachli.databinding.ActivityMainBinding
|
||||
|
@ -951,6 +954,14 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider {
|
|||
}
|
||||
}
|
||||
|
||||
activeTabLayout.alignment = when (sharedPreferencesRepository.tabAlignment) {
|
||||
TabAlignment.START -> AlignableTabLayoutAlignment.START
|
||||
TabAlignment.JUSTIFY_IF_POSSIBLE -> AlignableTabLayoutAlignment.JUSTIFY_IF_POSSIBLE
|
||||
TabAlignment.END -> AlignableTabLayoutAlignment.END
|
||||
}
|
||||
val tabContents = sharedPreferencesRepository.tabContents
|
||||
activeTabLayout.isInlineLabel = tabContents == TabContents.ICON_TEXT_INLINE
|
||||
|
||||
// Save the previous tab so it can be restored later
|
||||
val previousTabIndex = binding.viewPager.currentItem
|
||||
val previousTab = tabAdapter.tabs.getOrNull(previousTabIndex)
|
||||
|
@ -970,7 +981,12 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider {
|
|||
binding.viewPager,
|
||||
true,
|
||||
) { tab: TabLayout.Tab, position: Int ->
|
||||
tab.icon = AppCompatResources.getDrawable(this@MainActivity, tabs[position].icon)
|
||||
if (tabContents != TabContents.TEXT_ONLY) {
|
||||
tab.icon = AppCompatResources.getDrawable(this@MainActivity, tabs[position].icon)
|
||||
}
|
||||
if (tabContents != TabContents.ICON_ONLY) {
|
||||
tab.text = tabs[position].title(this@MainActivity)
|
||||
}
|
||||
tab.contentDescription = tabs[position].title(this@MainActivity)
|
||||
}.also { it.attach() }
|
||||
|
||||
|
|
|
@ -25,6 +25,8 @@ import androidx.preference.PreferenceFragmentCompat
|
|||
import androidx.preference.SwitchPreferenceCompat
|
||||
import app.pachli.R
|
||||
import app.pachli.core.preferences.PrefKeys
|
||||
import app.pachli.core.preferences.TabAlignment
|
||||
import app.pachli.core.preferences.TabContents
|
||||
import app.pachli.core.preferences.TabTapBehaviour
|
||||
import app.pachli.databinding.FragmentLabPreferencesWarningBinding
|
||||
import app.pachli.settings.enumListPreference
|
||||
|
@ -65,6 +67,20 @@ class LabPreferencesFragment : PreferenceFragmentCompat() {
|
|||
key = PrefKeys.TAB_TAP_BEHAVIOUR
|
||||
isIconSpaceReserved = false
|
||||
}
|
||||
|
||||
enumListPreference<TabAlignment> {
|
||||
setDefaultValue(TabAlignment.START)
|
||||
setTitle(app.pachli.core.preferences.R.string.pref_title_tab_alignment)
|
||||
key = PrefKeys.TAB_ALIGNMENT
|
||||
isIconSpaceReserved = false
|
||||
}
|
||||
|
||||
enumListPreference<TabContents> {
|
||||
setDefaultValue(TabContents.ICON_ONLY)
|
||||
setTitle(app.pachli.core.preferences.R.string.pref_title_tab_contents)
|
||||
key = PrefKeys.TAB_CONTENTS
|
||||
isIconSpaceReserved = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -118,6 +118,7 @@ class PreferencesActivity :
|
|||
PrefKeys.STATUS_TEXT_SIZE, PrefKeys.ABSOLUTE_TIME_VIEW, PrefKeys.SHOW_BOT_OVERLAY, PrefKeys.ANIMATE_GIF_AVATARS, PrefKeys.USE_BLURHASH,
|
||||
PrefKeys.SHOW_SELF_USERNAME, PrefKeys.SHOW_CARDS_IN_TIMELINES, PrefKeys.CONFIRM_REBLOGS, PrefKeys.CONFIRM_FAVOURITES,
|
||||
PrefKeys.ENABLE_SWIPE_FOR_TABS, PrefKeys.MAIN_NAV_POSITION, PrefKeys.HIDE_TOP_TOOLBAR, PrefKeys.SHOW_STATS_INLINE,
|
||||
PrefKeys.TAB_ALIGNMENT, PrefKeys.TAB_CONTENTS,
|
||||
-> {
|
||||
restartActivitiesOnBackPressedCallback.isEnabled = true
|
||||
}
|
||||
|
|
|
@ -31,13 +31,13 @@
|
|||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/topNav"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
app:contentInsetStart="0dp"
|
||||
app:contentInsetStartWithNavigation="0dp"
|
||||
app:navigationContentDescription="@string/action_open_drawer">
|
||||
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
<app.pachli.core.ui.AlignableTabLayout
|
||||
android:id="@+id/tabLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -65,7 +65,7 @@
|
|||
app:navigationContentDescription="@string/action_open_drawer"
|
||||
app:fabAlignmentMode="end">
|
||||
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
<app.pachli.core.ui.AlignableTabLayout
|
||||
android:id="@+id/bottomTabLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
|
|
|
@ -131,6 +131,12 @@ object PrefKeys {
|
|||
*/
|
||||
const val CONFIRM_STATUS_LANGUAGE = "confirmStatusLanguage"
|
||||
|
||||
/** Tab alignment. See [TabAlignment]. */
|
||||
const val TAB_ALIGNMENT = "tabAlignment"
|
||||
|
||||
/** Tab contents. See [TabContents]. */
|
||||
const val TAB_CONTENTS = "tabContents"
|
||||
|
||||
/** Keys that are no longer used (e.g., the preference has been removed */
|
||||
object Deprecated {
|
||||
const val WELLBEING_LIMITED_NOTIFICATIONS = "wellbeingModeLimitedNotifications"
|
||||
|
|
|
@ -85,6 +85,15 @@ class SharedPreferencesRepository @Inject constructor(
|
|||
val showSelfUsername: ShowSelfUsername
|
||||
get() = getEnum(PrefKeys.SHOW_SELF_USERNAME, ShowSelfUsername.DISAMBIGUATE)
|
||||
|
||||
/** How to align tabs. */
|
||||
val tabAlignment: TabAlignment
|
||||
get() = getEnum(PrefKeys.TAB_ALIGNMENT, TabAlignment.START)
|
||||
|
||||
/** How to display tabs. */
|
||||
val tabContents: TabContents
|
||||
get() = getEnum(PrefKeys.TAB_CONTENTS, TabContents.ICON_ONLY)
|
||||
|
||||
/** Behaviour when tapping on a tab. */
|
||||
val tabTapBehaviour: TabTapBehaviour
|
||||
get() = getEnum(PrefKeys.TAB_TAP_BEHAVIOUR, TabTapBehaviour.JUMP_TO_NEXT_PAGE)
|
||||
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* 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
|
||||
|
||||
enum class TabAlignment(
|
||||
override val displayResource: Int,
|
||||
override val value: String? = null,
|
||||
) : PreferenceEnum {
|
||||
/**
|
||||
* Tabs take required width, align with start of writing direction
|
||||
* (i.e., left in LTR locales, right in RTL locales).
|
||||
*/
|
||||
START(R.string.pref_tab_alignment_start),
|
||||
|
||||
/**
|
||||
* Tabs expand to fill available width, if space.
|
||||
*/
|
||||
JUSTIFY_IF_POSSIBLE(R.string.pref_tab_alignment_justify_if_possible),
|
||||
|
||||
/**
|
||||
* Tabs take required width, align with end of writing direction
|
||||
* (i.e., left in LTR locales, right in RTL locales).
|
||||
*/
|
||||
END(R.string.pref_tab_alignment_end),
|
||||
}
|
|
@ -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
|
||||
|
||||
enum class TabContents(
|
||||
override val displayResource: Int,
|
||||
override val value: String? = null,
|
||||
) : PreferenceEnum {
|
||||
ICON_ONLY(R.string.pref_tab_contents_icon_only),
|
||||
TEXT_ONLY(R.string.pref_tab_contents_text_only),
|
||||
ICON_TEXT_INLINE(R.string.pref_tab_contents_icon_text_inline),
|
||||
ICON_TEXT_BELOW(R.string.pref_tab_contents_icon_text_below),
|
||||
}
|
|
@ -37,4 +37,15 @@
|
|||
<string name="pref_show_self_username_always">Always</string>
|
||||
<string name="pref_show_self_username_disambiguate">When multiple accounts logged in</string>
|
||||
<string name="pref_show_self_username_never">Never</string>
|
||||
|
||||
<string name="pref_title_tab_alignment">Align main navigation tabs</string>
|
||||
<string name="pref_tab_alignment_start">Start of text direction</string>
|
||||
<string name="pref_tab_alignment_justify_if_possible">Expand to full width</string>
|
||||
<string name="pref_tab_alignment_end">End of text direction</string>
|
||||
|
||||
<string name="pref_title_tab_contents">Content of main navigation tabs</string>
|
||||
<string name="pref_tab_contents_icon_only">Icon only</string>
|
||||
<string name="pref_tab_contents_text_only">Text only</string>
|
||||
<string name="pref_tab_contents_icon_text_inline">Icon with text beside</string>
|
||||
<string name="pref_tab_contents_icon_text_below">Icon with text below</string>
|
||||
</resources>
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
* 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.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.text.TextUtilsCompat
|
||||
import androidx.core.view.children
|
||||
import app.pachli.core.ui.AlignableTabLayoutAlignment.END
|
||||
import app.pachli.core.ui.AlignableTabLayoutAlignment.JUSTIFY_IF_POSSIBLE
|
||||
import app.pachli.core.ui.AlignableTabLayoutAlignment.START
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import java.util.Locale
|
||||
|
||||
/** How to align tabs. */
|
||||
enum class AlignableTabLayoutAlignment {
|
||||
/** Tabs align with start of writing direction. */
|
||||
START,
|
||||
|
||||
/** Tabs expand to full width if possible. */
|
||||
JUSTIFY_IF_POSSIBLE,
|
||||
|
||||
/** Tabs align with end of writing direction. */
|
||||
END,
|
||||
}
|
||||
|
||||
/**
|
||||
* Specalised [TabLayout] that can align the tabs.
|
||||
*
|
||||
* Ignores [setTabMode] in favour of [alignment].
|
||||
*
|
||||
* [START] is equivalent to setting tabMode to [TabLayout.MODE_SCROLLABLE].
|
||||
*
|
||||
* [JUSTIFY_IF_POSSIBLE] uses [TabLayout.MODE_SCROLLABLE] if there is not
|
||||
* enough space to show all tabs, and [TabLayout.MODE_FIXED] if there is.
|
||||
* Effectively justifying the tabs.
|
||||
*
|
||||
* [END] is equivalent to [START], but adds additional left or right
|
||||
* padding (depending on the text direction) to push the start of the tabs
|
||||
* to the correct end of the layout.
|
||||
*/
|
||||
class AlignableTabLayout @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = com.google.android.material.R.attr.tabStyle,
|
||||
) : TabLayout(context, attrs, defStyleAttr) {
|
||||
var alignment: AlignableTabLayoutAlignment = START
|
||||
set(value) {
|
||||
field = value
|
||||
invalidate()
|
||||
}
|
||||
|
||||
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||
when (alignment) {
|
||||
START -> {
|
||||
tabMode = MODE_SCROLLABLE
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
||||
}
|
||||
|
||||
JUSTIFY_IF_POSSIBLE -> {
|
||||
tabMode = MODE_SCROLLABLE
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
||||
|
||||
if (tabCount < 2) return
|
||||
|
||||
val tabLayout = getChildAt(0) as ViewGroup
|
||||
val totalWidth = tabLayout.children.fold(0) { i, v -> i + v.measuredWidth }
|
||||
|
||||
if (totalWidth <= measuredWidth) {
|
||||
tabMode = MODE_FIXED
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
||||
}
|
||||
}
|
||||
|
||||
END -> {
|
||||
tabMode = MODE_SCROLLABLE
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
||||
|
||||
val tabLayout = getChildAt(0) as ViewGroup
|
||||
val totalWidth = tabLayout.children.fold(0) { i, v -> i + v.measuredWidth }
|
||||
|
||||
if (totalWidth < measuredWidth) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
||||
|
||||
val isLeftToRight = TextUtilsCompat.getLayoutDirectionFromLocale(Locale.getDefault()) == View.LAYOUT_DIRECTION_LTR
|
||||
|
||||
val padding = measuredWidth - totalWidth
|
||||
if (isLeftToRight) {
|
||||
tabLayout.setPadding(padding, 0, 0, 0)
|
||||
} else {
|
||||
tabLayout.setPadding(0, 0, padding, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue