From b8e2bfebd44a7bcd2a46286e9ea0091d60ccf85e Mon Sep 17 00:00:00 2001 From: ekager Date: Mon, 17 Aug 2020 17:34:35 -0400 Subject: [PATCH] [fenix] For https://github.com/mozilla-mobile/fenix/issues/4118 - Creates setting for auto closing tabs --- .../mozilla/fenix/ui/SettingsBasicsTest.kt | 12 ++++ .../mozilla/fenix/ui/robots/SettingsRobot.kt | 24 +++++++- .../ui/robots/SettingsSubMenuTabsRobot.kt | 58 +++++++++++++++++++ .../java/org/mozilla/fenix/HomeActivity.kt | 8 +++ .../settings/CloseTabsSettingsFragment.kt | 48 +++++++++++++++ .../fenix/settings/SettingsFragment.kt | 10 +++- .../fenix/tabtray/TabTrayController.kt | 5 ++ .../tabtray/TabTrayFragmentInteractor.kt | 9 +++ .../org/mozilla/fenix/tabtray/TabTrayView.kt | 9 +++ .../java/org/mozilla/fenix/utils/Settings.kt | 46 +++++++++++++++ .../main/res/drawable/ic_multiple_tabs.xml | 13 +++++ app/src/main/res/navigation/nav_graph.xml | 26 +++++++-- app/src/main/res/values/preference_keys.xml | 6 ++ .../main/res/xml/close_tabs_preferences.xml | 25 ++++++++ app/src/main/res/xml/preferences.xml | 5 ++ .../tabtray/DefaultTabTrayControllerTest.kt | 11 ++++ .../tabtray/TabTrayFragmentInteractorTest.kt | 9 +++ .../org/mozilla/fenix/utils/SettingsTest.kt | 41 +++++++++++++ 18 files changed, 358 insertions(+), 7 deletions(-) create mode 100644 app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuTabsRobot.kt create mode 100644 app/src/main/java/org/mozilla/fenix/settings/CloseTabsSettingsFragment.kt create mode 100644 app/src/main/res/drawable/ic_multiple_tabs.xml create mode 100644 app/src/main/res/xml/close_tabs_preferences.xml diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsBasicsTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsBasicsTest.kt index 4e11278758..5e5c69a1fb 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsBasicsTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsBasicsTest.kt @@ -69,6 +69,7 @@ class SettingsBasicsTest { verifyBasicsHeading() verifySearchEngineButton() verifyDefaultBrowserItem() + verifyCloseTabsItem() // drill down to submenu }.openSearchSubMenu { verifyDefaultSearchEngineHeader() @@ -168,6 +169,17 @@ class SettingsBasicsTest { } } + @Test + fun changeCloseTabsSetting() { + // Goes through the settings and verified the close tabs setting options. + homeScreen { + }.openThreeDotMenu { + }.openSettings { + }.openCloseTabsSubMenu { + verifyOptions() + } + } + @Test fun changeAccessibiltySettings() { // Goes through the settings and changes the default text on a webpage, then verifies if the text has changed. diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsRobot.kt index 5976ed4ac7..5ed56bb1ee 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsRobot.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsRobot.kt @@ -51,6 +51,7 @@ class SettingsRobot { fun verifyAccessibilityButton() = assertAccessibilityButton() fun verifySetAsDefaultBrowserButton() = assertSetAsDefaultBrowserButton() fun verifyDefaultBrowserItem() = assertDefaultBrowserItem() + fun verifyCloseTabsItem() = assertCloseTabsItem() fun verifyDefaultBrowserIsDisaled() = assertDefaultBrowserIsDisabled() fun clickDefaultBrowserSwitch() = toggleDefaultBrowserSwitch() fun verifyAndroidDefaultAppsMenuAppears() = assertAndroidDefaultAppsMenuAppears() @@ -134,6 +135,15 @@ class SettingsRobot { return SettingsSubMenuThemeRobot.Transition() } + fun openCloseTabsSubMenu(interact: SettingsSubMenuTabsRobot.() -> Unit): SettingsSubMenuTabsRobot.Transition { + + fun closeTabsButton() = onView(withText("Close tabs")) + closeTabsButton().click() + + SettingsSubMenuTabsRobot().interact() + return SettingsSubMenuTabsRobot.Transition() + } + fun openAccessibilitySubMenu(interact: SettingsSubMenuAccessibilityRobot.() -> Unit): SettingsSubMenuAccessibilityRobot.Transition { fun accessibilityButton() = onView(withText("Accessibility")) @@ -237,8 +247,11 @@ private fun assertSettingsView() { } // GENERAL SECTION -private fun assertGeneralHeading() = onView(withText("General")) - .check(matches(withEffectiveVisibility(Visibility.VISIBLE))) +private fun assertGeneralHeading() { + scrollToElementByText("General") + onView(withText("General")) + .check(matches(withEffectiveVisibility(Visibility.VISIBLE))) +} private fun assertSearchEngineButton() { mDevice.wait(Until.findObject(By.text("Search")), waitingTime) @@ -284,8 +297,15 @@ private fun assertDefaultBrowserItem() { .check(matches(withEffectiveVisibility(Visibility.VISIBLE))) } +private fun assertCloseTabsItem() { + mDevice.wait(Until.findObject(By.text("Close tabs")), waitingTime) + onView(withText("Close tabs")) + .check(matches(withEffectiveVisibility(Visibility.VISIBLE))) +} + // PRIVACY SECTION private fun assertPrivacyHeading() { + scrollToElementByText("Privacy and security") onView(withText("Privacy and security")) .check(matches(withEffectiveVisibility(Visibility.VISIBLE))) } diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuTabsRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuTabsRobot.kt new file mode 100644 index 0000000000..d1fbaf01b6 --- /dev/null +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuTabsRobot.kt @@ -0,0 +1,58 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +@file:Suppress("TooManyFunctions") + +package org.mozilla.fenix.ui.robots + +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.action.ViewActions +import androidx.test.espresso.assertion.ViewAssertions +import androidx.test.espresso.matcher.ViewMatchers +import androidx.test.espresso.matcher.ViewMatchers.withText +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import org.hamcrest.CoreMatchers.allOf + +/** + * Implementation of Robot Pattern for the settings Tabs sub menu. + */ +class SettingsSubMenuTabsRobot { + + fun verifyOptions() = assertOptions() + + class Transition { + val mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) + + fun goBack(interact: SettingsRobot.() -> Unit): SettingsRobot.Transition { + mDevice.waitForIdle() + goBackButton().perform(ViewActions.click()) + + SettingsRobot().interact() + return SettingsRobot.Transition() + } + } +} + +private fun assertOptions() { + afterOneDayToggle() + .check(ViewAssertions.matches(ViewMatchers.withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))) + manualToggle() + .check(ViewAssertions.matches(ViewMatchers.withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))) + afterOneWeekToggle() + .check(ViewAssertions.matches(ViewMatchers.withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))) + afterOneMonthToggle() + .check(ViewAssertions.matches(ViewMatchers.withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))) +} + +private fun manualToggle() = onView(withText("Manually")) + +private fun afterOneDayToggle() = onView(withText("After one day")) + +private fun afterOneWeekToggle() = onView(withText("After one week")) + +private fun afterOneMonthToggle() = onView(withText("After one month")) + +private fun goBackButton() = + onView(allOf(ViewMatchers.withContentDescription("Navigate up"))) diff --git a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt index 2e380e2dc3..a40e9afde8 100644 --- a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt +++ b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt @@ -281,6 +281,14 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity { } settings().wasDefaultBrowserOnLastResume = settings().isDefaultBrowser() + + if (!settings().manuallyCloseTabs) { + components.core.store.state.tabs.filter { + (System.currentTimeMillis() - it.lastAccess) > settings().getTabTimeout() + }.forEach { + components.useCases.tabsUseCases.removeTab(it.id) + } + } } } diff --git a/app/src/main/java/org/mozilla/fenix/settings/CloseTabsSettingsFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/CloseTabsSettingsFragment.kt new file mode 100644 index 0000000000..9bb15f2952 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/settings/CloseTabsSettingsFragment.kt @@ -0,0 +1,48 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.settings + +import android.os.Bundle +import androidx.preference.PreferenceFragmentCompat +import org.mozilla.fenix.R +import org.mozilla.fenix.ext.showToolbar +import org.mozilla.fenix.utils.view.addToRadioGroup + +/** + * Lets the user customize auto closing tabs. + */ +class CloseTabsSettingsFragment : PreferenceFragmentCompat() { + private lateinit var radioManual: RadioButtonPreference + private lateinit var radioOneDay: RadioButtonPreference + private lateinit var radioOneWeek: RadioButtonPreference + private lateinit var radioOneMonth: RadioButtonPreference + + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + setPreferencesFromResource(R.xml.close_tabs_preferences, rootKey) + } + + override fun onResume() { + super.onResume() + showToolbar(getString(R.string.preferences_close_tabs)) + setupPreferences() + } + + private fun setupPreferences() { + radioManual = requirePreference(R.string.pref_key_close_tabs_manually) + radioOneDay = requirePreference(R.string.pref_key_close_tabs_after_one_day) + radioOneWeek = requirePreference(R.string.pref_key_close_tabs_after_one_week) + radioOneMonth = requirePreference(R.string.pref_key_close_tabs_after_one_month) + setupRadioGroups() + } + + private fun setupRadioGroups() { + addToRadioGroup( + radioManual, + radioOneDay, + radioOneMonth, + radioOneWeek + ) + } +} diff --git a/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt index 86c117bd26..3bebc0fa2f 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt @@ -170,6 +170,10 @@ class SettingsFragment : PreferenceFragmentCompat() { } } + val tabSettingsPreference = + requirePreference(R.string.pref_key_close_tabs) + tabSettingsPreference.summary = context?.settings()?.getTabTimeoutString() + setupPreferences() if (shouldUpdateAccountUIState) { @@ -192,6 +196,9 @@ class SettingsFragment : PreferenceFragmentCompat() { resources.getString(R.string.pref_key_sign_in) -> { SettingsFragmentDirections.actionSettingsFragmentToTurnOnSyncFragment() } + resources.getString(R.string.pref_key_close_tabs) -> { + SettingsFragmentDirections.actionSettingsFragmentToCloseTabsSettingsFragment() + } resources.getString(R.string.pref_key_search_settings) -> { SettingsFragmentDirections.actionSettingsFragmentToSearchEngineFragment() } @@ -301,7 +308,8 @@ class SettingsFragment : PreferenceFragmentCompat() { requirePreference(R.string.pref_key_external_download_manager) val preferenceLeakCanary = findPreference(leakKey) val preferenceRemoteDebugging = findPreference(debuggingKey) - val preferenceMakeDefaultBrowser = requirePreference(R.string.pref_key_make_default_browser) + val preferenceMakeDefaultBrowser = + requirePreference(R.string.pref_key_make_default_browser) if (!Config.channel.isReleased) { preferenceLeakCanary?.setOnPreferenceChangeListener { _, newValue -> diff --git a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayController.kt b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayController.kt index 7a3a03eeeb..c3b4df91a9 100644 --- a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayController.kt +++ b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayController.kt @@ -31,6 +31,7 @@ import org.mozilla.fenix.home.HomeFragment interface TabTrayController { fun onNewTabTapped(private: Boolean) fun onTabTrayDismissed() + fun handleTabSettingsClicked() fun onShareTabsClicked(private: Boolean) fun onSyncedTabClicked(syncTab: SyncTab) fun onSaveToCollectionClicked(selectedTabs: Set) @@ -88,6 +89,10 @@ class DefaultTabTrayController( ) } + override fun handleTabSettingsClicked() { + navController.navigate(TabTrayDialogFragmentDirections.actionGlobalCloseTabSettingsFragment()) + } + override fun onTabTrayDismissed() { dismissTabTray() } diff --git a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayFragmentInteractor.kt b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayFragmentInteractor.kt index b6a65dd779..2ec18d928a 100644 --- a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayFragmentInteractor.kt +++ b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayFragmentInteractor.kt @@ -24,6 +24,11 @@ interface TabTrayInteractor { */ fun onShareTabsClicked(private: Boolean) + /** + * Called when user clicks the tab settings button. + */ + fun onTabSettingsClicked() + /** * Called when user clicks button to save selected tabs to a collection. */ @@ -83,6 +88,10 @@ class TabTrayFragmentInteractor(private val controller: TabTrayController) : Tab controller.onTabTrayDismissed() } + override fun onTabSettingsClicked() { + controller.handleTabSettingsClicked() + } + override fun onShareTabsClicked(private: Boolean) { controller.onShareTabsClicked(private) } 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 b45b9332be..d2717e5135 100644 --- a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayView.kt +++ b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayView.kt @@ -214,6 +214,7 @@ class TabTrayView( is TabTrayItemMenu.Item.ShareAllTabs -> interactor.onShareTabsClicked( isPrivateModeSelected ) + is TabTrayItemMenu.Item.OpenTabSettings -> interactor.onTabSettingsClicked() is TabTrayItemMenu.Item.SaveToCollection -> interactor.onEnterMultiselect() is TabTrayItemMenu.Item.CloseAllTabs -> interactor.onCloseAllTabsClicked( isPrivateModeSelected @@ -574,6 +575,7 @@ class TabTrayItemMenu( sealed class Item { object ShareAllTabs : Item() + object OpenTabSettings : Item() object SaveToCollection : Item() object CloseAllTabs : Item() } @@ -598,6 +600,13 @@ class TabTrayItemMenu( onItemTapped.invoke(Item.ShareAllTabs) }, + SimpleBrowserMenuItem( + context.getString(R.string.tab_tray_menu_tab_settings), + textColorResource = R.color.primary_text_normal_theme + ) { + onItemTapped.invoke(Item.OpenTabSettings) + }, + SimpleBrowserMenuItem( context.getString(R.string.tab_tray_menu_item_close), textColorResource = R.color.primary_text_normal_theme 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 e08622d0b3..d4555f00f9 100644 --- a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt +++ b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt @@ -62,6 +62,10 @@ class Settings(private val appContext: Context) : PreferencesHolder { private const val CFR_COUNT_CONDITION_FOCUS_NOT_INSTALLED = 3 private const val MIN_DAYS_SINCE_FEEDBACK_PROMPT = 120 + const val ONE_DAY_MS = 60 * 60 * 24 * 1000L + const val ONE_WEEK_MS = 60 * 60 * 24 * 7 * 1000L + const val ONE_MONTH_MS = (60 * 60 * 24 * 365 * 1000L) / 12 + private fun Action.toInt() = when (this) { Action.BLOCKED -> BLOCKED_INT Action.ASK_TO_ALLOW -> ASK_TO_ALLOW_INT @@ -324,6 +328,48 @@ class Settings(private val appContext: Context) : PreferencesHolder { default = false ) + var manuallyCloseTabs by booleanPreference( + appContext.getPreferenceKey(R.string.pref_key_close_tabs_manually), + default = true + ) + + var closeTabsAfterOneDay by booleanPreference( + appContext.getPreferenceKey(R.string.pref_key_close_tabs_after_one_day), + default = false + ) + + var closeTabsAfterOneWeek by booleanPreference( + appContext.getPreferenceKey(R.string.pref_key_close_tabs_after_one_week), + default = false + ) + + var closeTabsAfterOneMonth by booleanPreference( + appContext.getPreferenceKey(R.string.pref_key_close_tabs_after_one_month), + default = false + ) + + fun getTabTimeout(): Long = when { + closeTabsAfterOneDay -> ONE_DAY_MS + closeTabsAfterOneWeek -> ONE_WEEK_MS + closeTabsAfterOneMonth -> ONE_MONTH_MS + else -> System.currentTimeMillis() + } + + fun getTabTimeoutString(): String = when { + closeTabsAfterOneDay -> { + appContext.getString(R.string.close_tabs_after_one_day) + } + closeTabsAfterOneWeek -> { + appContext.getString(R.string.close_tabs_after_one_week) + } + closeTabsAfterOneMonth -> { + appContext.getString(R.string.close_tabs_after_one_week) + } + else -> { + appContext.getString(R.string.close_tabs_manually) + } + } + val shouldUseDarkTheme by booleanPreference( appContext.getPreferenceKey(R.string.pref_key_dark_theme), default = false diff --git a/app/src/main/res/drawable/ic_multiple_tabs.xml b/app/src/main/res/drawable/ic_multiple_tabs.xml new file mode 100644 index 0000000000..e57048109b --- /dev/null +++ b/app/src/main/res/drawable/ic_multiple_tabs.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml index 9aead7da93..dd3963cbba 100644 --- a/app/src/main/res/navigation/nav_graph.xml +++ b/app/src/main/res/navigation/nav_graph.xml @@ -68,7 +68,8 @@ android:id="@+id/action_global_historyFragment" app:destination="@id/historyFragment" /> - + + + - - - pref_key_login_exceptions pref_key_show_collections_home + + pref_key_close_tabs + pref_key_close_tabs_manually + pref_key_close_tabs_after_one_day + pref_key_close_tabs_after_one_week + pref_key_close_tabs_after_one_month diff --git a/app/src/main/res/xml/close_tabs_preferences.xml b/app/src/main/res/xml/close_tabs_preferences.xml new file mode 100644 index 0000000000..c6d5f962bf --- /dev/null +++ b/app/src/main/res/xml/close_tabs_preferences.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index c161e4c963..e1f18cd8c1 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -77,6 +77,11 @@ android:icon="@drawable/ic_internet" android:key="@string/pref_key_make_default_browser" android:title="@string/preferences_set_as_default_browser" /> + +