diff --git a/app/src/main/java/app/pachli/MainActivity.kt b/app/src/main/java/app/pachli/MainActivity.kt index 3d9539fa6..76b45b102 100644 --- a/app/src/main/java/app/pachli/MainActivity.kt +++ b/app/src/main/java/app/pachli/MainActivity.kt @@ -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() } diff --git a/app/src/main/java/app/pachli/components/preference/LabPreferencesFragment.kt b/app/src/main/java/app/pachli/components/preference/LabPreferencesFragment.kt index 5bce34f0f..6668c1e4b 100644 --- a/app/src/main/java/app/pachli/components/preference/LabPreferencesFragment.kt +++ b/app/src/main/java/app/pachli/components/preference/LabPreferencesFragment.kt @@ -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 { + setDefaultValue(TabAlignment.START) + setTitle(app.pachli.core.preferences.R.string.pref_title_tab_alignment) + key = PrefKeys.TAB_ALIGNMENT + isIconSpaceReserved = false + } + + enumListPreference { + setDefaultValue(TabContents.ICON_ONLY) + setTitle(app.pachli.core.preferences.R.string.pref_title_tab_contents) + key = PrefKeys.TAB_CONTENTS + isIconSpaceReserved = false + } } } diff --git a/app/src/main/java/app/pachli/components/preference/PreferencesActivity.kt b/app/src/main/java/app/pachli/components/preference/PreferencesActivity.kt index 3359535b3..8692a93fb 100644 --- a/app/src/main/java/app/pachli/components/preference/PreferencesActivity.kt +++ b/app/src/main/java/app/pachli/components/preference/PreferencesActivity.kt @@ -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 } diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 92187ab02..0f35ffae2 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -31,13 +31,13 @@ - - . + */ + +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), +} diff --git a/core/preferences/src/main/kotlin/app/pachli/core/preferences/TabContents.kt b/core/preferences/src/main/kotlin/app/pachli/core/preferences/TabContents.kt new file mode 100644 index 000000000..b5c737ab3 --- /dev/null +++ b/core/preferences/src/main/kotlin/app/pachli/core/preferences/TabContents.kt @@ -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 . + */ + +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), +} diff --git a/core/preferences/src/main/res/values/strings.xml b/core/preferences/src/main/res/values/strings.xml index f16a53b9c..d4966ff07 100644 --- a/core/preferences/src/main/res/values/strings.xml +++ b/core/preferences/src/main/res/values/strings.xml @@ -37,4 +37,15 @@ Always When multiple accounts logged in Never + + Align main navigation tabs + Start of text direction + Expand to full width + End of text direction + + Content of main navigation tabs + Icon only + Text only + Icon with text beside + Icon with text below diff --git a/core/ui/src/main/kotlin/app/pachli/core/ui/AlignableTabLayout.kt b/core/ui/src/main/kotlin/app/pachli/core/ui/AlignableTabLayout.kt new file mode 100644 index 000000000..8b26c9f3b --- /dev/null +++ b/core/ui/src/main/kotlin/app/pachli/core/ui/AlignableTabLayout.kt @@ -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 . + */ + +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) + } + } + } + } + } +}