diff --git a/app/src/main/java/org/mozilla/fenix/components/topsheet/TopSheetBehavior.kt b/app/src/main/java/org/mozilla/fenix/components/topsheet/TopSheetBehavior.kt index 577f94435..0b107d83a 100644 --- a/app/src/main/java/org/mozilla/fenix/components/topsheet/TopSheetBehavior.kt +++ b/app/src/main/java/org/mozilla/fenix/components/topsheet/TopSheetBehavior.kt @@ -35,7 +35,7 @@ import kotlin.math.abs * limitations under the License. */ /** * An interaction behavior plugin for a child view of [CoordinatorLayout] to make it work as - * a bottom sheet. + * a top sheet. */ class TopSheetBehavior /** diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlAdapter.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlAdapter.kt index bde850a17..5dc464418 100644 --- a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlAdapter.kt +++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlAdapter.kt @@ -23,17 +23,7 @@ import org.mozilla.fenix.home.sessioncontrol.viewholders.NoCollectionsMessageVie import org.mozilla.fenix.home.sessioncontrol.viewholders.PrivateBrowsingDescriptionViewHolder import org.mozilla.fenix.home.sessioncontrol.viewholders.TabInCollectionViewHolder import org.mozilla.fenix.home.sessioncontrol.viewholders.TopSitePagerViewHolder -import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.OnboardingAutomaticSignInViewHolder -import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.OnboardingFinishViewHolder -import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.OnboardingHeaderViewHolder -import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.OnboardingManualSignInViewHolder -import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.OnboardingPrivacyNoticeViewHolder -import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.OnboardingPrivateBrowsingViewHolder -import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.OnboardingSectionHeaderViewHolder -import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.OnboardingThemePickerViewHolder -import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.OnboardingToolbarPositionPickerViewHolder -import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.OnboardingTrackingProtectionViewHolder -import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.OnboardingWhatsNewViewHolder +import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.* import org.mozilla.fenix.home.tips.ButtonTipViewHolder import mozilla.components.feature.tab.collections.Tab as ComponentTab @@ -106,6 +96,7 @@ sealed class AdapterItem(@LayoutRes val viewType: Int) { object OnboardingFinish : AdapterItem(OnboardingFinishViewHolder.LAYOUT_ID) object OnboardingToolbarPositionPicker : AdapterItem(OnboardingToolbarPositionPickerViewHolder.LAYOUT_ID) + object OnboardingTabsTrayLayoutPicker : AdapterItem(OnboardingTabsTrayLayoutViewHolder.LAYOUT_ID) object OnboardingWhatsNew : AdapterItem(OnboardingWhatsNewViewHolder.LAYOUT_ID) @@ -189,6 +180,7 @@ class SessionControlAdapter( OnboardingToolbarPositionPickerViewHolder.LAYOUT_ID -> OnboardingToolbarPositionPickerViewHolder( view ) + OnboardingTabsTrayLayoutViewHolder.LAYOUT_ID -> OnboardingTabsTrayLayoutViewHolder(view) else -> throw IllegalStateException() } } diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlView.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlView.kt index 5aaaa3854..c9f1a3667 100644 --- a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlView.kt +++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlView.kt @@ -99,6 +99,7 @@ private fun onboardingAdapterItems(onboardingState: OnboardingState): List(R.string.pref_key_tabs_tray_compact_tab).apply { - isChecked = context.settings().enableCompactTabs + requirePreference(R.string.pref_key_tabs_tray_top_tray).apply { + isChecked = context.settings().useTopTabsTray onPreferenceChangeListener = SharedPreferenceUpdater() } - requirePreference(R.string.pref_key_tabs_tray_top_tray).apply { - isChecked = context.settings().useTopTabsTray + requirePreference(R.string.pref_key_use_fullscreen_tabs_screen).apply { + isChecked = context.settings().useFullScreenTabScreen onPreferenceChangeListener = SharedPreferenceUpdater() } - requirePreference(R.string.pref_key_tabs_tray_reverse_tab_order).apply { - isChecked = context.settings().reverseTabOrderInTabsTray + val reverseOrderPref = requirePreference(R.string.pref_key_tabs_tray_reverse_tab_order).apply { + if (context.settings().enableCompactTabs) { + isChecked = false + isEnabled = false + } else { + isChecked = context.settings().reverseTabOrderInTabsTray + isEnabled = true + } onPreferenceChangeListener = SharedPreferenceUpdater() } + + requirePreference(R.string.pref_key_tabs_tray_compact_tab).apply { + isChecked = context.settings().enableCompactTabs + + onPreferenceChangeListener = Preference.OnPreferenceChangeListener { preference, newValue -> + val newValueBoolean = newValue as Boolean + preference.context.settings().preferences.edit { + putBoolean(preference.key, newValueBoolean) + if (newValueBoolean) { + reverseOrderPref.isChecked = false + putBoolean(getString(R.string.pref_key_tabs_tray_reverse_tab_order), false) + } + reverseOrderPref.isEnabled = !newValueBoolean + } + true + } + } } private fun setupFabCategory() { - requirePreference(R.string.pref_key_tabs_tray_use_fab).apply { - isChecked = context.settings().useNewTabFloatingActionButton + val fabPositionTop = requirePreference(R.string.pref_key_tabs_tray_fab_top_position).apply { + if (context.settings().useNewTabFloatingActionButton) { + isChecked = context.settings().placeNewTabFloatingActionButtonAtTop + isEnabled = true + } else { + isChecked = false + isEnabled = false + } onPreferenceChangeListener = SharedPreferenceUpdater() } - requirePreference(R.string.pref_key_tabs_tray_fab_top_position).apply { - isChecked = context.settings().placeNewTabFloatingActionButtonAtTop - onPreferenceChangeListener = SharedPreferenceUpdater() + requirePreference(R.string.pref_key_tabs_tray_use_fab).apply { + isChecked = context.settings().useNewTabFloatingActionButton + onPreferenceChangeListener = Preference.OnPreferenceChangeListener { preference, newValue -> + val newValueBoolean = newValue as Boolean + preference.context.settings().preferences.edit { + putBoolean(preference.key, newValueBoolean) + if (!newValueBoolean) { + fabPositionTop.isChecked = false + putBoolean(getString(R.string.pref_key_tabs_tray_fab_top_position), false) + } + fabPositionTop.isEnabled = newValueBoolean + } + true + } } + + } private fun setupHomeCategory() { diff --git a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayView.kt b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayView.kt index b034c4f20..4128a4357 100644 --- a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayView.kt +++ b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayView.kt @@ -25,7 +25,18 @@ import androidx.recyclerview.widget.LinearLayoutManager import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.tabs.TabLayout import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.main.component_tabstray_bottom.view.* +import kotlinx.android.synthetic.main.component_tabs_screen_top.view.* +import kotlinx.android.synthetic.main.component_tabstray_bottom.view.collect_multi_select +import kotlinx.android.synthetic.main.component_tabstray_bottom.view.exit_multi_select +import kotlinx.android.synthetic.main.component_tabstray_bottom.view.handle +import kotlinx.android.synthetic.main.component_tabstray_bottom.view.multiselect_title +import kotlinx.android.synthetic.main.component_tabstray_bottom.view.tab_layout +import kotlinx.android.synthetic.main.component_tabstray_bottom.view.tab_tray_empty_view +import kotlinx.android.synthetic.main.component_tabstray_bottom.view.tab_tray_new_tab +import kotlinx.android.synthetic.main.component_tabstray_bottom.view.tab_tray_overflow +import kotlinx.android.synthetic.main.component_tabstray_bottom.view.tab_wrapper +import kotlinx.android.synthetic.main.component_tabstray_bottom.view.tabsTray +import kotlinx.android.synthetic.main.component_tabstray_bottom.view.topBar import kotlinx.android.synthetic.main.component_tabstray_fab_bottom.view.* import kotlinx.android.synthetic.main.tabs_tray_tab_counter.* import kotlinx.coroutines.Dispatchers.Main @@ -81,19 +92,33 @@ class TabTrayView( private val enableCompactTabs = container.context.settings().enableCompactTabs private val reverseTabOrderInTabsTray = container.context.settings().reverseTabOrderInTabsTray + private val isTabsTrayFullScreenMode = container.context.settings().useFullScreenTabScreen private val hasAccessibilityEnabled = container.context.settings().accessibilityServicesEnabled private val useTopTabsTray = container.context.settings().useTopTabsTray - val view: View = when (useTopTabsTray) { - true -> LayoutInflater.from(container.context).inflate(R.layout.component_tabstray_top, container, true) - false -> LayoutInflater.from(container.context).inflate(R.layout.component_tabstray_bottom, container, true) + val view: View = if (isTabsTrayFullScreenMode) { + when (useTopTabsTray) { + true -> LayoutInflater.from(container.context) + .inflate(R.layout.component_tabs_screen_bottom, container, true) + false -> LayoutInflater.from(container.context) + .inflate(R.layout.component_tabs_screen_top, container, true) + } + } else { + when (useTopTabsTray) { + true -> LayoutInflater.from(container.context) + .inflate(R.layout.component_tabstray_top, container, true) + false -> LayoutInflater.from(container.context) + .inflate(R.layout.component_tabstray_bottom, container, true) + } } private val isPrivateModeSelected: Boolean get() = view.tab_layout.selectedTabPosition == PRIVATE_TAB_ID - private val behavior = when (useTopTabsTray) { - true -> TopSheetBehavior.from(view.tab_wrapper) - false -> BottomSheetBehavior.from(view.tab_wrapper) + private val behavior = if (isTabsTrayFullScreenMode) null else { + when (useTopTabsTray) { + true -> TopSheetBehavior.from(view.tab_wrapper) + false -> BottomSheetBehavior.from(view.tab_wrapper) + } } private val concatAdapter = ConcatAdapter(tabsAdapter) @@ -118,46 +143,54 @@ class TabTrayView( toggleFabText(isPrivate) - if (useTopTabsTray) { - (behavior as TopSheetBehavior).setTopSheetCallback(object : - TopSheetBehavior.TopSheetCallback() { - override fun onSlide(topSheet: View, slideOffset: Float, isOpening: Boolean?) { - if (interactor.onModeRequested() is Mode.Normal && useFab) { - if (slideOffset >= SLIDE_OFFSET) { - fabView.new_tab_button.show() - } else { - fabView.new_tab_button.hide() + if (!isTabsTrayFullScreenMode) { + if (useTopTabsTray) { + (behavior as TopSheetBehavior).setTopSheetCallback(object : + TopSheetBehavior.TopSheetCallback() { + override fun onSlide(topSheet: View, slideOffset: Float, isOpening: Boolean?) { + if (interactor.onModeRequested() is Mode.Normal && useFab) { + if (slideOffset >= SLIDE_OFFSET) { + fabView.new_tab_button.show() + } else { + fabView.new_tab_button.hide() + } } } - } - override fun onStateChanged(topSheet: View, newState: Int) { - if (newState == TopSheetBehavior.STATE_HIDDEN) { - components.analytics.metrics.track(Event.TabsTrayClosed) - interactor.onTabTrayDismissed() + override fun onStateChanged(topSheet: View, newState: Int) { + if (newState == TopSheetBehavior.STATE_HIDDEN) { + components.analytics.metrics.track(Event.TabsTrayClosed) + interactor.onTabTrayDismissed() + } } - } - }) - } else { - (behavior as BottomSheetBehavior).addBottomSheetCallback(object : - BottomSheetBehavior.BottomSheetCallback() { - override fun onSlide(bottomSheet: View, slideOffset: Float) { - if (interactor.onModeRequested() is Mode.Normal && useFab) { - if (slideOffset >= SLIDE_OFFSET) { - fabView.new_tab_button.show() - } else { - fabView.new_tab_button.hide() + }) + } else { + (behavior as BottomSheetBehavior).addBottomSheetCallback(object : + BottomSheetBehavior.BottomSheetCallback() { + override fun onSlide(bottomSheet: View, slideOffset: Float) { + if (interactor.onModeRequested() is Mode.Normal && useFab) { + if (slideOffset >= SLIDE_OFFSET) { + fabView.new_tab_button.show() + } else { + fabView.new_tab_button.hide() + } } } - } - override fun onStateChanged(bottomSheet: View, newState: Int) { - if (newState == BottomSheetBehavior.STATE_HIDDEN) { - components.analytics.metrics.track(Event.TabsTrayClosed) - interactor.onTabTrayDismissed() + override fun onStateChanged(bottomSheet: View, newState: Int) { + if (newState == BottomSheetBehavior.STATE_HIDDEN) { + components.analytics.metrics.track(Event.TabsTrayClosed) + interactor.onTabTrayDismissed() + } } - } - }) + }) + } + } + + if (isTabsTrayFullScreenMode) { + view.exit_tabs_screen.setOnClickListener { + interactor.onTabTrayDismissed() + } } val selectedTabIndex = if (!isPrivate) { @@ -218,6 +251,8 @@ class TabTrayView( // Put the 'Add to collections' button after the tabs have loaded. // And, put the Synced Tabs adapter at the end. if (reverseTabOrderInTabsTray) { + // Put these at the start when reverse tab order is enabled. Also, we disallow + // reverse tab order for compact tabs in settings. concatAdapter.addAdapter(0, collectionsButtonAdapter) concatAdapter.addAdapter(0, syncedTabsController.adapter) } else { @@ -330,32 +365,15 @@ class TabTrayView( if (enableCompactTabs) { val gridLayoutManager = GridLayoutManager(container.context, gridViewNumberOfCols(container.context)) if (useTopTabsTray) { - if (!reverseTabOrderInTabsTray) { - gridLayoutManager.reverseLayout = true - } - } else { - if (reverseTabOrderInTabsTray) { - gridLayoutManager.reverseLayout = true - } + gridLayoutManager.reverseLayout = true } gridLayoutManager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() { override fun getSpanSize(position: Int): Int { val numTabs = tabsAdapter.itemCount - val totalItems = numTabs + collectionsButtonAdapter.itemCount + - syncedTabsController.adapter.itemCount - - return if (reverseTabOrderInTabsTray) { - if (totalItems - 1 - position < numTabs) { - 1 - } else { - gridViewNumberOfCols(container.context) - } + return if (position < numTabs) { + 1 } else { - if (position < numTabs) { - 1 - } else { - gridViewNumberOfCols(container.context) - } + gridViewNumberOfCols(container.context) } } } @@ -382,10 +400,12 @@ class TabTrayView( } fun expand() { - if (useTopTabsTray) { - (behavior as TopSheetBehavior).state = TopSheetBehavior.STATE_EXPANDED - } else { - (behavior as BottomSheetBehavior).state = BottomSheetBehavior.STATE_EXPANDED + if (!isTabsTrayFullScreenMode) { + if (useTopTabsTray) { + (behavior as TopSheetBehavior).state = TopSheetBehavior.STATE_EXPANDED + } else { + (behavior as BottomSheetBehavior).state = BottomSheetBehavior.STATE_EXPANDED + } } } @@ -570,10 +590,12 @@ class TabTrayView( ) ) + if (isTabsTrayFullScreenMode) { + view.exit_tabs_screen.isVisible = !multiselect + } view.tab_layout.isVisible = !multiselect view.tab_tray_empty_view.isVisible = !multiselect view.tab_tray_overflow.isVisible = !multiselect - view.tab_layout.isVisible = !multiselect } private fun updateTabsForMultiselectModeChanged(inMultiselectMode: Boolean) { @@ -622,14 +644,16 @@ class TabTrayView( } fun setTopOffset(landscape: Boolean) { - val topOffset = if (landscape) { - 0 - } else { - view.context.resources.getDimension(R.dimen.tab_tray_top_offset).toInt() - } + if (!isTabsTrayFullScreenMode) { + val topOffset = if (landscape) { + 0 + } else { + view.context.resources.getDimension(R.dimen.tab_tray_top_offset).toInt() + } - if (!useTopTabsTray) { - (behavior as BottomSheetBehavior).setExpandedOffset(topOffset) + if (!useTopTabsTray) { + (behavior as BottomSheetBehavior).setExpandedOffset(topOffset) + } } } @@ -664,18 +688,12 @@ class TabTrayView( val selectedBrowserTabIndex = tabs .indexOfFirst { it.id == sessionId } - val recyclerViewIndex = if (reverseTabOrderInTabsTray && !enableCompactTabs) { + val recyclerViewIndex = if (reverseTabOrderInTabsTray) { // For reverse tab order and non-compact tabs, we add the items in collections button // adapter and synced tabs adapter, and offset by 1 to show the tab above the // current tab, unless current tab is first in reverse order. min(selectedBrowserTabIndex + 1, tabsAdapter.itemCount - 1) + collectionsButtonAdapter.itemCount + syncedTabsController.adapter.itemCount - } else if (reverseTabOrderInTabsTray && enableCompactTabs) { - // For reverse tab order and compact tabs, we add the items in collections button - // adapter and synced tabs adapter, and offset by -1 to show the tab above the - // current tab, unless current tab is first in reverse order. - max(0, selectedBrowserTabIndex - 1 + collectionsButtonAdapter.itemCount + - syncedTabsController.adapter.itemCount) } else { // We offset index by -1 to show the tab above the current tab, unless current tab // is the first. diff --git a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt index 4652b855d..7a08f93c9 100644 --- a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt +++ b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt @@ -875,9 +875,9 @@ class Settings(private val appContext: Context) : PreferencesHolder { BuildConfig.AMO_COLLECTION ) - val enableCompactTabs by booleanPreference( + var enableCompactTabs by booleanPreference( appContext.getPreferenceKey(R.string.pref_key_tabs_tray_compact_tab), - default = false + default = true ) val useTopTabsTray by booleanPreference( @@ -885,17 +885,25 @@ class Settings(private val appContext: Context) : PreferencesHolder { default = false ) - val reverseTabOrderInTabsTray by booleanPreference( - appContext.getPreferenceKey(R.string.pref_key_tabs_tray_reverse_tab_order), + var useFullScreenTabScreen by booleanPreference( + appContext.getPreferenceKey(R.string.pref_key_use_fullscreen_tabs_screen), default = true ) - val useNewTabFloatingActionButton by booleanPreference( + val shouldUseFennecStyleTabsScreen: Boolean + get() = enableCompactTabs && useFullScreenTabScreen + + var reverseTabOrderInTabsTray by booleanPreference( + appContext.getPreferenceKey(R.string.pref_key_tabs_tray_reverse_tab_order), + default = false + ) + + var useNewTabFloatingActionButton by booleanPreference( appContext.getPreferenceKey(R.string.pref_key_tabs_tray_use_fab), - default = true + default = false ) - val placeNewTabFloatingActionButtonAtTop by booleanPreference( + var placeNewTabFloatingActionButtonAtTop by booleanPreference( appContext.getPreferenceKey(R.string.pref_key_tabs_tray_fab_top_position), default = false ) diff --git a/app/src/main/res/drawable/ic_new.xml b/app/src/main/res/drawable/ic_new.xml index 52f76c04b..57a1dbcd9 100644 --- a/app/src/main/res/drawable/ic_new.xml +++ b/app/src/main/res/drawable/ic_new.xml @@ -8,6 +8,6 @@ android:viewportWidth="24" android:viewportHeight="24"> diff --git a/app/src/main/res/layout/component_tabs_screen_bottom.xml b/app/src/main/res/layout/component_tabs_screen_bottom.xml new file mode 100644 index 000000000..7c6234118 --- /dev/null +++ b/app/src/main/res/layout/component_tabs_screen_bottom.xml @@ -0,0 +1,191 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/component_tabs_screen_top.xml b/app/src/main/res/layout/component_tabs_screen_top.xml new file mode 100644 index 000000000..ba8162aea --- /dev/null +++ b/app/src/main/res/layout/component_tabs_screen_top.xml @@ -0,0 +1,192 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/component_tabstray_top.xml b/app/src/main/res/layout/component_tabstray_top.xml index ad7480343..09d16d7a8 100644 --- a/app/src/main/res/layout/component_tabstray_top.xml +++ b/app/src/main/res/layout/component_tabstray_top.xml @@ -108,6 +108,7 @@ app:tabGravity="fill" app:tabIconTint="@color/tab_icon" app:tabIndicatorColor="@color/accent_normal_theme" + app:tabIndicatorGravity="top" app:tabRippleColor="@android:color/transparent"> + + + + + + + + + + + + diff --git a/app/src/main/res/values/preference_keys.xml b/app/src/main/res/values/preference_keys.xml index 1c8c284b0..532ffff3c 100644 --- a/app/src/main/res/values/preference_keys.xml +++ b/app/src/main/res/values/preference_keys.xml @@ -130,6 +130,8 @@ pref_tabs_tray_settings_category pref_key_tabs_tray_compact_tab pref_key_tabs_tray_top_tray + pref_key_use_fullscreen_tabs_screen + pref_key_use_fenix_tabs_tray pref_key_tabs_tray_reverse_tab_order diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c7379ba4d..530674eb1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -33,6 +33,8 @@ Select collection Exit multiselect mode + + Close tabs screen Save selected tabs to collection @@ -1072,6 +1074,18 @@ Strict Blocks more trackers, ads, and popups. Pages load faster, but some functionality might not work. + + Choose your tabs tray layout + + Pick either the old Firefox (Fennec) style tabs screen or the new and fancy (Fenix) style tabs tray. + + Old Fennec style (default) + + Clicking the tab count on the toolbar will show you a tabs screen with a layout similar to the previous (Fennec) version of Firefox. + + New Fenix style + + Clicking the tab count on the toolbar will show you a new tabs tray layout from the Fenix version of Firefox. @@ -1549,6 +1563,8 @@ Enable compact tabs Enable top tabs tray + + Enable fullscreen tabs screen Reverse tab order in tray diff --git a/app/src/main/res/xml/customization_preferences.xml b/app/src/main/res/xml/customization_preferences.xml index 25bee4f1e..d03f5881c 100644 --- a/app/src/main/res/xml/customization_preferences.xml +++ b/app/src/main/res/xml/customization_preferences.xml @@ -57,7 +57,7 @@ app:allowDividerAbove="true" app:iconSpaceReserved="false"> + @@ -78,7 +82,7 @@ app:allowDividerAbove="false" app:iconSpaceReserved="false">