v122
|
@ -0,0 +1,473 @@
|
||||||
|
/* 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.compose.ui.test.SemanticsMatcher
|
||||||
|
import androidx.compose.ui.test.assert
|
||||||
|
import androidx.compose.ui.test.assertIsDisplayed
|
||||||
|
import androidx.compose.ui.test.hasAnySibling
|
||||||
|
import androidx.compose.ui.test.hasContentDescription
|
||||||
|
import androidx.compose.ui.test.hasText
|
||||||
|
import androidx.compose.ui.test.junit4.ComposeTestRule
|
||||||
|
import androidx.compose.ui.test.onNodeWithText
|
||||||
|
import androidx.compose.ui.test.performClick
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import androidx.test.espresso.Espresso.closeSoftKeyboard
|
||||||
|
import androidx.test.espresso.Espresso.onView
|
||||||
|
import androidx.test.espresso.ViewInteraction
|
||||||
|
import androidx.test.espresso.action.ViewActions.clearText
|
||||||
|
import androidx.test.espresso.action.ViewActions.click
|
||||||
|
import androidx.test.espresso.action.ViewActions.typeText
|
||||||
|
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||||
|
import androidx.test.espresso.contrib.RecyclerViewActions
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.hasDescendant
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.hasSibling
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.withChild
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.withClassName
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||||
|
import androidx.test.uiautomator.By
|
||||||
|
import androidx.test.uiautomator.UiSelector
|
||||||
|
import org.hamcrest.CoreMatchers
|
||||||
|
import org.hamcrest.Matchers.allOf
|
||||||
|
import org.hamcrest.Matchers.endsWith
|
||||||
|
import org.junit.Assert.assertFalse
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
import org.mozilla.fenix.R
|
||||||
|
import org.mozilla.fenix.helpers.DataGenerationHelper.getAvailableSearchEngines
|
||||||
|
import org.mozilla.fenix.helpers.DataGenerationHelper.getRegionSearchEnginesList
|
||||||
|
import org.mozilla.fenix.helpers.DataGenerationHelper.getStringResource
|
||||||
|
import org.mozilla.fenix.helpers.MatcherHelper.assertUIObjectExists
|
||||||
|
import org.mozilla.fenix.helpers.MatcherHelper.itemContainingText
|
||||||
|
import org.mozilla.fenix.helpers.MatcherHelper.itemWithResIdAndText
|
||||||
|
import org.mozilla.fenix.helpers.MatcherHelper.itemWithText
|
||||||
|
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTimeShort
|
||||||
|
import org.mozilla.fenix.helpers.TestHelper.hasCousin
|
||||||
|
import org.mozilla.fenix.helpers.TestHelper.mDevice
|
||||||
|
import org.mozilla.fenix.helpers.TestHelper.packageName
|
||||||
|
import org.mozilla.fenix.helpers.click
|
||||||
|
import org.mozilla.fenix.helpers.isChecked
|
||||||
|
import org.mozilla.fenix.helpers.isEnabled
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of Robot Pattern for the settings search sub menu.
|
||||||
|
*/
|
||||||
|
class SettingsSubMenuSearchRobot {
|
||||||
|
fun verifyToolbarText(title: String) {
|
||||||
|
onView(
|
||||||
|
allOf(
|
||||||
|
withId(R.id.navigationToolbar),
|
||||||
|
hasDescendant(withContentDescription(R.string.action_bar_up_description)),
|
||||||
|
hasDescendant(withText(title)),
|
||||||
|
),
|
||||||
|
).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun verifySearchEnginesSectionHeader() {
|
||||||
|
onView(withText("Search engines")).check(matches(isDisplayed()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun verifyDefaultSearchEngineHeader() {
|
||||||
|
defaultSearchEngineHeader
|
||||||
|
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun verifyDefaultSearchEngineSummary(engineName: String) {
|
||||||
|
defaultSearchEngineHeader.check(matches(hasSibling(withText(engineName))))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun verifyManageSearchShortcutsHeader() {
|
||||||
|
manageSearchShortcutsHeader.check(matches(isDisplayed()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun verifyManageShortcutsSummary() {
|
||||||
|
manageSearchShortcutsHeader
|
||||||
|
.check(matches(hasSibling(withText("Edit engines visible in the search menu"))))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun verifyEnginesShortcutsListHeader() =
|
||||||
|
assertUIObjectExists(itemWithText("Engines visible on the search menu"))
|
||||||
|
|
||||||
|
fun verifyAddressBarSectionHeader() {
|
||||||
|
onView(withText("Address bar - Firefox Suggest")).check(matches(isDisplayed()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun verifyDefaultSearchEngineList() {
|
||||||
|
defaultSearchEngineOption("LeOSearch")
|
||||||
|
.check(matches(hasSibling(withId(R.id.engine_icon))))
|
||||||
|
.check(matches(isDisplayed()))
|
||||||
|
defaultSearchEngineOption("DuckDuckGo")
|
||||||
|
.check(matches(hasSibling(withId(R.id.engine_icon))))
|
||||||
|
.check(matches(isDisplayed()))
|
||||||
|
assertUIObjectExists(addSearchEngineButton)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun verifyManageShortcutsList(testRule: ComposeTestRule) {
|
||||||
|
val availableShortcutsEngines = getRegionSearchEnginesList() + getAvailableSearchEngines()
|
||||||
|
|
||||||
|
availableShortcutsEngines.forEach {
|
||||||
|
testRule.onNodeWithText(it.name)
|
||||||
|
.assert(hasAnySibling(hasContentDescription("${it.name} search engine")))
|
||||||
|
.assertIsDisplayed()
|
||||||
|
}
|
||||||
|
|
||||||
|
assertUIObjectExists(addSearchEngineButton)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method that verifies the selected engines inside the Manage search shortcuts list.
|
||||||
|
*/
|
||||||
|
fun verifySearchShortcutChecked(vararg engineShortcut: EngineShortcut) {
|
||||||
|
engineShortcut.forEach {
|
||||||
|
val shortcutIsChecked = mDevice.findObject(UiSelector().text(it.name))
|
||||||
|
.getFromParent(
|
||||||
|
UiSelector().index(it.checkboxIndex),
|
||||||
|
).isChecked
|
||||||
|
|
||||||
|
if (it.isChecked) {
|
||||||
|
assertTrue(shortcutIsChecked)
|
||||||
|
} else {
|
||||||
|
assertFalse(shortcutIsChecked)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun verifyAutocompleteURlsIsEnabled(enabled: Boolean) {
|
||||||
|
autocompleteSwitchButton()
|
||||||
|
.check(matches(hasCousin(allOf(withClassName(endsWith("Switch")), isChecked(enabled)))))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun verifyShowSearchSuggestionsEnabled(enabled: Boolean) {
|
||||||
|
showSearchSuggestionSwitchButton()
|
||||||
|
.check(matches(hasCousin(allOf(withClassName(endsWith("Switch")), isChecked(enabled)))))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun verifyShowSearchSuggestionsInPrivateEnabled(enabled: Boolean) {
|
||||||
|
showSuggestionsInPrivateModeSwitch()
|
||||||
|
.check(
|
||||||
|
matches(
|
||||||
|
hasSibling(
|
||||||
|
withChild(
|
||||||
|
allOf(
|
||||||
|
withClassName(endsWith("CheckBox")),
|
||||||
|
isChecked(enabled),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun verifyShowClipboardSuggestionsEnabled(enabled: Boolean) {
|
||||||
|
showClipboardSuggestionSwitch()
|
||||||
|
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
|
||||||
|
.check(matches(hasCousin(allOf(withClassName(endsWith("Switch")), isChecked(enabled)))))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun verifySearchBrowsingHistoryEnabled(enabled: Boolean) {
|
||||||
|
searchHistorySwitchButton()
|
||||||
|
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
|
||||||
|
.check(matches(hasCousin(allOf(withClassName(endsWith("Switch")), isChecked(enabled)))))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun verifySearchBookmarksEnabled(enabled: Boolean) {
|
||||||
|
searchBookmarksSwitchButton()
|
||||||
|
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
|
||||||
|
.check(matches(hasCousin(allOf(withClassName(endsWith("Switch")), isChecked(enabled)))))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun verifySearchSyncedTabsEnabled(enabled: Boolean) {
|
||||||
|
searchSyncedTabsSwitchButton()
|
||||||
|
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
|
||||||
|
.check(matches(hasCousin(allOf(withClassName(endsWith("Switch")), isChecked(enabled)))))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun verifyVoiceSearchEnabled(enabled: Boolean) {
|
||||||
|
voiceSearchSwitchButton()
|
||||||
|
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
|
||||||
|
.check(matches(hasCousin(allOf(withClassName(endsWith("Switch")), isChecked(enabled)))))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun openDefaultSearchEngineMenu() {
|
||||||
|
defaultSearchEngineHeader.click()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun openManageShortcutsMenu() {
|
||||||
|
manageSearchShortcutsHeader.click()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun changeDefaultSearchEngine(searchEngineName: String) {
|
||||||
|
onView(withText(searchEngineName))
|
||||||
|
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
|
||||||
|
.perform(click())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun selectSearchShortcut(shortcut: EngineShortcut) {
|
||||||
|
mDevice.findObject(UiSelector().text(shortcut.name))
|
||||||
|
.getFromParent(UiSelector().index(shortcut.checkboxIndex))
|
||||||
|
.click()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toggleAutocomplete() = autocompleteSwitchButton().click()
|
||||||
|
|
||||||
|
fun toggleShowSearchSuggestions() = showSearchSuggestionSwitchButton().click()
|
||||||
|
|
||||||
|
fun toggleVoiceSearch() {
|
||||||
|
voiceSearchSwitchButton().perform(click())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toggleClipboardSuggestion() {
|
||||||
|
showClipboardSuggestionSwitch().click()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun switchSearchHistoryToggle() = searchHistorySwitchButton().click()
|
||||||
|
|
||||||
|
fun switchSearchBookmarksToggle() = searchBookmarksSwitchButton().click()
|
||||||
|
|
||||||
|
fun switchShowSuggestionsInPrivateSessionsToggle() =
|
||||||
|
showSuggestionsInPrivateModeSwitch().click()
|
||||||
|
|
||||||
|
fun openAddSearchEngineMenu() = addSearchEngineButton.click()
|
||||||
|
|
||||||
|
fun verifyEngineListContains(searchEngineName: String, shouldExist: Boolean) =
|
||||||
|
assertUIObjectExists(itemWithText(searchEngineName), exists = shouldExist)
|
||||||
|
|
||||||
|
fun verifyDefaultSearchEngineSelected(searchEngineName: String) {
|
||||||
|
defaultSearchEngineOption(searchEngineName).check(matches(isChecked(true)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun verifySaveSearchEngineButtonEnabled(enabled: Boolean) {
|
||||||
|
addSearchEngineSaveButton().check(matches(isEnabled(enabled)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveNewSearchEngine() {
|
||||||
|
closeSoftKeyboard()
|
||||||
|
addSearchEngineSaveButton().click()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun typeCustomEngineDetails(engineName: String, engineURL: String) {
|
||||||
|
try {
|
||||||
|
mDevice.findObject(By.res("$packageName:id/edit_engine_name")).clear()
|
||||||
|
mDevice.findObject(By.res("$packageName:id/edit_engine_name")).text = engineName
|
||||||
|
assertUIObjectExists(
|
||||||
|
itemWithResIdAndText("$packageName:id/edit_engine_name", engineName),
|
||||||
|
)
|
||||||
|
|
||||||
|
mDevice.findObject(By.res("$packageName:id/edit_search_string")).clear()
|
||||||
|
mDevice.findObject(By.res("$packageName:id/edit_search_string")).text = engineURL
|
||||||
|
assertUIObjectExists(
|
||||||
|
itemWithResIdAndText("$packageName:id/edit_search_string", engineURL),
|
||||||
|
)
|
||||||
|
} catch (e: AssertionError) {
|
||||||
|
println("The name or the search string were not set properly")
|
||||||
|
|
||||||
|
mDevice.findObject(By.res("$packageName:id/edit_engine_name")).clear()
|
||||||
|
mDevice.findObject(By.res("$packageName:id/edit_engine_name")).setText(engineName)
|
||||||
|
assertUIObjectExists(
|
||||||
|
itemWithResIdAndText("$packageName:id/edit_engine_name", engineName),
|
||||||
|
)
|
||||||
|
mDevice.findObject(By.res("$packageName:id/edit_search_string")).clear()
|
||||||
|
mDevice.findObject(By.res("$packageName:id/edit_search_string")).setText(engineURL)
|
||||||
|
assertUIObjectExists(
|
||||||
|
itemWithResIdAndText("$packageName:id/edit_search_string", engineURL),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun typeSearchEngineSuggestionString(searchSuggestionString: String) {
|
||||||
|
onView(withId(R.id.edit_suggest_string))
|
||||||
|
.click()
|
||||||
|
.perform(clearText())
|
||||||
|
.perform(typeText(searchSuggestionString))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used in the non-Compose Default search engines menu
|
||||||
|
fun openEngineOverflowMenu(searchEngineName: String) {
|
||||||
|
threeDotMenu(searchEngineName).waitForExists(waitingTimeShort)
|
||||||
|
threeDotMenu(searchEngineName).click()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used in the composable Manage shortcuts menu, otherwise the overflow menu is not visible
|
||||||
|
fun openCustomShortcutOverflowMenu(testRule: ComposeTestRule, searchEngineName: String) {
|
||||||
|
testRule.onNode(overflowMenuWithSiblingText(searchEngineName)).performClick()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clickEdit() = onView(withText("Edit")).click()
|
||||||
|
|
||||||
|
// Used in the Default search engine menu
|
||||||
|
fun clickDeleteSearchEngine() =
|
||||||
|
mDevice.findObject(
|
||||||
|
UiSelector().textContains(getStringResource(R.string.search_engine_delete)),
|
||||||
|
).click()
|
||||||
|
|
||||||
|
// Used in the composable Manage search shortcuts menu, otherwise the overflow menu is not visible
|
||||||
|
fun clickDeleteSearchEngine(testRule: ComposeTestRule) =
|
||||||
|
testRule.onNodeWithText("Delete").performClick()
|
||||||
|
|
||||||
|
fun clickUndoSnackBarButton() =
|
||||||
|
mDevice.findObject(
|
||||||
|
UiSelector()
|
||||||
|
.resourceId("$packageName:id/snackbar_btn"),
|
||||||
|
).click()
|
||||||
|
|
||||||
|
fun saveEditSearchEngine() {
|
||||||
|
onView(withId(R.id.save_button)).click()
|
||||||
|
assertUIObjectExists(itemContainingText("Saved"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun verifyInvalidTemplateSearchStringFormatError() {
|
||||||
|
closeSoftKeyboard()
|
||||||
|
onView(withText(getStringResource(R.string.search_add_custom_engine_error_missing_template)))
|
||||||
|
.check(matches(isDisplayed()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun verifyErrorConnectingToSearchString(searchEngineName: String) {
|
||||||
|
closeSoftKeyboard()
|
||||||
|
onView(withText(getStringResource(R.string.search_add_custom_engine_error_cannot_reach, searchEngineName)))
|
||||||
|
.check(matches(isDisplayed()))
|
||||||
|
}
|
||||||
|
|
||||||
|
class Transition {
|
||||||
|
fun goBack(interact: SettingsRobot.() -> Unit): SettingsRobot.Transition {
|
||||||
|
mDevice.waitForIdle()
|
||||||
|
goBackButton().perform(click())
|
||||||
|
|
||||||
|
SettingsRobot().interact()
|
||||||
|
return SettingsRobot.Transition()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clickCustomSearchStringLearnMoreLink(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
|
||||||
|
onView(withId(R.id.custom_search_engines_learn_more)).click()
|
||||||
|
|
||||||
|
BrowserRobot().interact()
|
||||||
|
return BrowserRobot.Transition()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clickCustomSearchSuggestionsLearnMoreLink(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
|
||||||
|
onView(withId(R.id.custom_search_suggestions_learn_more)).click()
|
||||||
|
|
||||||
|
BrowserRobot().interact()
|
||||||
|
return BrowserRobot.Transition()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Matches search shortcut items inside the 'Manage search shortcuts' menu
|
||||||
|
* @param name, of type String, should be the name of the search engine.
|
||||||
|
* @param checkboxIndex, of type Int, is the checkbox' index afferent to the search engine.
|
||||||
|
* @param isChecked, of type Boolean, should show if the checkbox is expected to be checked.
|
||||||
|
*/
|
||||||
|
class EngineShortcut(
|
||||||
|
val name: String,
|
||||||
|
val checkboxIndex: Int,
|
||||||
|
val isChecked: Boolean = true,
|
||||||
|
)
|
||||||
|
|
||||||
|
private val defaultSearchEngineHeader = onView(withText("Default search engine"))
|
||||||
|
|
||||||
|
private val manageSearchShortcutsHeader = onView(withText("Manage alternative search engines"))
|
||||||
|
|
||||||
|
private fun searchHistorySwitchButton(): ViewInteraction {
|
||||||
|
onView(withId(androidx.preference.R.id.recycler_view)).perform(
|
||||||
|
RecyclerViewActions.scrollTo<RecyclerView.ViewHolder>(
|
||||||
|
hasDescendant(withText("Search browsing history")),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return onView(withText("Search browsing history"))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun searchBookmarksSwitchButton(): ViewInteraction {
|
||||||
|
onView(withId(androidx.preference.R.id.recycler_view)).perform(
|
||||||
|
RecyclerViewActions.scrollTo<RecyclerView.ViewHolder>(
|
||||||
|
hasDescendant(withText("Search bookmarks")),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return onView(withText("Search bookmarks"))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun searchSyncedTabsSwitchButton(): ViewInteraction {
|
||||||
|
onView(withId(androidx.preference.R.id.recycler_view)).perform(
|
||||||
|
RecyclerViewActions.scrollTo<RecyclerView.ViewHolder>(
|
||||||
|
hasDescendant(withText("Search synced tabs")),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return onView(withText("Search synced tabs"))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun voiceSearchSwitchButton(): ViewInteraction {
|
||||||
|
onView(withId(androidx.preference.R.id.recycler_view)).perform(
|
||||||
|
RecyclerViewActions.scrollTo<RecyclerView.ViewHolder>(
|
||||||
|
hasDescendant(withText("Show voice search")),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return onView(withText("Show voice search"))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun autocompleteSwitchButton(): ViewInteraction {
|
||||||
|
onView(withId(androidx.preference.R.id.recycler_view)).perform(
|
||||||
|
RecyclerViewActions.scrollTo<RecyclerView.ViewHolder>(
|
||||||
|
hasDescendant(withText(getStringResource(R.string.preferences_enable_autocomplete_urls))),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
return onView(withText(getStringResource(R.string.preferences_enable_autocomplete_urls)))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showSearchSuggestionSwitchButton(): ViewInteraction {
|
||||||
|
onView(withId(androidx.preference.R.id.recycler_view)).perform(
|
||||||
|
RecyclerViewActions.scrollTo<RecyclerView.ViewHolder>(
|
||||||
|
hasDescendant(withText("Show search suggestions")),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
return onView(withText("Show search suggestions"))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showClipboardSuggestionSwitch(): ViewInteraction {
|
||||||
|
onView(withId(androidx.preference.R.id.recycler_view)).perform(
|
||||||
|
RecyclerViewActions.scrollTo<RecyclerView.ViewHolder>(
|
||||||
|
hasDescendant(withText(getStringResource(R.string.preferences_show_clipboard_suggestions))),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return onView(withText(getStringResource(R.string.preferences_show_clipboard_suggestions)))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showSuggestionsInPrivateModeSwitch(): ViewInteraction {
|
||||||
|
onView(withId(androidx.preference.R.id.recycler_view)).perform(
|
||||||
|
RecyclerViewActions.scrollTo<RecyclerView.ViewHolder>(
|
||||||
|
hasDescendant(withText(getStringResource(R.string.preferences_show_search_suggestions_in_private))),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return onView(withText(getStringResource(R.string.preferences_show_search_suggestions_in_private)))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun goBackButton() =
|
||||||
|
onView(CoreMatchers.allOf(withContentDescription("Navigate up")))
|
||||||
|
|
||||||
|
private val addSearchEngineButton = mDevice.findObject(UiSelector().text("Add search engine"))
|
||||||
|
|
||||||
|
private fun addSearchEngineSaveButton() = onView(withId(R.id.save_button))
|
||||||
|
|
||||||
|
private fun threeDotMenu(searchEngineName: String) =
|
||||||
|
mDevice.findObject(UiSelector().text(searchEngineName))
|
||||||
|
.getFromParent(UiSelector().description("More options"))
|
||||||
|
|
||||||
|
private fun defaultSearchEngineOption(searchEngineName: String) =
|
||||||
|
onView(
|
||||||
|
allOf(
|
||||||
|
withId(R.id.radio_button),
|
||||||
|
hasSibling(withText(searchEngineName)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun overflowMenuWithSiblingText(text: String): SemanticsMatcher =
|
||||||
|
hasAnySibling(hasText(text)) and hasContentDescription("More options")
|
|
@ -1 +1 @@
|
||||||
Subproject commit b79bdfd15bedaf5e60d3432134d369e73b03d53b
|
Subproject commit 868ab5a098f62933b83eb70dd41126b3971ef4fc
|
|
@ -32,7 +32,7 @@ android {
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "com.leos"
|
applicationId "com.leos"
|
||||||
minSdk = 26
|
minSdkVersion config.minSdkVersion
|
||||||
compileSdk config.compileSdkVersion
|
compileSdk config.compileSdkVersion
|
||||||
targetSdkVersion config.targetSdkVersion
|
targetSdkVersion config.targetSdkVersion
|
||||||
versionCode 1
|
versionCode 1
|
||||||
|
@ -63,7 +63,7 @@ android {
|
||||||
// This should be the base URL used to call the AMO API.
|
// This should be the base URL used to call the AMO API.
|
||||||
buildConfigField "String", "AMO_SERVER_URL", "\"https://services.addons.mozilla.org\""
|
buildConfigField "String", "AMO_SERVER_URL", "\"https://services.addons.mozilla.org\""
|
||||||
|
|
||||||
def deepLinkSchemeValue = "leosium"
|
def deepLinkSchemeValue = "fenix-dev"
|
||||||
buildConfigField "String", "DEEP_LINK_SCHEME", "\"$deepLinkSchemeValue\""
|
buildConfigField "String", "DEEP_LINK_SCHEME", "\"$deepLinkSchemeValue\""
|
||||||
|
|
||||||
// This allows overriding the target activity for MozillaOnline builds, which happens
|
// This allows overriding the target activity for MozillaOnline builds, which happens
|
||||||
|
@ -82,6 +82,8 @@ android {
|
||||||
"targetActivity": targetActivity,
|
"targetActivity": targetActivity,
|
||||||
"deepLinkScheme": deepLinkSchemeValue
|
"deepLinkScheme": deepLinkSchemeValue
|
||||||
]
|
]
|
||||||
|
|
||||||
|
buildConfigField "String[]", "SUPPORTED_LOCALE_ARRAY", getSupportedLocales()
|
||||||
}
|
}
|
||||||
|
|
||||||
def releaseTemplate = {
|
def releaseTemplate = {
|
||||||
|
@ -109,14 +111,14 @@ android {
|
||||||
debug {
|
debug {
|
||||||
shrinkResources false
|
shrinkResources false
|
||||||
minifyEnabled false
|
minifyEnabled false
|
||||||
applicationIdSuffix ".leosium.debug"
|
applicationIdSuffix ".fenix.debug"
|
||||||
resValue "bool", "IS_DEBUG", "true"
|
resValue "bool", "IS_DEBUG", "true"
|
||||||
pseudoLocalesEnabled true
|
pseudoLocalesEnabled true
|
||||||
}
|
}
|
||||||
nightly releaseTemplate >> {
|
nightly releaseTemplate >> {
|
||||||
applicationIdSuffix ".fenix"
|
applicationIdSuffix ".fenix"
|
||||||
buildConfigField "boolean", "USE_RELEASE_VERSIONING", "true"
|
buildConfigField "boolean", "USE_RELEASE_VERSIONING", "true"
|
||||||
def deepLinkSchemeValue = "leosium.debug"
|
def deepLinkSchemeValue = "fenix-nightly"
|
||||||
buildConfigField "String", "DEEP_LINK_SCHEME", "\"$deepLinkSchemeValue\""
|
buildConfigField "String", "DEEP_LINK_SCHEME", "\"$deepLinkSchemeValue\""
|
||||||
manifestPlaceholders.putAll([
|
manifestPlaceholders.putAll([
|
||||||
"deepLinkScheme": deepLinkSchemeValue
|
"deepLinkScheme": deepLinkSchemeValue
|
||||||
|
@ -124,7 +126,7 @@ android {
|
||||||
}
|
}
|
||||||
beta releaseTemplate >> {
|
beta releaseTemplate >> {
|
||||||
buildConfigField "boolean", "USE_RELEASE_VERSIONING", "true"
|
buildConfigField "boolean", "USE_RELEASE_VERSIONING", "true"
|
||||||
applicationIdSuffix ".leosium.beta"
|
applicationIdSuffix ".firefox_beta"
|
||||||
def deepLinkSchemeValue = "fenix-beta"
|
def deepLinkSchemeValue = "fenix-beta"
|
||||||
buildConfigField "String", "DEEP_LINK_SCHEME", "\"$deepLinkSchemeValue\""
|
buildConfigField "String", "DEEP_LINK_SCHEME", "\"$deepLinkSchemeValue\""
|
||||||
manifestPlaceholders.putAll([
|
manifestPlaceholders.putAll([
|
||||||
|
@ -141,7 +143,7 @@ android {
|
||||||
}
|
}
|
||||||
release releaseTemplate >> {
|
release releaseTemplate >> {
|
||||||
buildConfigField "boolean", "USE_RELEASE_VERSIONING", "true"
|
buildConfigField "boolean", "USE_RELEASE_VERSIONING", "true"
|
||||||
applicationIdSuffix ".leosium"
|
applicationIdSuffix ".firefox"
|
||||||
def deepLinkSchemeValue = "fenix"
|
def deepLinkSchemeValue = "fenix"
|
||||||
buildConfigField "String", "DEEP_LINK_SCHEME", "\"$deepLinkSchemeValue\""
|
buildConfigField "String", "DEEP_LINK_SCHEME", "\"$deepLinkSchemeValue\""
|
||||||
manifestPlaceholders.putAll([
|
manifestPlaceholders.putAll([
|
||||||
|
@ -239,7 +241,14 @@ android {
|
||||||
|
|
||||||
reset()
|
reset()
|
||||||
|
|
||||||
include "armeabi-v7a", "arm64-v8a"
|
// As gradle is unable to pick the right apk to install when multiple apks are generated
|
||||||
|
// while running benchmark tests or generating baseline profiles. To circumvent this,
|
||||||
|
// this flag is passed to make sure only one apk is generated so gradle can pick that one.
|
||||||
|
if (project.hasProperty("benchmarkTest")) {
|
||||||
|
include "arm64-v8a"
|
||||||
|
} else {
|
||||||
|
include "x86", "armeabi-v7a", "arm64-v8a", "x86_64"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -515,7 +524,6 @@ android.applicationVariants.configureEach { variant ->
|
||||||
} else {
|
} else {
|
||||||
buildConfigField "boolean", "LEAKCANARY", "false"
|
buildConfigField "boolean", "LEAKCANARY", "false"
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate Kotlin code for the Fenix Glean metrics.
|
// Generate Kotlin code for the Fenix Glean metrics.
|
||||||
|
@ -670,13 +678,16 @@ dependencies {
|
||||||
implementation ComponentsDependencies.androidx_fragment
|
implementation ComponentsDependencies.androidx_fragment
|
||||||
implementation FenixDependencies.androidx_navigation_fragment
|
implementation FenixDependencies.androidx_navigation_fragment
|
||||||
implementation FenixDependencies.androidx_navigation_ui
|
implementation FenixDependencies.androidx_navigation_ui
|
||||||
|
implementation ComponentsDependencies.androidx_compose_navigation
|
||||||
implementation ComponentsDependencies.androidx_recyclerview
|
implementation ComponentsDependencies.androidx_recyclerview
|
||||||
|
|
||||||
implementation ComponentsDependencies.androidx_lifecycle_common
|
implementation ComponentsDependencies.androidx_lifecycle_common
|
||||||
implementation ComponentsDependencies.androidx_lifecycle_livedata
|
implementation ComponentsDependencies.androidx_lifecycle_livedata
|
||||||
implementation ComponentsDependencies.androidx_lifecycle_process
|
implementation ComponentsDependencies.androidx_lifecycle_process
|
||||||
implementation ComponentsDependencies.androidx_lifecycle_runtime
|
implementation ComponentsDependencies.androidx_lifecycle_runtime
|
||||||
|
|
||||||
implementation ComponentsDependencies.androidx_lifecycle_viewmodel
|
implementation ComponentsDependencies.androidx_lifecycle_viewmodel
|
||||||
|
implementation ComponentsDependencies.androidx_lifecycle_service
|
||||||
|
|
||||||
implementation ComponentsDependencies.androidx_core
|
implementation ComponentsDependencies.androidx_core
|
||||||
implementation ComponentsDependencies.androidx_core_ktx
|
implementation ComponentsDependencies.androidx_core_ktx
|
||||||
implementation FenixDependencies.androidx_core_splashscreen
|
implementation FenixDependencies.androidx_core_splashscreen
|
||||||
|
@ -837,25 +848,6 @@ tasks.register('printVariants') {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.register('buildTranslationArray') {
|
|
||||||
// This isn't running as a task, instead the array is build when the gradle file is parsed.
|
|
||||||
// https://github.com/mozilla-mobile/fenix/issues/14175
|
|
||||||
def foundLocales = new StringBuilder()
|
|
||||||
foundLocales.append("new String[]{")
|
|
||||||
|
|
||||||
fileTree("src/main/res").visit { FileVisitDetails details ->
|
|
||||||
if (details.file.path.endsWith("${File.separator}strings.xml")) {
|
|
||||||
def languageCode = details.file.parent.tokenize(File.separator).last().replaceAll('values-', '').replaceAll('-r', '-')
|
|
||||||
languageCode = (languageCode == "values") ? "en-US" : languageCode
|
|
||||||
foundLocales.append("\"").append(languageCode).append("\"").append(",")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foundLocales.append("}")
|
|
||||||
def foundLocalesString = foundLocales.toString().replaceAll(',}', '}')
|
|
||||||
android.defaultConfig.buildConfigField "String[]", "SUPPORTED_LOCALE_ARRAY", foundLocalesString
|
|
||||||
}
|
|
||||||
|
|
||||||
afterEvaluate {
|
afterEvaluate {
|
||||||
|
|
||||||
// Format test output. Ported from AC #2401
|
// Format test output. Ported from AC #2401
|
||||||
|
@ -922,5 +914,24 @@ android.applicationVariants.configureEach { variant ->
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def getSupportedLocales() {
|
||||||
|
// This isn't running as a task, instead the array is build when the gradle file is parsed.
|
||||||
|
// https://github.com/mozilla-mobile/fenix/issues/14175
|
||||||
|
def foundLocales = new StringBuilder()
|
||||||
|
foundLocales.append("new String[]{")
|
||||||
|
|
||||||
|
fileTree("src/main/res").visit { FileVisitDetails details ->
|
||||||
|
if (details.file.path.endsWith("${File.separator}strings.xml")) {
|
||||||
|
def languageCode = details.file.parent.tokenize(File.separator).last().replaceAll('values-', '').replaceAll('-r', '-')
|
||||||
|
languageCode = (languageCode == "values") ? "en-US" : languageCode
|
||||||
|
foundLocales.append("\"").append(languageCode).append("\"").append(",")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foundLocales.append("}")
|
||||||
|
def foundLocalesString = foundLocales.toString().replaceAll(',}', '}')
|
||||||
|
return foundLocalesString
|
||||||
|
}
|
||||||
|
|
||||||
// Enable expiration by major version.
|
// Enable expiration by major version.
|
||||||
ext.gleanExpireByVersion = 26
|
ext.gleanExpireByVersion = 26
|
||||||
|
|
2610
app/github.txt
|
@ -135,12 +135,12 @@ events:
|
||||||
description: |
|
description: |
|
||||||
A string containing the name of the item the user tapped. These items
|
A string containing the name of the item the user tapped. These items
|
||||||
include:
|
include:
|
||||||
add_to_homescreen, add_to_top_sites, addons_manager, back, bookmark,
|
add_to_homescreen, add_to_top_sites, addons_manager, back, back_long_press,
|
||||||
bookmarks, desktop_view_off, desktop_view_on, downloads,
|
bookmark, bookmarks, desktop_view_off, desktop_view_on, downloads,
|
||||||
find_in_page, forward, history, new_tab, open_in_app, open_in_fenix,
|
find_in_page, forward, forward_long_press, history, new_tab, open_in_app,
|
||||||
quit, reader_mode_appearance, reload, remove_from_top_sites,
|
open_in_fenix, quit, reader_mode_appearance, reload, remove_from_top_sites,
|
||||||
save_to_collection, set_default_browser, settings, share, stop,
|
save_to_collection, set_default_browser, settings, share, stop,
|
||||||
sync_account, and print_content.
|
sync_account, translate and print_content.
|
||||||
type: string
|
type: string
|
||||||
bugs:
|
bugs:
|
||||||
- https://github.com/mozilla-mobile/fenix/issues/1024
|
- https://github.com/mozilla-mobile/fenix/issues/1024
|
||||||
|
@ -475,6 +475,23 @@ events:
|
||||||
notification_emails:
|
notification_emails:
|
||||||
- android-probes@mozilla.com
|
- android-probes@mozilla.com
|
||||||
expires: never
|
expires: never
|
||||||
|
browser_toolbar_security_indicator_tapped:
|
||||||
|
type: event
|
||||||
|
description: |
|
||||||
|
An event that indicates that a user has tapped
|
||||||
|
the security indicator icon (at the start of the domain name).
|
||||||
|
bugs:
|
||||||
|
- https://bugzilla.mozilla.org/show_bug.cgi?id=1869664
|
||||||
|
data_reviews:
|
||||||
|
- https://github.com/mozilla-mobile/firefox-android/pull/5019#issuecomment-1876329933
|
||||||
|
data_sensitivity:
|
||||||
|
- interaction
|
||||||
|
notification_emails:
|
||||||
|
- android-probes@mozilla.com
|
||||||
|
expires: never
|
||||||
|
metadata:
|
||||||
|
tags:
|
||||||
|
- Toolbar
|
||||||
browser_toolbar_erase_tapped:
|
browser_toolbar_erase_tapped:
|
||||||
type: event
|
type: event
|
||||||
description: |
|
description: |
|
||||||
|
@ -489,6 +506,22 @@ events:
|
||||||
notification_emails:
|
notification_emails:
|
||||||
- android-probes@mozilla.com
|
- android-probes@mozilla.com
|
||||||
expires: never
|
expires: never
|
||||||
|
browser_toolbar_input_cleared:
|
||||||
|
type: event
|
||||||
|
description: |
|
||||||
|
A user pressed the circle cross icon, clearing the input in the toolbar.
|
||||||
|
bugs:
|
||||||
|
- https://bugzilla.mozilla.org/show_bug.cgi?id=1869664
|
||||||
|
data_reviews:
|
||||||
|
- https://github.com/mozilla-mobile/firefox-android/pull/5019#issuecomment-1876329933
|
||||||
|
data_sensitivity:
|
||||||
|
- interaction
|
||||||
|
notification_emails:
|
||||||
|
- android-probes@mozilla.com
|
||||||
|
expires: never
|
||||||
|
metadata:
|
||||||
|
tags:
|
||||||
|
- Toolbar
|
||||||
browser_toolbar_qr_scan_tapped:
|
browser_toolbar_qr_scan_tapped:
|
||||||
type: event
|
type: event
|
||||||
description: |
|
description: |
|
||||||
|
@ -506,6 +539,22 @@ events:
|
||||||
metadata:
|
metadata:
|
||||||
tags:
|
tags:
|
||||||
- Toolbar
|
- Toolbar
|
||||||
|
browser_toolbar_qr_scan_completed:
|
||||||
|
type: event
|
||||||
|
description: |
|
||||||
|
An event that indicates that a QR code has been scanned successfully.
|
||||||
|
bugs:
|
||||||
|
- https://bugzilla.mozilla.org/show_bug.cgi?id=1869664
|
||||||
|
data_reviews:
|
||||||
|
- https://github.com/mozilla-mobile/firefox-android/pull/5019#issuecomment-1876329933
|
||||||
|
data_sensitivity:
|
||||||
|
- interaction
|
||||||
|
notification_emails:
|
||||||
|
- android-probes@mozilla.com
|
||||||
|
expires: never
|
||||||
|
metadata:
|
||||||
|
tags:
|
||||||
|
- Toolbar
|
||||||
toolbar_tab_swipe:
|
toolbar_tab_swipe:
|
||||||
type: event
|
type: event
|
||||||
description: |
|
description: |
|
||||||
|
|
|
@ -214,14 +214,6 @@ features:
|
||||||
type: Boolean
|
type: Boolean
|
||||||
default: true
|
default: true
|
||||||
|
|
||||||
extensions-process:
|
|
||||||
description: A feature to rollout the extensions process.
|
|
||||||
variables:
|
|
||||||
enabled:
|
|
||||||
description: If true, the extensions process is enabled.
|
|
||||||
type: Boolean
|
|
||||||
default: true
|
|
||||||
|
|
||||||
growth-data:
|
growth-data:
|
||||||
description: A feature measuring campaign growth data
|
description: A feature measuring campaign growth data
|
||||||
variables:
|
variables:
|
||||||
|
@ -341,6 +333,26 @@ features:
|
||||||
type: Map<String, String>
|
type: Map<String, String>
|
||||||
default: {}
|
default: {}
|
||||||
|
|
||||||
|
fx-strong-password:
|
||||||
|
description: A feature that provides a generated strong password on sign up.
|
||||||
|
variables:
|
||||||
|
enabled:
|
||||||
|
description: >
|
||||||
|
When the feature is enabled and Firefox receives a Login event with an
|
||||||
|
empty saved logins list, a suggested strong password prompt will be shown,
|
||||||
|
allowing the user to use the generated password to fill in the password field
|
||||||
|
for the new account that will be created. When the feature is disabled,
|
||||||
|
there won't be any prompt displayed that would allow using a generated password.
|
||||||
|
type: Boolean
|
||||||
|
default: false
|
||||||
|
defaults:
|
||||||
|
- channel: developer
|
||||||
|
value:
|
||||||
|
enabled: true
|
||||||
|
- channel: nightly
|
||||||
|
value:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
fx-suggest:
|
fx-suggest:
|
||||||
description: A feature that provides Firefox Suggest search suggestions.
|
description: A feature that provides Firefox Suggest search suggestions.
|
||||||
variables:
|
variables:
|
||||||
|
@ -361,6 +373,17 @@ features:
|
||||||
- channel: nightly
|
- channel: nightly
|
||||||
value:
|
value:
|
||||||
enabled: true
|
enabled: true
|
||||||
|
|
||||||
|
nimbus-is-ready:
|
||||||
|
description: >
|
||||||
|
A feature that provides the number of Nimbus is_ready events to send
|
||||||
|
when Nimbus finishes launching.
|
||||||
|
variables:
|
||||||
|
event-count:
|
||||||
|
description: The number of events that should be sent.
|
||||||
|
type: Int
|
||||||
|
default: 1
|
||||||
|
|
||||||
types:
|
types:
|
||||||
objects: {}
|
objects: {}
|
||||||
|
|
||||||
|
|
|
@ -25,8 +25,7 @@ features:
|
||||||
card-type: default-browser
|
card-type: default-browser
|
||||||
title: juno_onboarding_default_browser_title_nimbus_2
|
title: juno_onboarding_default_browser_title_nimbus_2
|
||||||
ordering: 10
|
ordering: 10
|
||||||
body: juno_onboarding_default_browser_description_nimbus_2
|
body: juno_onboarding_default_browser_description_nimbus_3
|
||||||
link-text: juno_onboarding_default_browser_description_link_text
|
|
||||||
image-res: ic_onboarding_welcome
|
image-res: ic_onboarding_welcome
|
||||||
primary-button-label: juno_onboarding_default_browser_positive_button
|
primary-button-label: juno_onboarding_default_browser_positive_button
|
||||||
secondary-button-label: juno_onboarding_default_browser_negative_button
|
secondary-button-label: juno_onboarding_default_browser_negative_button
|
||||||
|
@ -83,13 +82,6 @@ objects:
|
||||||
description: The message text displayed to the user. May contain linkable text.
|
description: The message text displayed to the user. May contain linkable text.
|
||||||
# This should never be defaulted.
|
# This should never be defaulted.
|
||||||
default: ""
|
default: ""
|
||||||
link-text:
|
|
||||||
type: Option<Text>
|
|
||||||
description: >
|
|
||||||
The text to link from the body text. This should match the linkable text from the body text exactly.
|
|
||||||
e.g. body: This is a policy link
|
|
||||||
link-text: policy link
|
|
||||||
default: null
|
|
||||||
image-res:
|
image-res:
|
||||||
type: Image
|
type: Image
|
||||||
description: The resource id of the image to be displayed.
|
description: The resource id of the image to be displayed.
|
||||||
|
|
|
@ -92,7 +92,6 @@ class AppRequestInterceptor(
|
||||||
|
|
||||||
// This method is the only difference from the production code.
|
// This method is the only difference from the production code.
|
||||||
// Otherwise the code should be kept identical
|
// Otherwise the code should be kept identical
|
||||||
@Suppress("LongParameterList")
|
|
||||||
private fun interceptFxaRequest(
|
private fun interceptFxaRequest(
|
||||||
engineSession: EngineSession,
|
engineSession: EngineSession,
|
||||||
uri: String,
|
uri: String,
|
||||||
|
|
|
@ -146,11 +146,12 @@
|
||||||
},
|
},
|
||||||
"jinja2": {
|
"jinja2": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852",
|
"sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa",
|
||||||
"sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"
|
"sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"
|
||||||
],
|
],
|
||||||
|
"index": "pypi",
|
||||||
"markers": "python_version >= '3.7'",
|
"markers": "python_version >= '3.7'",
|
||||||
"version": "==3.1.2"
|
"version": "==3.1.3"
|
||||||
},
|
},
|
||||||
"markupsafe": {
|
"markupsafe": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
|
|
@ -6,19 +6,15 @@ package org.mozilla.fenix.extensions
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import mozilla.components.concept.engine.EngineSession
|
import mozilla.components.concept.engine.EngineSession
|
||||||
import org.json.JSONObject
|
|
||||||
import org.junit.Assert.assertFalse
|
|
||||||
import org.junit.Assert.assertTrue
|
import org.junit.Assert.assertTrue
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.mozilla.experiments.nimbus.HardcodedNimbusFeatures
|
|
||||||
import org.mozilla.fenix.ext.components
|
import org.mozilla.fenix.ext.components
|
||||||
import org.mozilla.fenix.gecko.GeckoProvider
|
import org.mozilla.fenix.gecko.GeckoProvider
|
||||||
import org.mozilla.fenix.helpers.TestHelper
|
import org.mozilla.fenix.helpers.TestHelper
|
||||||
import org.mozilla.fenix.nimbus.FxNimbus
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instrumentation test for verifying that the extensions process can be controlled with Nimbus.
|
* Instrumentation test for verifying that the extensions process is enabled unconditionally.
|
||||||
*/
|
*/
|
||||||
class ExtensionProcessTest {
|
class ExtensionProcessTest {
|
||||||
private lateinit var context: Context
|
private lateinit var context: Context
|
||||||
|
@ -27,49 +23,12 @@ class ExtensionProcessTest {
|
||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
context = TestHelper.appContext
|
context = TestHelper.appContext
|
||||||
policy =
|
policy = context.components.core.trackingProtectionPolicyFactory.createTrackingProtectionPolicy()
|
||||||
context.components.core.trackingProtectionPolicyFactory.createTrackingProtectionPolicy()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun test_extension_process_can_be_enabled_by_nimbus() {
|
fun test_extension_process_is_enabled() {
|
||||||
val hardcodedNimbus = HardcodedNimbusFeatures(
|
|
||||||
context,
|
|
||||||
"extensions-process" to JSONObject(
|
|
||||||
"""
|
|
||||||
{
|
|
||||||
"enabled": true
|
|
||||||
}
|
|
||||||
""".trimIndent(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
hardcodedNimbus.connectWith(FxNimbus)
|
|
||||||
|
|
||||||
val runtime = GeckoProvider.createRuntimeSettings(context, policy)
|
val runtime = GeckoProvider.createRuntimeSettings(context, policy)
|
||||||
|
|
||||||
assertTrue(FxNimbus.features.extensionsProcess.value().enabled)
|
|
||||||
assertTrue(runtime.extensionsProcessEnabled!!)
|
assertTrue(runtime.extensionsProcessEnabled!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun test_extension_process_can_be_disabled_by_nimbus() {
|
|
||||||
val hardcodedNimbus = HardcodedNimbusFeatures(
|
|
||||||
context,
|
|
||||||
"extensions-process" to JSONObject(
|
|
||||||
"""
|
|
||||||
{
|
|
||||||
"enabled": false
|
|
||||||
}
|
|
||||||
""".trimIndent(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
hardcodedNimbus.connectWith(FxNimbus)
|
|
||||||
|
|
||||||
val runtime = GeckoProvider.createRuntimeSettings(context, policy)
|
|
||||||
|
|
||||||
assertFalse(FxNimbus.features.extensionsProcess.value().enabled)
|
|
||||||
assertFalse(runtime.extensionsProcessEnabled!!)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -249,6 +249,7 @@ object AppAndSystemHelper {
|
||||||
* Runs on Debug variant as we don't want to adjust Release permission manifests
|
* Runs on Debug variant as we don't want to adjust Release permission manifests
|
||||||
* Runs the test in its testBlock.
|
* Runs the test in its testBlock.
|
||||||
* Cleans up and sets the default locale after it's done.
|
* Cleans up and sets the default locale after it's done.
|
||||||
|
* As a safety measure, always add the resetSystemLocaleToEnUS() method in the tearDown method of your Class.
|
||||||
*/
|
*/
|
||||||
fun runWithSystemLocaleChanged(locale: Locale, testRule: ActivityTestRule<HomeActivity>, testBlock: () -> Unit) {
|
fun runWithSystemLocaleChanged(locale: Locale, testRule: ActivityTestRule<HomeActivity>, testBlock: () -> Unit) {
|
||||||
if (Config.channel.isDebug) {
|
if (Config.channel.isDebug) {
|
||||||
|
@ -274,6 +275,21 @@ object AppAndSystemHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the default language of the entire device back to EN-US.
|
||||||
|
* In case of a test instrumentation crash, the finally statement in the
|
||||||
|
* runWithSystemLocaleChanged(locale: Locale) method, will not be reached.
|
||||||
|
* Add this method inside the tearDown method of your test class, where the above method is used.
|
||||||
|
* Note: If set inside the ActivityTestRule's afterActivityFinished() method, this also won't work,
|
||||||
|
* as the methods inside it are not always executed: https://github.com/android/android-test/issues/498
|
||||||
|
*/
|
||||||
|
fun resetSystemLocaleToEnUS() {
|
||||||
|
if (Locale.getDefault() != Locale.US) {
|
||||||
|
Log.i(TAG, "Resetting system locale to EN US")
|
||||||
|
setSystemLocale(Locale.US)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Changes the default language of the entire device, not just the app.
|
* Changes the default language of the entire device, not just the app.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -22,6 +22,8 @@ import org.junit.Assert
|
||||||
import org.mozilla.fenix.ext.components
|
import org.mozilla.fenix.ext.components
|
||||||
import org.mozilla.fenix.helpers.TestHelper.mDevice
|
import org.mozilla.fenix.helpers.TestHelper.mDevice
|
||||||
import org.mozilla.fenix.utils.IntentUtils
|
import org.mozilla.fenix.utils.IntentUtils
|
||||||
|
import java.time.LocalDate
|
||||||
|
import java.time.LocalTime
|
||||||
|
|
||||||
object DataGenerationHelper {
|
object DataGenerationHelper {
|
||||||
val appContext: Context = InstrumentationRegistry.getInstrumentation().targetContext
|
val appContext: Context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||||
|
@ -75,6 +77,28 @@ object DataGenerationHelper {
|
||||||
clipBoard.setPrimaryClip(clipData)
|
clipBoard.setPrimaryClip(clipData)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a date and time placeholder string for sponsored Fx suggest links.
|
||||||
|
* The format of the datetime is YYYYMMDDHH, where YYYY is the four-digit year,
|
||||||
|
* MM is the two-digit month, DD is the two-digit day, and HH is the two-digit hour.
|
||||||
|
* Single-digit months, days, and hours are padded with a leading zero to ensure
|
||||||
|
* the correct format. For example, a date and time of January 10, 2024, at 3 PM
|
||||||
|
* would be represented as "2024011015".
|
||||||
|
*
|
||||||
|
* @return A string representing the current date and time in the specified format.
|
||||||
|
*/
|
||||||
|
fun getSponsoredFxSuggestPlaceHolder(): String {
|
||||||
|
val currentDate = LocalDate.now()
|
||||||
|
val currentTime = LocalTime.now()
|
||||||
|
|
||||||
|
val currentDay = currentDate.dayOfMonth.toString().padStart(2, '0')
|
||||||
|
val currentMonth = currentDate.monthValue.toString().padStart(2, '0')
|
||||||
|
val currentYear = currentDate.year.toString()
|
||||||
|
val currentHour = currentTime.hour.toString().padStart(2, '0')
|
||||||
|
|
||||||
|
return currentYear + currentMonth + currentDay + currentHour
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns sponsored shortcut title based on the index.
|
* Returns sponsored shortcut title based on the index.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -82,6 +82,11 @@ interface FeatureSettingsHelper {
|
||||||
*/
|
*/
|
||||||
var composeTopSitesEnabled: Boolean
|
var composeTopSitesEnabled: Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable or disable translations flow.
|
||||||
|
*/
|
||||||
|
var isTranslationsEnabled: Boolean
|
||||||
|
|
||||||
fun applyFlagUpdates()
|
fun applyFlagUpdates()
|
||||||
|
|
||||||
fun resetAllFeatureFlags()
|
fun resetAllFeatureFlags()
|
||||||
|
|
|
@ -37,6 +37,7 @@ class FeatureSettingsHelperDelegate() : FeatureSettingsHelper {
|
||||||
etpPolicy = getETPPolicy(settings),
|
etpPolicy = getETPPolicy(settings),
|
||||||
tabsTrayRewriteEnabled = settings.enableTabsTrayToCompose,
|
tabsTrayRewriteEnabled = settings.enableTabsTrayToCompose,
|
||||||
composeTopSitesEnabled = settings.enableComposeTopSites,
|
composeTopSitesEnabled = settings.enableComposeTopSites,
|
||||||
|
translationsEnabled = settings.enableTranslations,
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -66,6 +67,7 @@ class FeatureSettingsHelperDelegate() : FeatureSettingsHelper {
|
||||||
override var etpPolicy: ETPPolicy by updatedFeatureFlags::etpPolicy
|
override var etpPolicy: ETPPolicy by updatedFeatureFlags::etpPolicy
|
||||||
override var tabsTrayRewriteEnabled: Boolean by updatedFeatureFlags::tabsTrayRewriteEnabled
|
override var tabsTrayRewriteEnabled: Boolean by updatedFeatureFlags::tabsTrayRewriteEnabled
|
||||||
override var composeTopSitesEnabled: Boolean by updatedFeatureFlags::composeTopSitesEnabled
|
override var composeTopSitesEnabled: Boolean by updatedFeatureFlags::composeTopSitesEnabled
|
||||||
|
override var isTranslationsEnabled: Boolean by updatedFeatureFlags::translationsEnabled
|
||||||
|
|
||||||
override fun applyFlagUpdates() {
|
override fun applyFlagUpdates() {
|
||||||
applyFeatureFlags(updatedFeatureFlags)
|
applyFeatureFlags(updatedFeatureFlags)
|
||||||
|
@ -91,6 +93,7 @@ class FeatureSettingsHelperDelegate() : FeatureSettingsHelper {
|
||||||
settings.shouldShowOpenInAppBanner = featureFlags.isOpenInAppBannerEnabled
|
settings.shouldShowOpenInAppBanner = featureFlags.isOpenInAppBannerEnabled
|
||||||
settings.enableTabsTrayToCompose = featureFlags.tabsTrayRewriteEnabled
|
settings.enableTabsTrayToCompose = featureFlags.tabsTrayRewriteEnabled
|
||||||
settings.enableComposeTopSites = featureFlags.composeTopSitesEnabled
|
settings.enableComposeTopSites = featureFlags.composeTopSitesEnabled
|
||||||
|
settings.enableTranslations = featureFlags.translationsEnabled
|
||||||
setETPPolicy(featureFlags.etpPolicy)
|
setETPPolicy(featureFlags.etpPolicy)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -110,6 +113,7 @@ private data class FeatureFlags(
|
||||||
var etpPolicy: ETPPolicy,
|
var etpPolicy: ETPPolicy,
|
||||||
var tabsTrayRewriteEnabled: Boolean,
|
var tabsTrayRewriteEnabled: Boolean,
|
||||||
var composeTopSitesEnabled: Boolean,
|
var composeTopSitesEnabled: Boolean,
|
||||||
|
var translationsEnabled: Boolean,
|
||||||
)
|
)
|
||||||
|
|
||||||
internal fun getETPPolicy(settings: Settings): ETPPolicy {
|
internal fun getETPPolicy(settings: Settings): ETPPolicy {
|
||||||
|
|
|
@ -165,6 +165,7 @@ class HomeActivityIntentTestRule internal constructor(
|
||||||
etpPolicy: ETPPolicy = getETPPolicy(settings),
|
etpPolicy: ETPPolicy = getETPPolicy(settings),
|
||||||
tabsTrayRewriteEnabled: Boolean = false,
|
tabsTrayRewriteEnabled: Boolean = false,
|
||||||
composeTopSitesEnabled: Boolean = false,
|
composeTopSitesEnabled: Boolean = false,
|
||||||
|
translationsEnabled: Boolean = false,
|
||||||
) : this(initialTouchMode, launchActivity, skipOnboarding) {
|
) : this(initialTouchMode, launchActivity, skipOnboarding) {
|
||||||
this.isHomeOnboardingDialogEnabled = isHomeOnboardingDialogEnabled
|
this.isHomeOnboardingDialogEnabled = isHomeOnboardingDialogEnabled
|
||||||
this.isPocketEnabled = isPocketEnabled
|
this.isPocketEnabled = isPocketEnabled
|
||||||
|
@ -179,6 +180,7 @@ class HomeActivityIntentTestRule internal constructor(
|
||||||
this.etpPolicy = etpPolicy
|
this.etpPolicy = etpPolicy
|
||||||
this.tabsTrayRewriteEnabled = tabsTrayRewriteEnabled
|
this.tabsTrayRewriteEnabled = tabsTrayRewriteEnabled
|
||||||
this.composeTopSitesEnabled = composeTopSitesEnabled
|
this.composeTopSitesEnabled = composeTopSitesEnabled
|
||||||
|
this.isTranslationsEnabled = translationsEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
private val longTapUserPreference = getLongPressTimeout()
|
private val longTapUserPreference = getLongPressTimeout()
|
||||||
|
@ -260,6 +262,7 @@ class HomeActivityIntentTestRule internal constructor(
|
||||||
skipOnboarding: Boolean = false,
|
skipOnboarding: Boolean = false,
|
||||||
tabsTrayRewriteEnabled: Boolean = false,
|
tabsTrayRewriteEnabled: Boolean = false,
|
||||||
composeTopSitesEnabled: Boolean = false,
|
composeTopSitesEnabled: Boolean = false,
|
||||||
|
translationsEnabled: Boolean = false,
|
||||||
) = HomeActivityIntentTestRule(
|
) = HomeActivityIntentTestRule(
|
||||||
initialTouchMode = initialTouchMode,
|
initialTouchMode = initialTouchMode,
|
||||||
launchActivity = launchActivity,
|
launchActivity = launchActivity,
|
||||||
|
@ -271,6 +274,7 @@ class HomeActivityIntentTestRule internal constructor(
|
||||||
isWallpaperOnboardingEnabled = false,
|
isWallpaperOnboardingEnabled = false,
|
||||||
isOpenInAppBannerEnabled = false,
|
isOpenInAppBannerEnabled = false,
|
||||||
composeTopSitesEnabled = composeTopSitesEnabled,
|
composeTopSitesEnabled = composeTopSitesEnabled,
|
||||||
|
translationsEnabled = translationsEnabled,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
package org.mozilla.fenix.helpers
|
package org.mozilla.fenix.helpers
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
import androidx.test.espresso.IdlingResourceTimeoutException
|
import androidx.test.espresso.IdlingResourceTimeoutException
|
||||||
import androidx.test.espresso.NoMatchingViewException
|
import androidx.test.espresso.NoMatchingViewException
|
||||||
import androidx.test.uiautomator.UiObjectNotFoundException
|
import androidx.test.uiautomator.UiObjectNotFoundException
|
||||||
|
@ -13,7 +14,9 @@ import org.junit.rules.TestRule
|
||||||
import org.junit.runner.Description
|
import org.junit.runner.Description
|
||||||
import org.junit.runners.model.Statement
|
import org.junit.runners.model.Statement
|
||||||
import org.mozilla.fenix.components.PermissionStorage
|
import org.mozilla.fenix.components.PermissionStorage
|
||||||
|
import org.mozilla.fenix.ext.settings
|
||||||
import org.mozilla.fenix.helpers.AppAndSystemHelper.setNetworkEnabled
|
import org.mozilla.fenix.helpers.AppAndSystemHelper.setNetworkEnabled
|
||||||
|
import org.mozilla.fenix.helpers.Constants.TAG
|
||||||
import org.mozilla.fenix.helpers.IdlingResourceHelper.unregisterAllIdlingResources
|
import org.mozilla.fenix.helpers.IdlingResourceHelper.unregisterAllIdlingResources
|
||||||
import org.mozilla.fenix.helpers.TestHelper.appContext
|
import org.mozilla.fenix.helpers.TestHelper.appContext
|
||||||
|
|
||||||
|
@ -32,68 +35,83 @@ class RetryTestRule(private val retryCount: Int = 5) : TestRule {
|
||||||
return statement {
|
return statement {
|
||||||
for (i in 1..retryCount) {
|
for (i in 1..retryCount) {
|
||||||
try {
|
try {
|
||||||
|
Log.i(TAG, "RetryTestRule: Started try #$i.")
|
||||||
base.evaluate()
|
base.evaluate()
|
||||||
break
|
break
|
||||||
} catch (t: AssertionError) {
|
} catch (t: AssertionError) {
|
||||||
setNetworkEnabled(true)
|
setNetworkEnabled(true)
|
||||||
unregisterAllIdlingResources()
|
unregisterAllIdlingResources()
|
||||||
runBlocking {
|
runBlocking {
|
||||||
|
appContext.settings().alwaysOpenTheHomepageWhenOpeningTheApp = true
|
||||||
permissionStorage.deleteAllSitePermissions()
|
permissionStorage.deleteAllSitePermissions()
|
||||||
}
|
}
|
||||||
if (i == retryCount) {
|
if (i == retryCount) {
|
||||||
|
Log.i(TAG, "RetryTestRule: Max numbers of retries reached.")
|
||||||
throw t
|
throw t
|
||||||
}
|
}
|
||||||
} catch (t: AssertionFailedError) {
|
} catch (t: AssertionFailedError) {
|
||||||
unregisterAllIdlingResources()
|
unregisterAllIdlingResources()
|
||||||
runBlocking {
|
runBlocking {
|
||||||
|
appContext.settings().alwaysOpenTheHomepageWhenOpeningTheApp = true
|
||||||
permissionStorage.deleteAllSitePermissions()
|
permissionStorage.deleteAllSitePermissions()
|
||||||
}
|
}
|
||||||
if (i == retryCount) {
|
if (i == retryCount) {
|
||||||
|
Log.i(TAG, "RetryTestRule: Max numbers of retries reached.")
|
||||||
throw t
|
throw t
|
||||||
}
|
}
|
||||||
} catch (t: UiObjectNotFoundException) {
|
} catch (t: UiObjectNotFoundException) {
|
||||||
setNetworkEnabled(true)
|
setNetworkEnabled(true)
|
||||||
unregisterAllIdlingResources()
|
unregisterAllIdlingResources()
|
||||||
runBlocking {
|
runBlocking {
|
||||||
|
appContext.settings().alwaysOpenTheHomepageWhenOpeningTheApp = true
|
||||||
permissionStorage.deleteAllSitePermissions()
|
permissionStorage.deleteAllSitePermissions()
|
||||||
}
|
}
|
||||||
if (i == retryCount) {
|
if (i == retryCount) {
|
||||||
|
Log.i(TAG, "RetryTestRule: Max numbers of retries reached.")
|
||||||
throw t
|
throw t
|
||||||
}
|
}
|
||||||
} catch (t: NoMatchingViewException) {
|
} catch (t: NoMatchingViewException) {
|
||||||
setNetworkEnabled(true)
|
setNetworkEnabled(true)
|
||||||
unregisterAllIdlingResources()
|
unregisterAllIdlingResources()
|
||||||
runBlocking {
|
runBlocking {
|
||||||
|
appContext.settings().alwaysOpenTheHomepageWhenOpeningTheApp = true
|
||||||
permissionStorage.deleteAllSitePermissions()
|
permissionStorage.deleteAllSitePermissions()
|
||||||
}
|
}
|
||||||
if (i == retryCount) {
|
if (i == retryCount) {
|
||||||
|
Log.i(TAG, "RetryTestRule: Max numbers of retries reached.")
|
||||||
throw t
|
throw t
|
||||||
}
|
}
|
||||||
} catch (t: IdlingResourceTimeoutException) {
|
} catch (t: IdlingResourceTimeoutException) {
|
||||||
setNetworkEnabled(true)
|
setNetworkEnabled(true)
|
||||||
unregisterAllIdlingResources()
|
unregisterAllIdlingResources()
|
||||||
runBlocking {
|
runBlocking {
|
||||||
|
appContext.settings().alwaysOpenTheHomepageWhenOpeningTheApp = true
|
||||||
permissionStorage.deleteAllSitePermissions()
|
permissionStorage.deleteAllSitePermissions()
|
||||||
}
|
}
|
||||||
if (i == retryCount) {
|
if (i == retryCount) {
|
||||||
|
Log.i(TAG, "RetryTestRule: Max numbers of retries reached.")
|
||||||
throw t
|
throw t
|
||||||
}
|
}
|
||||||
} catch (t: RuntimeException) {
|
} catch (t: RuntimeException) {
|
||||||
setNetworkEnabled(true)
|
setNetworkEnabled(true)
|
||||||
unregisterAllIdlingResources()
|
unregisterAllIdlingResources()
|
||||||
runBlocking {
|
runBlocking {
|
||||||
|
appContext.settings().alwaysOpenTheHomepageWhenOpeningTheApp = true
|
||||||
permissionStorage.deleteAllSitePermissions()
|
permissionStorage.deleteAllSitePermissions()
|
||||||
}
|
}
|
||||||
if (i == retryCount) {
|
if (i == retryCount) {
|
||||||
|
Log.i(TAG, "RetryTestRule: Max numbers of retries reached.")
|
||||||
throw t
|
throw t
|
||||||
}
|
}
|
||||||
} catch (t: NullPointerException) {
|
} catch (t: NullPointerException) {
|
||||||
setNetworkEnabled(true)
|
setNetworkEnabled(true)
|
||||||
unregisterAllIdlingResources()
|
unregisterAllIdlingResources()
|
||||||
runBlocking {
|
runBlocking {
|
||||||
|
appContext.settings().alwaysOpenTheHomepageWhenOpeningTheApp = true
|
||||||
permissionStorage.deleteAllSitePermissions()
|
permissionStorage.deleteAllSitePermissions()
|
||||||
}
|
}
|
||||||
if (i == retryCount) {
|
if (i == retryCount) {
|
||||||
|
Log.i(TAG, "RetryTestRule: Max numbers of retries reached.")
|
||||||
throw t
|
throw t
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,10 +45,12 @@ class OnboardingMapperTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun showNotificationTrue_showAddWidgetFalse_pagesToDisplay_returnsSortedListOfAllConvertedPages_withoutAddWidgetPage() {
|
fun showNotificationTrue_showAddWidgetFalse_pagesToDisplay_returnsSortedListOfAllConvertedPages_withoutAddWidgetPage() {
|
||||||
val expected = listOf(defaultBrowserPageUiData, syncPageUiData, notificationPageUiData)
|
val expected = listOf(defaultBrowserPageUiDataWithPrivacyCaption, syncPageUiData, notificationPageUiData)
|
||||||
assertEquals(
|
assertEquals(
|
||||||
expected,
|
expected,
|
||||||
unsortedAllKnownCardData.toPageUiData(
|
unsortedAllKnownCardData.toPageUiData(
|
||||||
|
privacyCaption = privacyCaption,
|
||||||
|
showDefaultBrowserPage = true,
|
||||||
showNotificationPage = true,
|
showNotificationPage = true,
|
||||||
showAddWidgetPage = false,
|
showAddWidgetPage = false,
|
||||||
jexlConditions = jexlConditions,
|
jexlConditions = jexlConditions,
|
||||||
|
@ -59,10 +61,12 @@ class OnboardingMapperTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun showNotificationFalse_showAddWidgetFalse_pagesToDisplay_returnsSortedListOfConvertedPages_withoutNotificationPage_and_addWidgetPage() {
|
fun showNotificationFalse_showAddWidgetFalse_pagesToDisplay_returnsSortedListOfConvertedPages_withoutNotificationPage_and_addWidgetPage() {
|
||||||
val expected = listOf(defaultBrowserPageUiData, syncPageUiData)
|
val expected = listOf(defaultBrowserPageUiDataWithPrivacyCaption, syncPageUiData)
|
||||||
assertEquals(
|
assertEquals(
|
||||||
expected,
|
expected,
|
||||||
unsortedAllKnownCardData.toPageUiData(
|
unsortedAllKnownCardData.toPageUiData(
|
||||||
|
privacyCaption = privacyCaption,
|
||||||
|
showDefaultBrowserPage = true,
|
||||||
showNotificationPage = false,
|
showNotificationPage = false,
|
||||||
showAddWidgetPage = false,
|
showAddWidgetPage = false,
|
||||||
jexlConditions = jexlConditions,
|
jexlConditions = jexlConditions,
|
||||||
|
@ -72,11 +76,76 @@ class OnboardingMapperTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun showNotificationFalse_showAddWidgetTrue_pagesToDisplay_returnsSortedListOfAllConvertedPages_withoutNotificationPage() {
|
fun pagesToDisplay_returnsSortedListOfConvertedPages_withPrivacyCaption_alwaysOnFirstPage() {
|
||||||
val expected = listOf(defaultBrowserPageUiData, addSearchWidgetPageUiData, syncPageUiData)
|
var result = unsortedAllKnownCardData.toPageUiData(
|
||||||
|
privacyCaption = privacyCaption,
|
||||||
|
showDefaultBrowserPage = false,
|
||||||
|
showNotificationPage = false,
|
||||||
|
showAddWidgetPage = false,
|
||||||
|
jexlConditions = jexlConditions,
|
||||||
|
func = evalFunction,
|
||||||
|
)
|
||||||
|
assertEquals(result[0].privacyCaption, privacyCaption)
|
||||||
|
|
||||||
|
result = unsortedAllKnownCardData.toPageUiData(
|
||||||
|
privacyCaption = privacyCaption,
|
||||||
|
showDefaultBrowserPage = false,
|
||||||
|
showNotificationPage = true,
|
||||||
|
showAddWidgetPage = false,
|
||||||
|
jexlConditions = jexlConditions,
|
||||||
|
func = evalFunction,
|
||||||
|
)
|
||||||
|
assertEquals(result[0].privacyCaption, privacyCaption)
|
||||||
|
assertEquals(result[1].privacyCaption, null)
|
||||||
|
|
||||||
|
result = unsortedAllKnownCardData.toPageUiData(
|
||||||
|
privacyCaption = privacyCaption,
|
||||||
|
showDefaultBrowserPage = true,
|
||||||
|
showNotificationPage = true,
|
||||||
|
showAddWidgetPage = false,
|
||||||
|
jexlConditions = jexlConditions,
|
||||||
|
func = evalFunction,
|
||||||
|
)
|
||||||
|
assertEquals(result[0].privacyCaption, privacyCaption)
|
||||||
|
assertEquals(result[1].privacyCaption, null)
|
||||||
|
assertEquals(result[2].privacyCaption, null)
|
||||||
|
|
||||||
|
result = unsortedAllKnownCardData.toPageUiData(
|
||||||
|
privacyCaption = privacyCaption,
|
||||||
|
showDefaultBrowserPage = false,
|
||||||
|
showNotificationPage = false,
|
||||||
|
showAddWidgetPage = true,
|
||||||
|
jexlConditions = jexlConditions,
|
||||||
|
func = evalFunction,
|
||||||
|
)
|
||||||
|
assertEquals(result[0].privacyCaption, privacyCaption)
|
||||||
|
assertEquals(result[1].privacyCaption, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun showDefaultBrowserPageFalse_showNotificationFalse_showAddWidgetTrue_pagesToDisplay_returnsSortedListOfAllConvertedPages() {
|
||||||
|
val expected = listOf(addSearchWidgetPageUiDataWithPrivacyCaption, syncPageUiData)
|
||||||
assertEquals(
|
assertEquals(
|
||||||
expected,
|
expected,
|
||||||
unsortedAllKnownCardData.toPageUiData(
|
unsortedAllKnownCardData.toPageUiData(
|
||||||
|
privacyCaption = privacyCaption,
|
||||||
|
showDefaultBrowserPage = false,
|
||||||
|
showNotificationPage = false,
|
||||||
|
showAddWidgetPage = true,
|
||||||
|
jexlConditions = jexlConditions,
|
||||||
|
func = evalFunction,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun showNotificationFalse_showAddWidgetTrue_pagesToDisplay_returnsSortedListOfAllConvertedPages_withoutNotificationPage() {
|
||||||
|
val expected = listOf(defaultBrowserPageUiDataWithPrivacyCaption, addSearchWidgetPageUiData, syncPageUiData)
|
||||||
|
assertEquals(
|
||||||
|
expected,
|
||||||
|
unsortedAllKnownCardData.toPageUiData(
|
||||||
|
privacyCaption = privacyCaption,
|
||||||
|
showDefaultBrowserPage = true,
|
||||||
showNotificationPage = false,
|
showNotificationPage = false,
|
||||||
showAddWidgetPage = true,
|
showAddWidgetPage = true,
|
||||||
jexlConditions = jexlConditions,
|
jexlConditions = jexlConditions,
|
||||||
|
@ -88,7 +157,7 @@ class OnboardingMapperTest {
|
||||||
@Test
|
@Test
|
||||||
fun showNotificationTrue_and_showAddWidgetTrue_pagesToDisplay_returnsSortedListOfConvertedPages() {
|
fun showNotificationTrue_and_showAddWidgetTrue_pagesToDisplay_returnsSortedListOfConvertedPages() {
|
||||||
val expected = listOf(
|
val expected = listOf(
|
||||||
defaultBrowserPageUiData,
|
defaultBrowserPageUiDataWithPrivacyCaption,
|
||||||
addSearchWidgetPageUiData,
|
addSearchWidgetPageUiData,
|
||||||
syncPageUiData,
|
syncPageUiData,
|
||||||
notificationPageUiData,
|
notificationPageUiData,
|
||||||
|
@ -96,6 +165,8 @@ class OnboardingMapperTest {
|
||||||
assertEquals(
|
assertEquals(
|
||||||
expected,
|
expected,
|
||||||
unsortedAllKnownCardData.toPageUiData(
|
unsortedAllKnownCardData.toPageUiData(
|
||||||
|
privacyCaption = privacyCaption,
|
||||||
|
showDefaultBrowserPage = true,
|
||||||
showNotificationPage = true,
|
showNotificationPage = true,
|
||||||
showAddWidgetPage = true,
|
showAddWidgetPage = true,
|
||||||
jexlConditions = jexlConditions,
|
jexlConditions = jexlConditions,
|
||||||
|
@ -107,11 +178,13 @@ class OnboardingMapperTest {
|
||||||
@Test
|
@Test
|
||||||
fun cardConditionsMatchJexlConditions_shouldDisplayCard_returnsConvertedPage() {
|
fun cardConditionsMatchJexlConditions_shouldDisplayCard_returnsConvertedPage() {
|
||||||
val jexlConditions = mapOf("ALWAYS" to "true", "NEVER" to "false")
|
val jexlConditions = mapOf("ALWAYS" to "true", "NEVER" to "false")
|
||||||
val expected = listOf(defaultBrowserPageUiData)
|
val expected = listOf(defaultBrowserPageUiDataWithPrivacyCaption)
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
expected,
|
expected,
|
||||||
listOf(defaultBrowserCardData).toPageUiData(
|
listOf(defaultBrowserCardData).toPageUiData(
|
||||||
|
privacyCaption = privacyCaption,
|
||||||
|
showDefaultBrowserPage = true,
|
||||||
showNotificationPage = false,
|
showNotificationPage = false,
|
||||||
showAddWidgetPage = false,
|
showAddWidgetPage = false,
|
||||||
jexlConditions = jexlConditions,
|
jexlConditions = jexlConditions,
|
||||||
|
@ -128,6 +201,8 @@ class OnboardingMapperTest {
|
||||||
assertEquals(
|
assertEquals(
|
||||||
expected,
|
expected,
|
||||||
listOf(addSearchWidgetCardDataNoConditions).toPageUiData(
|
listOf(addSearchWidgetCardDataNoConditions).toPageUiData(
|
||||||
|
privacyCaption = privacyCaption,
|
||||||
|
showDefaultBrowserPage = true,
|
||||||
showNotificationPage = false,
|
showNotificationPage = false,
|
||||||
showAddWidgetPage = false,
|
showAddWidgetPage = false,
|
||||||
jexlConditions = jexlConditions,
|
jexlConditions = jexlConditions,
|
||||||
|
@ -144,6 +219,8 @@ class OnboardingMapperTest {
|
||||||
assertEquals(
|
assertEquals(
|
||||||
expected,
|
expected,
|
||||||
listOf(defaultBrowserCardData).toPageUiData(
|
listOf(defaultBrowserCardData).toPageUiData(
|
||||||
|
privacyCaption = privacyCaption,
|
||||||
|
showDefaultBrowserPage = true,
|
||||||
showNotificationPage = false,
|
showNotificationPage = false,
|
||||||
showAddWidgetPage = false,
|
showAddWidgetPage = false,
|
||||||
jexlConditions = jexlConditions,
|
jexlConditions = jexlConditions,
|
||||||
|
@ -155,11 +232,13 @@ class OnboardingMapperTest {
|
||||||
@Test
|
@Test
|
||||||
fun prerequisitesMatchJexlConditions_shouldDisplayCard_returnsConvertedPage() {
|
fun prerequisitesMatchJexlConditions_shouldDisplayCard_returnsConvertedPage() {
|
||||||
val jexlConditions = mapOf("ALWAYS" to "true")
|
val jexlConditions = mapOf("ALWAYS" to "true")
|
||||||
val expected = listOf(defaultBrowserPageUiData)
|
val expected = listOf(defaultBrowserPageUiDataWithPrivacyCaption)
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
expected,
|
expected,
|
||||||
listOf(defaultBrowserCardData).toPageUiData(
|
listOf(defaultBrowserCardData).toPageUiData(
|
||||||
|
privacyCaption = privacyCaption,
|
||||||
|
showDefaultBrowserPage = true,
|
||||||
showNotificationPage = false,
|
showNotificationPage = false,
|
||||||
showAddWidgetPage = false,
|
showAddWidgetPage = false,
|
||||||
jexlConditions = jexlConditions,
|
jexlConditions = jexlConditions,
|
||||||
|
@ -176,6 +255,8 @@ class OnboardingMapperTest {
|
||||||
assertEquals(
|
assertEquals(
|
||||||
expected,
|
expected,
|
||||||
listOf(defaultBrowserCardData).toPageUiData(
|
listOf(defaultBrowserCardData).toPageUiData(
|
||||||
|
privacyCaption = privacyCaption,
|
||||||
|
showDefaultBrowserPage = true,
|
||||||
showNotificationPage = false,
|
showNotificationPage = false,
|
||||||
showAddWidgetPage = false,
|
showAddWidgetPage = false,
|
||||||
jexlConditions = jexlConditions,
|
jexlConditions = jexlConditions,
|
||||||
|
@ -192,6 +273,8 @@ class OnboardingMapperTest {
|
||||||
assertEquals(
|
assertEquals(
|
||||||
expected,
|
expected,
|
||||||
listOf(addSearchWidgetCardDataNoConditions).toPageUiData(
|
listOf(addSearchWidgetCardDataNoConditions).toPageUiData(
|
||||||
|
privacyCaption = privacyCaption,
|
||||||
|
showDefaultBrowserPage = true,
|
||||||
showNotificationPage = false,
|
showNotificationPage = false,
|
||||||
showAddWidgetPage = false,
|
showAddWidgetPage = false,
|
||||||
jexlConditions = jexlConditions,
|
jexlConditions = jexlConditions,
|
||||||
|
@ -203,11 +286,13 @@ class OnboardingMapperTest {
|
||||||
@Test
|
@Test
|
||||||
fun noDisqualifiers_shouldDisplayCard_returnsConvertedPage() {
|
fun noDisqualifiers_shouldDisplayCard_returnsConvertedPage() {
|
||||||
val jexlConditions = mapOf("ALWAYS" to "true", "NEVER" to "false")
|
val jexlConditions = mapOf("ALWAYS" to "true", "NEVER" to "false")
|
||||||
val expected = listOf(defaultBrowserPageUiData)
|
val expected = listOf(defaultBrowserPageUiDataWithPrivacyCaption)
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
expected,
|
expected,
|
||||||
listOf(defaultBrowserCardDataNoDisqualifiers).toPageUiData(
|
listOf(defaultBrowserCardDataNoDisqualifiers).toPageUiData(
|
||||||
|
privacyCaption = privacyCaption,
|
||||||
|
showDefaultBrowserPage = true,
|
||||||
showNotificationPage = false,
|
showNotificationPage = false,
|
||||||
showAddWidgetPage = false,
|
showAddWidgetPage = false,
|
||||||
jexlConditions = jexlConditions,
|
jexlConditions = jexlConditions,
|
||||||
|
@ -219,11 +304,13 @@ class OnboardingMapperTest {
|
||||||
@Test
|
@Test
|
||||||
fun disqualifiersMatchJexlConditions_shouldDisplayCard_returnsConvertedPage() {
|
fun disqualifiersMatchJexlConditions_shouldDisplayCard_returnsConvertedPage() {
|
||||||
val jexlConditions = mapOf("NEVER" to "false")
|
val jexlConditions = mapOf("NEVER" to "false")
|
||||||
val expected = listOf(syncPageUiData)
|
val expected = listOf(syncPageUiDataWithPrivacyCaption)
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
expected,
|
expected,
|
||||||
listOf(syncCardData).toPageUiData(
|
listOf(syncCardData).toPageUiData(
|
||||||
|
privacyCaption = privacyCaption,
|
||||||
|
showDefaultBrowserPage = true,
|
||||||
showNotificationPage = false,
|
showNotificationPage = false,
|
||||||
showAddWidgetPage = false,
|
showAddWidgetPage = false,
|
||||||
jexlConditions = jexlConditions,
|
jexlConditions = jexlConditions,
|
||||||
|
@ -240,6 +327,8 @@ class OnboardingMapperTest {
|
||||||
assertEquals(
|
assertEquals(
|
||||||
expected,
|
expected,
|
||||||
listOf(notificationCardData).toPageUiData(
|
listOf(notificationCardData).toPageUiData(
|
||||||
|
privacyCaption = privacyCaption,
|
||||||
|
showDefaultBrowserPage = true,
|
||||||
showNotificationPage = false,
|
showNotificationPage = false,
|
||||||
showAddWidgetPage = false,
|
showAddWidgetPage = false,
|
||||||
jexlConditions = jexlConditions,
|
jexlConditions = jexlConditions,
|
||||||
|
@ -251,11 +340,13 @@ class OnboardingMapperTest {
|
||||||
@Test
|
@Test
|
||||||
fun noPrerequisites_shouldDisplayCard_returnsConvertedPage() {
|
fun noPrerequisites_shouldDisplayCard_returnsConvertedPage() {
|
||||||
val jexlConditions = mapOf("ALWAYS" to "true", "NEVER" to "false")
|
val jexlConditions = mapOf("ALWAYS" to "true", "NEVER" to "false")
|
||||||
val expected = listOf(syncPageUiData)
|
val expected = listOf(syncPageUiDataWithPrivacyCaption)
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
expected,
|
expected,
|
||||||
listOf(syncCardData).toPageUiData(
|
listOf(syncCardData).toPageUiData(
|
||||||
|
privacyCaption = privacyCaption,
|
||||||
|
showDefaultBrowserPage = true,
|
||||||
showNotificationPage = false,
|
showNotificationPage = false,
|
||||||
showAddWidgetPage = false,
|
showAddWidgetPage = false,
|
||||||
jexlConditions = jexlConditions,
|
jexlConditions = jexlConditions,
|
||||||
|
@ -264,24 +355,34 @@ class OnboardingMapperTest {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
val privacyCaption: Caption = mockk(relaxed = true)
|
||||||
|
|
||||||
private val defaultBrowserPageUiData = OnboardingPageUiData(
|
private val defaultBrowserPageUiDataWithPrivacyCaption = OnboardingPageUiData(
|
||||||
type = OnboardingPageUiData.Type.DEFAULT_BROWSER,
|
type = OnboardingPageUiData.Type.DEFAULT_BROWSER,
|
||||||
imageRes = R.drawable.ic_onboarding_welcome,
|
imageRes = R.drawable.ic_onboarding_welcome,
|
||||||
title = "default browser title",
|
title = "default browser title",
|
||||||
description = "default browser body with link text",
|
description = "default browser body",
|
||||||
linkText = "link text",
|
|
||||||
primaryButtonLabel = "default browser primary button text",
|
primaryButtonLabel = "default browser primary button text",
|
||||||
secondaryButtonLabel = "default browser secondary button text",
|
secondaryButtonLabel = "default browser secondary button text",
|
||||||
|
privacyCaption = privacyCaption,
|
||||||
)
|
)
|
||||||
private val addSearchWidgetPageUiData = OnboardingPageUiData(
|
private val addSearchWidgetPageUiData = OnboardingPageUiData(
|
||||||
type = OnboardingPageUiData.Type.ADD_SEARCH_WIDGET,
|
type = OnboardingPageUiData.Type.ADD_SEARCH_WIDGET,
|
||||||
imageRes = R.drawable.ic_onboarding_search_widget,
|
imageRes = R.drawable.ic_onboarding_search_widget,
|
||||||
title = "add search widget title",
|
title = "add search widget title",
|
||||||
description = "add search widget body with link text",
|
description = "add search widget body",
|
||||||
linkText = "link text",
|
|
||||||
primaryButtonLabel = "add search widget primary button text",
|
primaryButtonLabel = "add search widget primary button text",
|
||||||
secondaryButtonLabel = "add search widget secondary button text",
|
secondaryButtonLabel = "add search widget secondary button text",
|
||||||
|
privacyCaption = null,
|
||||||
|
)
|
||||||
|
private val addSearchWidgetPageUiDataWithPrivacyCaption = OnboardingPageUiData(
|
||||||
|
type = OnboardingPageUiData.Type.ADD_SEARCH_WIDGET,
|
||||||
|
imageRes = R.drawable.ic_onboarding_search_widget,
|
||||||
|
title = "add search widget title",
|
||||||
|
description = "add search widget body",
|
||||||
|
primaryButtonLabel = "add search widget primary button text",
|
||||||
|
secondaryButtonLabel = "add search widget secondary button text",
|
||||||
|
privacyCaption = privacyCaption,
|
||||||
)
|
)
|
||||||
private val syncPageUiData = OnboardingPageUiData(
|
private val syncPageUiData = OnboardingPageUiData(
|
||||||
type = OnboardingPageUiData.Type.SYNC_SIGN_IN,
|
type = OnboardingPageUiData.Type.SYNC_SIGN_IN,
|
||||||
|
@ -290,6 +391,16 @@ private val syncPageUiData = OnboardingPageUiData(
|
||||||
description = "sync body",
|
description = "sync body",
|
||||||
primaryButtonLabel = "sync primary button text",
|
primaryButtonLabel = "sync primary button text",
|
||||||
secondaryButtonLabel = "sync secondary button text",
|
secondaryButtonLabel = "sync secondary button text",
|
||||||
|
privacyCaption = null,
|
||||||
|
)
|
||||||
|
private val syncPageUiDataWithPrivacyCaption = OnboardingPageUiData(
|
||||||
|
type = OnboardingPageUiData.Type.SYNC_SIGN_IN,
|
||||||
|
imageRes = R.drawable.ic_onboarding_sync,
|
||||||
|
title = "sync title",
|
||||||
|
description = "sync body",
|
||||||
|
primaryButtonLabel = "sync primary button text",
|
||||||
|
secondaryButtonLabel = "sync secondary button text",
|
||||||
|
privacyCaption = privacyCaption,
|
||||||
)
|
)
|
||||||
private val notificationPageUiData = OnboardingPageUiData(
|
private val notificationPageUiData = OnboardingPageUiData(
|
||||||
type = OnboardingPageUiData.Type.NOTIFICATION_PERMISSION,
|
type = OnboardingPageUiData.Type.NOTIFICATION_PERMISSION,
|
||||||
|
@ -298,14 +409,14 @@ private val notificationPageUiData = OnboardingPageUiData(
|
||||||
description = "notification body",
|
description = "notification body",
|
||||||
primaryButtonLabel = "notification primary button text",
|
primaryButtonLabel = "notification primary button text",
|
||||||
secondaryButtonLabel = "notification secondary button text",
|
secondaryButtonLabel = "notification secondary button text",
|
||||||
|
privacyCaption = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
private val defaultBrowserCardData = OnboardingCardData(
|
private val defaultBrowserCardData = OnboardingCardData(
|
||||||
cardType = OnboardingCardType.DEFAULT_BROWSER,
|
cardType = OnboardingCardType.DEFAULT_BROWSER,
|
||||||
imageRes = R.drawable.ic_onboarding_welcome,
|
imageRes = R.drawable.ic_onboarding_welcome,
|
||||||
title = StringHolder(null, "default browser title"),
|
title = StringHolder(null, "default browser title"),
|
||||||
body = StringHolder(null, "default browser body with link text"),
|
body = StringHolder(null, "default browser body"),
|
||||||
linkText = StringHolder(null, "link text"),
|
|
||||||
primaryButtonLabel = StringHolder(null, "default browser primary button text"),
|
primaryButtonLabel = StringHolder(null, "default browser primary button text"),
|
||||||
secondaryButtonLabel = StringHolder(null, "default browser secondary button text"),
|
secondaryButtonLabel = StringHolder(null, "default browser secondary button text"),
|
||||||
ordering = 10,
|
ordering = 10,
|
||||||
|
@ -317,8 +428,7 @@ private val defaultBrowserCardDataNoDisqualifiers = OnboardingCardData(
|
||||||
cardType = OnboardingCardType.DEFAULT_BROWSER,
|
cardType = OnboardingCardType.DEFAULT_BROWSER,
|
||||||
imageRes = R.drawable.ic_onboarding_welcome,
|
imageRes = R.drawable.ic_onboarding_welcome,
|
||||||
title = StringHolder(null, "default browser title"),
|
title = StringHolder(null, "default browser title"),
|
||||||
body = StringHolder(null, "default browser body with link text"),
|
body = StringHolder(null, "default browser body"),
|
||||||
linkText = StringHolder(null, "link text"),
|
|
||||||
primaryButtonLabel = StringHolder(null, "default browser primary button text"),
|
primaryButtonLabel = StringHolder(null, "default browser primary button text"),
|
||||||
secondaryButtonLabel = StringHolder(null, "default browser secondary button text"),
|
secondaryButtonLabel = StringHolder(null, "default browser secondary button text"),
|
||||||
ordering = 10,
|
ordering = 10,
|
||||||
|
@ -330,8 +440,7 @@ private val addSearchWidgetCardDataNoConditions = OnboardingCardData(
|
||||||
cardType = OnboardingCardType.ADD_SEARCH_WIDGET,
|
cardType = OnboardingCardType.ADD_SEARCH_WIDGET,
|
||||||
imageRes = R.drawable.ic_onboarding_search_widget,
|
imageRes = R.drawable.ic_onboarding_search_widget,
|
||||||
title = StringHolder(null, "add search widget title"),
|
title = StringHolder(null, "add search widget title"),
|
||||||
body = StringHolder(null, "add search widget body with link text"),
|
body = StringHolder(null, "add search widget body"),
|
||||||
linkText = StringHolder(null, "link text"),
|
|
||||||
primaryButtonLabel = StringHolder(null, "add search widget primary button text"),
|
primaryButtonLabel = StringHolder(null, "add search widget primary button text"),
|
||||||
secondaryButtonLabel = StringHolder(null, "add search widget secondary button text"),
|
secondaryButtonLabel = StringHolder(null, "add search widget secondary button text"),
|
||||||
ordering = 15,
|
ordering = 15,
|
||||||
|
@ -343,8 +452,7 @@ private val addSearchWidgetCardData = OnboardingCardData(
|
||||||
cardType = OnboardingCardType.ADD_SEARCH_WIDGET,
|
cardType = OnboardingCardType.ADD_SEARCH_WIDGET,
|
||||||
imageRes = R.drawable.ic_onboarding_search_widget,
|
imageRes = R.drawable.ic_onboarding_search_widget,
|
||||||
title = StringHolder(null, "add search widget title"),
|
title = StringHolder(null, "add search widget title"),
|
||||||
body = StringHolder(null, "add search widget body with link text"),
|
body = StringHolder(null, "add search widget body"),
|
||||||
linkText = StringHolder(null, "link text"),
|
|
||||||
primaryButtonLabel = StringHolder(null, "add search widget primary button text"),
|
primaryButtonLabel = StringHolder(null, "add search widget primary button text"),
|
||||||
secondaryButtonLabel = StringHolder(null, "add search widget secondary button text"),
|
secondaryButtonLabel = StringHolder(null, "add search widget secondary button text"),
|
||||||
ordering = 15,
|
ordering = 15,
|
||||||
|
|
|
@ -7,7 +7,6 @@ package org.mozilla.fenix.ui
|
||||||
import okhttp3.mockwebserver.MockWebServer
|
import okhttp3.mockwebserver.MockWebServer
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Ignore
|
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.mozilla.fenix.customannotations.SmokeTest
|
import org.mozilla.fenix.customannotations.SmokeTest
|
||||||
|
@ -265,7 +264,6 @@ class AddressAutofillTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/1836849
|
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/1836849
|
||||||
@Ignore("Failing, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1814032")
|
|
||||||
@Test
|
@Test
|
||||||
fun verifyMultipleAddressesSelectionTest() {
|
fun verifyMultipleAddressesSelectionTest() {
|
||||||
val addressFormPage =
|
val addressFormPage =
|
||||||
|
|
|
@ -14,6 +14,7 @@ import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.mozilla.fenix.customannotations.SmokeTest
|
import org.mozilla.fenix.customannotations.SmokeTest
|
||||||
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
|
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
|
||||||
|
import org.mozilla.fenix.helpers.AppAndSystemHelper.resetSystemLocaleToEnUS
|
||||||
import org.mozilla.fenix.helpers.AppAndSystemHelper.runWithSystemLocaleChanged
|
import org.mozilla.fenix.helpers.AppAndSystemHelper.runWithSystemLocaleChanged
|
||||||
import org.mozilla.fenix.helpers.HomeActivityTestRule
|
import org.mozilla.fenix.helpers.HomeActivityTestRule
|
||||||
import org.mozilla.fenix.helpers.TestAssetHelper
|
import org.mozilla.fenix.helpers.TestAssetHelper
|
||||||
|
@ -54,6 +55,7 @@ class ComposeNavigationToolbarTest {
|
||||||
@After
|
@After
|
||||||
fun tearDown() {
|
fun tearDown() {
|
||||||
mockWebServer.shutdown()
|
mockWebServer.shutdown()
|
||||||
|
resetSystemLocaleToEnUS()
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/987326
|
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/987326
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
/* 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.ui
|
||||||
|
|
||||||
|
import androidx.core.net.toUri
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
import org.mozilla.fenix.customannotations.SmokeTest
|
||||||
|
import org.mozilla.fenix.ext.settings
|
||||||
|
import org.mozilla.fenix.helpers.AppAndSystemHelper.runWithCondition
|
||||||
|
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
|
||||||
|
import org.mozilla.fenix.helpers.TestHelper.appContext
|
||||||
|
import org.mozilla.fenix.ui.robots.homeScreen
|
||||||
|
import org.mozilla.fenix.ui.robots.navigationToolbar
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for verifying the new Cookie banner blocker option and functionality.
|
||||||
|
*/
|
||||||
|
class CookieBannerBlockerTest {
|
||||||
|
@get:Rule
|
||||||
|
val activityTestRule = HomeActivityIntentTestRule.withDefaultSettingsOverrides(skipOnboarding = true)
|
||||||
|
|
||||||
|
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/2419260
|
||||||
|
@SmokeTest
|
||||||
|
@Test
|
||||||
|
fun verifyCookieBannerBlockerSettingsOptionTest() {
|
||||||
|
runWithCondition(appContext.settings().shouldUseCookieBannerPrivateMode) {
|
||||||
|
homeScreen {
|
||||||
|
}.openThreeDotMenu {
|
||||||
|
}.openSettings {
|
||||||
|
verifyCookieBannerBlockerButton(enabled = true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/2419273
|
||||||
|
@SmokeTest
|
||||||
|
@Test
|
||||||
|
fun verifyCFRAfterBlockingTheCookieBanner() {
|
||||||
|
runWithCondition(appContext.settings().shouldUseCookieBannerPrivateMode) {
|
||||||
|
homeScreen {
|
||||||
|
}.togglePrivateBrowsingMode()
|
||||||
|
|
||||||
|
navigationToolbar {
|
||||||
|
}.enterURLAndEnterToBrowser("voetbal24.be".toUri()) {
|
||||||
|
waitForPageToLoad()
|
||||||
|
verifyCookieBannerExists(exists = false)
|
||||||
|
verifyCookieBannerBlockerCFRExists(exists = true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,11 +5,13 @@
|
||||||
package org.mozilla.fenix.ui
|
package org.mozilla.fenix.ui
|
||||||
|
|
||||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
||||||
|
import org.junit.Ignore
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.mozilla.fenix.customannotations.SmokeTest
|
import org.mozilla.fenix.customannotations.SmokeTest
|
||||||
import org.mozilla.fenix.ext.settings
|
import org.mozilla.fenix.ext.settings
|
||||||
import org.mozilla.fenix.helpers.AppAndSystemHelper
|
import org.mozilla.fenix.helpers.AppAndSystemHelper.runWithCondition
|
||||||
|
import org.mozilla.fenix.helpers.DataGenerationHelper.getSponsoredFxSuggestPlaceHolder
|
||||||
import org.mozilla.fenix.helpers.HomeActivityTestRule
|
import org.mozilla.fenix.helpers.HomeActivityTestRule
|
||||||
import org.mozilla.fenix.helpers.TestHelper
|
import org.mozilla.fenix.helpers.TestHelper
|
||||||
import org.mozilla.fenix.ui.robots.navigationToolbar
|
import org.mozilla.fenix.ui.robots.navigationToolbar
|
||||||
|
@ -20,6 +22,7 @@ import org.mozilla.fenix.ui.robots.navigationToolbar
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class FirefoxSuggestTest {
|
class FirefoxSuggestTest {
|
||||||
|
|
||||||
@get:Rule
|
@get:Rule
|
||||||
val activityTestRule = AndroidComposeTestRule(
|
val activityTestRule = AndroidComposeTestRule(
|
||||||
HomeActivityTestRule(
|
HomeActivityTestRule(
|
||||||
|
@ -33,91 +36,156 @@ class FirefoxSuggestTest {
|
||||||
),
|
),
|
||||||
) { it.activity }
|
) { it.activity }
|
||||||
|
|
||||||
|
private val sponsoredKeyWords: Map<String, List<String>> =
|
||||||
|
mapOf(
|
||||||
|
"Amazon" to
|
||||||
|
listOf(
|
||||||
|
"Amazon.com - Official Site",
|
||||||
|
"amazon.com/?tag=admarketus-20&ref=pd_sl_924ab4435c5a5c23aa2804307ee0669ab36f88caee841ce51d1f2ecb&mfadid=adm",
|
||||||
|
),
|
||||||
|
"Nike" to
|
||||||
|
listOf(
|
||||||
|
"Nike.com - Official Site",
|
||||||
|
"nike.com/?cp=16423867261_search_318370984us128${getSponsoredFxSuggestPlaceHolder()}&mfadid=adm",
|
||||||
|
),
|
||||||
|
"Macy" to listOf(
|
||||||
|
"macys.com - Official Site",
|
||||||
|
"macys.com/?cm_mmc=Google_AdMarketPlace-_-Privacy_Instant%20Suggest-_-319101130_Broad-_-kclickid__kenshoo_clickid_&m_sc=sem&m_sb=Admarketplace&m_tp=Search&m_ac=Admarketplace&m_ag=Instant%20Suggest&m_cn=Privacy&m_pi=kclickid__kenshoo_clickid__319101130us1201${getSponsoredFxSuggestPlaceHolder()}&mfadid=adm",
|
||||||
|
),
|
||||||
|
"Spanx" to listOf(
|
||||||
|
"SPANX® - Official Site",
|
||||||
|
"spanx.com/?utm_source=admarketplace&utm_medium=cpc&utm_campaign=privacy&utm_content=319093361us1202${getSponsoredFxSuggestPlaceHolder()}&mfadid=adm",
|
||||||
|
),
|
||||||
|
"Bloom" to listOf(
|
||||||
|
"Bloomingdales.com - Official Site",
|
||||||
|
"bloomingdales.com/?cm_mmc=Admarketplace-_-Privacy-_-Privacy-_-privacy%20instant%20suggest-_-319093353us1228${getSponsoredFxSuggestPlaceHolder()}-_-kclickid__kenshoo_clickid_&mfadid=adm",
|
||||||
|
),
|
||||||
|
"Groupon" to listOf(
|
||||||
|
"groupon.com - Discover & Save!",
|
||||||
|
"groupon.com/?utm_source=google&utm_medium=cpc&utm_campaign=us_dt_sea_ggl_txt_smp_sr_cbp_ch1_nbr_k*{keyword}_m*{match-type}_d*ADMRKT_319093357us1279${getSponsoredFxSuggestPlaceHolder()}&mfadid=adm",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
private val sponsoredKeyWord = sponsoredKeyWords.keys.random()
|
||||||
|
|
||||||
|
private val nonSponsoredKeyWords: Map<String, List<String>> =
|
||||||
|
mapOf(
|
||||||
|
"Marvel" to
|
||||||
|
listOf(
|
||||||
|
"Wikipedia - Marvel Cinematic Universe",
|
||||||
|
"wikipedia.org/wiki/Marvel_Cinematic_Universe",
|
||||||
|
),
|
||||||
|
"Apple" to
|
||||||
|
listOf(
|
||||||
|
"Wikipedia - Apple Inc.",
|
||||||
|
"wikipedia.org/wiki/Apple_Inc",
|
||||||
|
),
|
||||||
|
"Africa" to listOf(
|
||||||
|
"Wikipedia - African Union",
|
||||||
|
"wikipedia.org/wiki/African_Union",
|
||||||
|
),
|
||||||
|
"Ultimate" to listOf(
|
||||||
|
"Wikipedia - Ultimate Fighting Championship",
|
||||||
|
"wikipedia.org/wiki/Ultimate_Fighting_Championship",
|
||||||
|
),
|
||||||
|
"Youtube" to listOf(
|
||||||
|
"Wikipedia - YouTube",
|
||||||
|
"wikipedia.org/wiki/YouTube",
|
||||||
|
),
|
||||||
|
"Fifa" to listOf(
|
||||||
|
"Wikipedia - FIFA World Cup",
|
||||||
|
"en.m.wikipedia.org/wiki/FIFA_World_Cup",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
private val nonSponsoredKeyWord = nonSponsoredKeyWords.keys.random()
|
||||||
|
|
||||||
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/2348361
|
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/2348361
|
||||||
|
@Ignore("Failing, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1874831")
|
||||||
@SmokeTest
|
@SmokeTest
|
||||||
@Test
|
@Test
|
||||||
fun verifyFirefoxSuggestSponsoredSearchResultsTest() {
|
fun verifyFirefoxSuggestSponsoredSearchResultsTest() {
|
||||||
AppAndSystemHelper.runWithCondition(TestHelper.appContext.settings().enableFxSuggest) {
|
runWithCondition(TestHelper.appContext.settings().enableFxSuggest) {
|
||||||
navigationToolbar {
|
navigationToolbar {
|
||||||
}.clickUrlbar {
|
}.clickUrlbar {
|
||||||
typeSearch(searchTerm = "Amazon")
|
typeSearch(searchTerm = sponsoredKeyWord)
|
||||||
verifySearchEngineSuggestionResults(
|
verifySearchEngineSuggestionResults(
|
||||||
rule = activityTestRule,
|
rule = activityTestRule,
|
||||||
searchSuggestions = arrayOf(
|
searchSuggestions = arrayOf(
|
||||||
"Firefox Suggest",
|
"Firefox Suggest",
|
||||||
"Amazon.com - Official Site",
|
sponsoredKeyWords.getValue(sponsoredKeyWord)[0],
|
||||||
"Sponsored",
|
"Sponsored",
|
||||||
),
|
),
|
||||||
searchTerm = "Amazon",
|
searchTerm = sponsoredKeyWord,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/2348362
|
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/2348362
|
||||||
|
@Ignore("Failing, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1874831")
|
||||||
@Test
|
@Test
|
||||||
fun verifyFirefoxSuggestSponsoredSearchResultsWithPartialKeywordTest() {
|
fun verifyFirefoxSuggestSponsoredSearchResultsWithPartialKeywordTest() {
|
||||||
AppAndSystemHelper.runWithCondition(TestHelper.appContext.settings().enableFxSuggest) {
|
runWithCondition(TestHelper.appContext.settings().enableFxSuggest) {
|
||||||
navigationToolbar {
|
navigationToolbar {
|
||||||
}.clickUrlbar {
|
}.clickUrlbar {
|
||||||
typeSearch(searchTerm = "Amaz")
|
typeSearch(searchTerm = sponsoredKeyWord.dropLast(1))
|
||||||
verifySearchEngineSuggestionResults(
|
verifySearchEngineSuggestionResults(
|
||||||
rule = activityTestRule,
|
rule = activityTestRule,
|
||||||
searchSuggestions = arrayOf(
|
searchSuggestions = arrayOf(
|
||||||
"Firefox Suggest",
|
"Firefox Suggest",
|
||||||
"Amazon.com - Official Site",
|
sponsoredKeyWords.getValue(sponsoredKeyWord)[0],
|
||||||
"Sponsored",
|
"Sponsored",
|
||||||
),
|
),
|
||||||
searchTerm = "Amaz",
|
searchTerm = sponsoredKeyWord.dropLast(1),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/2348363
|
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/2348363
|
||||||
|
@Ignore("Failing, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1874831")
|
||||||
@Test
|
@Test
|
||||||
fun openFirefoxSuggestSponsoredSearchResultsTest() {
|
fun openFirefoxSuggestSponsoredSearchResultsTest() {
|
||||||
AppAndSystemHelper.runWithCondition(TestHelper.appContext.settings().enableFxSuggest) {
|
runWithCondition(TestHelper.appContext.settings().enableFxSuggest) {
|
||||||
navigationToolbar {
|
navigationToolbar {
|
||||||
}.clickUrlbar {
|
}.clickUrlbar {
|
||||||
typeSearch(searchTerm = "Amazon")
|
typeSearch(searchTerm = sponsoredKeyWord)
|
||||||
verifySearchEngineSuggestionResults(
|
verifySearchEngineSuggestionResults(
|
||||||
rule = activityTestRule,
|
rule = activityTestRule,
|
||||||
searchSuggestions = arrayOf(
|
searchSuggestions = arrayOf(
|
||||||
"Firefox Suggest",
|
"Firefox Suggest",
|
||||||
"Amazon.com - Official Site",
|
sponsoredKeyWords.getValue(sponsoredKeyWord)[0],
|
||||||
"Sponsored",
|
"Sponsored",
|
||||||
),
|
),
|
||||||
searchTerm = "Amazon",
|
searchTerm = sponsoredKeyWord,
|
||||||
)
|
|
||||||
}.clickSearchSuggestion("Amazon.com - Official Site") {
|
|
||||||
waitForPageToLoad()
|
|
||||||
verifyUrl(
|
|
||||||
"amazon.com/?tag=admarketus-20&ref=pd_sl_924ab4435c5a5c23aa2804307ee0669ab36f88caee841ce51d1f2ecb&mfadid=adm",
|
|
||||||
)
|
)
|
||||||
|
}.clickSearchSuggestion(sponsoredKeyWords.getValue(sponsoredKeyWord)[0]) {
|
||||||
|
verifyUrl(sponsoredKeyWords.getValue(sponsoredKeyWord)[1])
|
||||||
verifyTabCounter("1")
|
verifyTabCounter("1")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/2348369
|
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/2348369
|
||||||
|
@Ignore("Failing, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1874831")
|
||||||
@Test
|
@Test
|
||||||
fun verifyFirefoxSuggestSponsoredSearchResultsWithEditedKeywordTest() {
|
fun verifyFirefoxSuggestSponsoredSearchResultsWithEditedKeywordTest() {
|
||||||
AppAndSystemHelper.runWithCondition(TestHelper.appContext.settings().enableFxSuggest) {
|
runWithCondition(TestHelper.appContext.settings().enableFxSuggest) {
|
||||||
navigationToolbar {
|
navigationToolbar {
|
||||||
}.clickUrlbar {
|
}.clickUrlbar {
|
||||||
typeSearch(searchTerm = "Amazon")
|
typeSearch(searchTerm = sponsoredKeyWord)
|
||||||
deleteSearchKeywordCharacters(numberOfDeletionSteps = 3)
|
deleteSearchKeywordCharacters(numberOfDeletionSteps = 1)
|
||||||
verifySearchEngineSuggestionResults(
|
verifySearchEngineSuggestionResults(
|
||||||
rule = activityTestRule,
|
rule = activityTestRule,
|
||||||
searchSuggestions = arrayOf(
|
searchSuggestions = arrayOf(
|
||||||
"Firefox Suggest",
|
"Firefox Suggest",
|
||||||
"Amazon.com - Official Site",
|
sponsoredKeyWords.getValue(sponsoredKeyWord)[0],
|
||||||
"Sponsored",
|
"Sponsored",
|
||||||
),
|
),
|
||||||
searchTerm = "Amazon",
|
searchTerm = sponsoredKeyWord,
|
||||||
shouldEditKeyword = true,
|
shouldEditKeyword = true,
|
||||||
numberOfDeletionSteps = 3,
|
numberOfDeletionSteps = 1,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -127,17 +195,17 @@ class FirefoxSuggestTest {
|
||||||
@SmokeTest
|
@SmokeTest
|
||||||
@Test
|
@Test
|
||||||
fun verifyFirefoxSuggestNonSponsoredSearchResultsTest() {
|
fun verifyFirefoxSuggestNonSponsoredSearchResultsTest() {
|
||||||
AppAndSystemHelper.runWithCondition(TestHelper.appContext.settings().enableFxSuggest) {
|
runWithCondition(TestHelper.appContext.settings().enableFxSuggest) {
|
||||||
navigationToolbar {
|
navigationToolbar {
|
||||||
}.clickUrlbar {
|
}.clickUrlbar {
|
||||||
typeSearch(searchTerm = "Marvel")
|
typeSearch(searchTerm = nonSponsoredKeyWord)
|
||||||
verifySearchEngineSuggestionResults(
|
verifySearchEngineSuggestionResults(
|
||||||
rule = activityTestRule,
|
rule = activityTestRule,
|
||||||
searchSuggestions = arrayOf(
|
searchSuggestions = arrayOf(
|
||||||
"Firefox Suggest",
|
"Firefox Suggest",
|
||||||
"Wikipedia - Marvel Cinematic Universe",
|
nonSponsoredKeyWords.getValue(nonSponsoredKeyWord)[0],
|
||||||
),
|
),
|
||||||
searchTerm = "Marvel",
|
searchTerm = nonSponsoredKeyWord,
|
||||||
)
|
)
|
||||||
verifySuggestionsAreNotDisplayed(
|
verifySuggestionsAreNotDisplayed(
|
||||||
rule = activityTestRule,
|
rule = activityTestRule,
|
||||||
|
@ -152,17 +220,17 @@ class FirefoxSuggestTest {
|
||||||
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/2348375
|
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/2348375
|
||||||
@Test
|
@Test
|
||||||
fun verifyFirefoxSuggestNonSponsoredSearchResultsWithPartialKeywordTest() {
|
fun verifyFirefoxSuggestNonSponsoredSearchResultsWithPartialKeywordTest() {
|
||||||
AppAndSystemHelper.runWithCondition(TestHelper.appContext.settings().enableFxSuggest) {
|
runWithCondition(TestHelper.appContext.settings().enableFxSuggest) {
|
||||||
navigationToolbar {
|
navigationToolbar {
|
||||||
}.clickUrlbar {
|
}.clickUrlbar {
|
||||||
typeSearch(searchTerm = "Marv")
|
typeSearch(searchTerm = nonSponsoredKeyWord.dropLast(1))
|
||||||
verifySearchEngineSuggestionResults(
|
verifySearchEngineSuggestionResults(
|
||||||
rule = activityTestRule,
|
rule = activityTestRule,
|
||||||
searchSuggestions = arrayOf(
|
searchSuggestions = arrayOf(
|
||||||
"Firefox Suggest",
|
"Firefox Suggest",
|
||||||
"Wikipedia - Marvel Cinematic Universe",
|
nonSponsoredKeyWords.getValue(nonSponsoredKeyWord)[0],
|
||||||
),
|
),
|
||||||
searchTerm = "Marv",
|
searchTerm = nonSponsoredKeyWord.dropLast(1),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -171,23 +239,21 @@ class FirefoxSuggestTest {
|
||||||
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/2348376
|
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/2348376
|
||||||
@Test
|
@Test
|
||||||
fun openFirefoxSuggestNonSponsoredSearchResultsTest() {
|
fun openFirefoxSuggestNonSponsoredSearchResultsTest() {
|
||||||
AppAndSystemHelper.runWithCondition(TestHelper.appContext.settings().enableFxSuggest) {
|
runWithCondition(TestHelper.appContext.settings().enableFxSuggest) {
|
||||||
navigationToolbar {
|
navigationToolbar {
|
||||||
}.clickUrlbar {
|
}.clickUrlbar {
|
||||||
typeSearch(searchTerm = "Marvel")
|
typeSearch(searchTerm = nonSponsoredKeyWord)
|
||||||
verifySearchEngineSuggestionResults(
|
verifySearchEngineSuggestionResults(
|
||||||
rule = activityTestRule,
|
rule = activityTestRule,
|
||||||
searchSuggestions = arrayOf(
|
searchSuggestions = arrayOf(
|
||||||
"Firefox Suggest",
|
"Firefox Suggest",
|
||||||
"Wikipedia - Marvel Cinematic Universe",
|
nonSponsoredKeyWords.getValue(nonSponsoredKeyWord)[0],
|
||||||
),
|
),
|
||||||
searchTerm = "Marvel",
|
searchTerm = nonSponsoredKeyWord,
|
||||||
)
|
)
|
||||||
}.clickSearchSuggestion("Wikipedia - Marvel Cinematic Universe") {
|
}.clickSearchSuggestion(nonSponsoredKeyWords.getValue(nonSponsoredKeyWord)[0]) {
|
||||||
waitForPageToLoad()
|
waitForPageToLoad()
|
||||||
verifyUrl(
|
verifyUrl(nonSponsoredKeyWords.getValue(nonSponsoredKeyWord)[1])
|
||||||
"wikipedia.org/wiki/Marvel_Cinematic_Universe",
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -607,14 +607,12 @@ class LoginsTest {
|
||||||
revealPassword()
|
revealPassword()
|
||||||
verifyPasswordSaved("firefox")
|
verifyPasswordSaved("firefox")
|
||||||
}.goBackToSavedLogins {
|
}.goBackToSavedLogins {
|
||||||
clickSearchLoginButton()
|
|
||||||
searchLogin("android")
|
searchLogin("android")
|
||||||
viewSavedLoginDetails(originWebsite)
|
viewSavedLoginDetails(originWebsite)
|
||||||
verifyLoginItemUsername("android")
|
verifyLoginItemUsername("android")
|
||||||
revealPassword()
|
revealPassword()
|
||||||
verifyPasswordSaved("firefox")
|
verifyPasswordSaved("firefox")
|
||||||
}.goBackToSavedLogins {
|
}.goBackToSavedLogins {
|
||||||
clickSearchLoginButton()
|
|
||||||
searchLogin("AnDrOiD")
|
searchLogin("AnDrOiD")
|
||||||
viewSavedLoginDetails(originWebsite)
|
viewSavedLoginDetails(originWebsite)
|
||||||
verifyLoginItemUsername("android")
|
verifyLoginItemUsername("android")
|
||||||
|
@ -654,14 +652,12 @@ class LoginsTest {
|
||||||
revealPassword()
|
revealPassword()
|
||||||
verifyPasswordSaved("firefox")
|
verifyPasswordSaved("firefox")
|
||||||
}.goBackToSavedLogins {
|
}.goBackToSavedLogins {
|
||||||
clickSearchLoginButton()
|
|
||||||
searchLogin("mozilla")
|
searchLogin("mozilla")
|
||||||
viewSavedLoginDetails(originWebsite)
|
viewSavedLoginDetails(originWebsite)
|
||||||
verifyLoginItemUsername("android")
|
verifyLoginItemUsername("android")
|
||||||
revealPassword()
|
revealPassword()
|
||||||
verifyPasswordSaved("firefox")
|
verifyPasswordSaved("firefox")
|
||||||
}.goBackToSavedLogins {
|
}.goBackToSavedLogins {
|
||||||
clickSearchLoginButton()
|
|
||||||
searchLogin("MoZiLlA")
|
searchLogin("MoZiLlA")
|
||||||
viewSavedLoginDetails(originWebsite)
|
viewSavedLoginDetails(originWebsite)
|
||||||
verifyLoginItemUsername("android")
|
verifyLoginItemUsername("android")
|
||||||
|
|
|
@ -37,7 +37,8 @@ class MainMenuTest {
|
||||||
private lateinit var mockWebServer: MockWebServer
|
private lateinit var mockWebServer: MockWebServer
|
||||||
|
|
||||||
@get:Rule
|
@get:Rule
|
||||||
val activityTestRule = HomeActivityIntentTestRule.withDefaultSettingsOverrides()
|
val activityTestRule =
|
||||||
|
HomeActivityIntentTestRule.withDefaultSettingsOverrides(translationsEnabled = true)
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
|
|
|
@ -14,6 +14,7 @@ import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.mozilla.fenix.customannotations.SmokeTest
|
import org.mozilla.fenix.customannotations.SmokeTest
|
||||||
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
|
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
|
||||||
|
import org.mozilla.fenix.helpers.AppAndSystemHelper
|
||||||
import org.mozilla.fenix.helpers.AppAndSystemHelper.runWithSystemLocaleChanged
|
import org.mozilla.fenix.helpers.AppAndSystemHelper.runWithSystemLocaleChanged
|
||||||
import org.mozilla.fenix.helpers.HomeActivityTestRule
|
import org.mozilla.fenix.helpers.HomeActivityTestRule
|
||||||
import org.mozilla.fenix.helpers.TestAssetHelper
|
import org.mozilla.fenix.helpers.TestAssetHelper
|
||||||
|
@ -50,6 +51,7 @@ class NavigationToolbarTest {
|
||||||
@After
|
@After
|
||||||
fun tearDown() {
|
fun tearDown() {
|
||||||
mockWebServer.shutdown()
|
mockWebServer.shutdown()
|
||||||
|
AppAndSystemHelper.resetSystemLocaleToEnUS()
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/987326
|
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/987326
|
||||||
|
|
|
@ -21,6 +21,7 @@ import org.junit.Test
|
||||||
import org.mozilla.fenix.customannotations.SmokeTest
|
import org.mozilla.fenix.customannotations.SmokeTest
|
||||||
import org.mozilla.fenix.ext.components
|
import org.mozilla.fenix.ext.components
|
||||||
import org.mozilla.fenix.ext.settings
|
import org.mozilla.fenix.ext.settings
|
||||||
|
import org.mozilla.fenix.helpers.AppAndSystemHelper
|
||||||
import org.mozilla.fenix.helpers.AppAndSystemHelper.assertNativeAppOpens
|
import org.mozilla.fenix.helpers.AppAndSystemHelper.assertNativeAppOpens
|
||||||
import org.mozilla.fenix.helpers.AppAndSystemHelper.denyPermission
|
import org.mozilla.fenix.helpers.AppAndSystemHelper.denyPermission
|
||||||
import org.mozilla.fenix.helpers.AppAndSystemHelper.grantSystemPermission
|
import org.mozilla.fenix.helpers.AppAndSystemHelper.grantSystemPermission
|
||||||
|
@ -92,6 +93,7 @@ class SearchTest {
|
||||||
@After
|
@After
|
||||||
fun tearDown() {
|
fun tearDown() {
|
||||||
searchMockServer.shutdown()
|
searchMockServer.shutdown()
|
||||||
|
AppAndSystemHelper.resetSystemLocaleToEnUS()
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/2154189
|
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/2154189
|
||||||
|
|
|
@ -327,7 +327,7 @@ class SettingsAdvancedTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
navigationToolbar {
|
navigationToolbar {
|
||||||
}.enterURLAndEnterToBrowser(youTubePage) {
|
}.enterURLAndEnterToBrowser("https://m.youtube.com/".toUri()) {
|
||||||
waitForPageToLoad()
|
waitForPageToLoad()
|
||||||
verifyOpenLinksInAppsCFRExists(true)
|
verifyOpenLinksInAppsCFRExists(true)
|
||||||
}.clickOpenLinksInAppsGoToSettingsCFRButton {
|
}.clickOpenLinksInAppsGoToSettingsCFRButton {
|
||||||
|
|
|
@ -14,6 +14,7 @@ import org.mozilla.fenix.FenixApplication
|
||||||
import org.mozilla.fenix.R
|
import org.mozilla.fenix.R
|
||||||
import org.mozilla.fenix.customannotations.SmokeTest
|
import org.mozilla.fenix.customannotations.SmokeTest
|
||||||
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
|
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
|
||||||
|
import org.mozilla.fenix.helpers.AppAndSystemHelper
|
||||||
import org.mozilla.fenix.helpers.AppAndSystemHelper.registerAndCleanupIdlingResources
|
import org.mozilla.fenix.helpers.AppAndSystemHelper.registerAndCleanupIdlingResources
|
||||||
import org.mozilla.fenix.helpers.AppAndSystemHelper.runWithSystemLocaleChanged
|
import org.mozilla.fenix.helpers.AppAndSystemHelper.runWithSystemLocaleChanged
|
||||||
import org.mozilla.fenix.helpers.DataGenerationHelper.getStringResource
|
import org.mozilla.fenix.helpers.DataGenerationHelper.getStringResource
|
||||||
|
@ -51,6 +52,7 @@ class SettingsGeneralTest {
|
||||||
@After
|
@After
|
||||||
fun tearDown() {
|
fun tearDown() {
|
||||||
mockWebServer.shutdown()
|
mockWebServer.shutdown()
|
||||||
|
AppAndSystemHelper.resetSystemLocaleToEnUS()
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/2092697
|
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/2092697
|
||||||
|
|
|
@ -5,14 +5,12 @@
|
||||||
package org.mozilla.fenix.ui
|
package org.mozilla.fenix.ui
|
||||||
|
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import androidx.test.espresso.Espresso.pressBack
|
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.mozilla.fenix.customannotations.SmokeTest
|
import org.mozilla.fenix.customannotations.SmokeTest
|
||||||
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
|
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
|
||||||
import org.mozilla.fenix.helpers.MatcherHelper.itemContainingText
|
import org.mozilla.fenix.helpers.MatcherHelper.itemContainingText
|
||||||
import org.mozilla.fenix.helpers.TestHelper.exitMenu
|
import org.mozilla.fenix.helpers.TestHelper.exitMenu
|
||||||
import org.mozilla.fenix.ui.robots.browserScreen
|
|
||||||
import org.mozilla.fenix.ui.robots.clickPageObject
|
import org.mozilla.fenix.ui.robots.clickPageObject
|
||||||
import org.mozilla.fenix.ui.robots.homeScreen
|
import org.mozilla.fenix.ui.robots.homeScreen
|
||||||
import org.mozilla.fenix.ui.robots.navigationToolbar
|
import org.mozilla.fenix.ui.robots.navigationToolbar
|
||||||
|
@ -182,13 +180,7 @@ class SettingsHTTPSOnlyModeTest {
|
||||||
waitForPageToLoad()
|
waitForPageToLoad()
|
||||||
}.openNavigationToolbar {
|
}.openNavigationToolbar {
|
||||||
verifyUrl(httpsPageUrl)
|
verifyUrl(httpsPageUrl)
|
||||||
pressBack()
|
}.goBackToBrowserScreen {
|
||||||
}
|
|
||||||
browserScreen {
|
|
||||||
}.openTabDrawer {
|
|
||||||
closeTab()
|
|
||||||
}
|
|
||||||
homeScreen {
|
|
||||||
}.openThreeDotMenu {
|
}.openThreeDotMenu {
|
||||||
}.openSettings {
|
}.openSettings {
|
||||||
}.openHttpsOnlyModeMenu {
|
}.openHttpsOnlyModeMenu {
|
||||||
|
@ -203,7 +195,6 @@ class SettingsHTTPSOnlyModeTest {
|
||||||
waitForPageToLoad()
|
waitForPageToLoad()
|
||||||
}.openNavigationToolbar {
|
}.openNavigationToolbar {
|
||||||
verifyUrl(httpPageUrl)
|
verifyUrl(httpPageUrl)
|
||||||
pressBack()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.mozilla.fenix.customannotations.SmokeTest
|
import org.mozilla.fenix.customannotations.SmokeTest
|
||||||
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
|
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
|
||||||
|
import org.mozilla.fenix.helpers.AppAndSystemHelper.resetSystemLocaleToEnUS
|
||||||
import org.mozilla.fenix.helpers.AppAndSystemHelper.runWithSystemLocaleChanged
|
import org.mozilla.fenix.helpers.AppAndSystemHelper.runWithSystemLocaleChanged
|
||||||
import org.mozilla.fenix.helpers.AppAndSystemHelper.setSystemLocale
|
import org.mozilla.fenix.helpers.AppAndSystemHelper.setSystemLocale
|
||||||
import org.mozilla.fenix.helpers.DataGenerationHelper.setTextToClipBoard
|
import org.mozilla.fenix.helpers.DataGenerationHelper.setTextToClipBoard
|
||||||
|
@ -40,7 +41,6 @@ class SettingsSearchTest {
|
||||||
listOf(
|
listOf(
|
||||||
"LeOSearch",
|
"LeOSearch",
|
||||||
"DuckDuckGo",
|
"DuckDuckGo",
|
||||||
"Google",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@get:Rule
|
@get:Rule
|
||||||
|
@ -64,6 +64,7 @@ class SettingsSearchTest {
|
||||||
@After
|
@After
|
||||||
fun tearDown() {
|
fun tearDown() {
|
||||||
mockWebServer.shutdown()
|
mockWebServer.shutdown()
|
||||||
|
resetSystemLocaleToEnUS()
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/2203333
|
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/2203333
|
||||||
|
@ -424,6 +425,10 @@ class SettingsSearchTest {
|
||||||
fun verifyShowSearchSuggestionsToggleTest() {
|
fun verifyShowSearchSuggestionsToggleTest() {
|
||||||
homeScreen {
|
homeScreen {
|
||||||
}.openSearch {
|
}.openSearch {
|
||||||
|
// The Google related suggestions aren't always displayed on cold run
|
||||||
|
// Bugzilla ticket: https://bugzilla.mozilla.org/show_bug.cgi?id=1813587
|
||||||
|
clickSearchSelectorButton()
|
||||||
|
selectTemporarySearchMethod("DuckDuckGo")
|
||||||
typeSearch("mozilla ")
|
typeSearch("mozilla ")
|
||||||
verifySearchEngineSuggestionResults(
|
verifySearchEngineSuggestionResults(
|
||||||
activityTestRule,
|
activityTestRule,
|
||||||
|
@ -438,6 +443,10 @@ class SettingsSearchTest {
|
||||||
}.goBack {
|
}.goBack {
|
||||||
}.goBack {
|
}.goBack {
|
||||||
}.openSearch {
|
}.openSearch {
|
||||||
|
// The Google related suggestions aren't always displayed on cold run
|
||||||
|
// Bugzilla ticket: https://bugzilla.mozilla.org/show_bug.cgi?id=1813587
|
||||||
|
clickSearchSelectorButton()
|
||||||
|
selectTemporarySearchMethod("DuckDuckGo")
|
||||||
typeSearch("mozilla")
|
typeSearch("mozilla")
|
||||||
verifySuggestionsAreNotDisplayed(activityTestRule, "mozilla firefox")
|
verifySuggestionsAreNotDisplayed(activityTestRule, "mozilla firefox")
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,6 @@ import androidx.test.uiautomator.UiDevice
|
||||||
import okhttp3.mockwebserver.MockWebServer
|
import okhttp3.mockwebserver.MockWebServer
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Ignore
|
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.mozilla.fenix.customannotations.SmokeTest
|
import org.mozilla.fenix.customannotations.SmokeTest
|
||||||
|
@ -90,7 +89,6 @@ class SponsoredShortcutsTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/1729335
|
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/1729335
|
||||||
@Ignore("Failing, see: https://github.com/mozilla-mobile/fenix/issues/25926")
|
|
||||||
@Test
|
@Test
|
||||||
fun openSponsorsAndYourPrivacyOptionTest() {
|
fun openSponsorsAndYourPrivacyOptionTest() {
|
||||||
homeScreen {
|
homeScreen {
|
||||||
|
@ -102,7 +100,6 @@ class SponsoredShortcutsTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/1729336
|
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/1729336
|
||||||
@Ignore("Failing, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1807268")
|
|
||||||
@Test
|
@Test
|
||||||
fun openSponsoredShortcutsSettingsOptionTest() {
|
fun openSponsoredShortcutsSettingsOptionTest() {
|
||||||
homeScreen {
|
homeScreen {
|
||||||
|
|
|
@ -17,6 +17,7 @@ import androidx.compose.ui.test.performClick
|
||||||
import androidx.test.espresso.Espresso.onView
|
import androidx.test.espresso.Espresso.onView
|
||||||
import androidx.test.espresso.ViewInteraction
|
import androidx.test.espresso.ViewInteraction
|
||||||
import androidx.test.espresso.action.ViewActions.click
|
import androidx.test.espresso.action.ViewActions.click
|
||||||
|
import androidx.test.espresso.action.ViewActions.longClick
|
||||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||||
import androidx.test.espresso.contrib.PickerActions
|
import androidx.test.espresso.contrib.PickerActions
|
||||||
import androidx.test.espresso.matcher.RootMatchers.isDialog
|
import androidx.test.espresso.matcher.RootMatchers.isDialog
|
||||||
|
@ -40,7 +41,6 @@ import org.junit.Assert.assertTrue
|
||||||
import org.junit.Assert.fail
|
import org.junit.Assert.fail
|
||||||
import org.mozilla.fenix.R
|
import org.mozilla.fenix.R
|
||||||
import org.mozilla.fenix.ext.components
|
import org.mozilla.fenix.ext.components
|
||||||
import org.mozilla.fenix.helpers.Constants.LONG_CLICK_DURATION
|
|
||||||
import org.mozilla.fenix.helpers.Constants.RETRY_COUNT
|
import org.mozilla.fenix.helpers.Constants.RETRY_COUNT
|
||||||
import org.mozilla.fenix.helpers.Constants.TAG
|
import org.mozilla.fenix.helpers.Constants.TAG
|
||||||
import org.mozilla.fenix.helpers.DataGenerationHelper.getStringResource
|
import org.mozilla.fenix.helpers.DataGenerationHelper.getStringResource
|
||||||
|
@ -686,23 +686,28 @@ class BrowserRobot {
|
||||||
|
|
||||||
fun verifyCookieBannerExists(exists: Boolean) {
|
fun verifyCookieBannerExists(exists: Boolean) {
|
||||||
for (i in 1..RETRY_COUNT) {
|
for (i in 1..RETRY_COUNT) {
|
||||||
|
Log.i(TAG, "verifyCookieBannerExists: For loop: $i")
|
||||||
try {
|
try {
|
||||||
assertUIObjectExists(cookieBanner(), exists = exists)
|
// Wait for the blocker to kick-in and make the cookie banner disappear
|
||||||
|
itemWithResId("CybotCookiebotDialog").waitUntilGone(waitingTime)
|
||||||
|
Log.i(TAG, "verifyCookieBannerExists: Waiting for window update")
|
||||||
|
// Assert that the blocker properly dismissed the cookie banner
|
||||||
|
assertUIObjectExists(itemWithResId("CybotCookiebotDialog"), exists = exists)
|
||||||
|
|
||||||
break
|
break
|
||||||
} catch (e: AssertionError) {
|
} catch (e: AssertionError) {
|
||||||
if (i == RETRY_COUNT) {
|
if (i == RETRY_COUNT) {
|
||||||
throw e
|
throw e
|
||||||
} else {
|
|
||||||
browserScreen {
|
|
||||||
}.openThreeDotMenu {
|
|
||||||
}.refreshPage {
|
|
||||||
waitForPageToLoad()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assertUIObjectExists(cookieBanner(), exists = exists)
|
|
||||||
}
|
fun verifyCookieBannerBlockerCFRExists(exists: Boolean) =
|
||||||
|
assertUIObjectExists(
|
||||||
|
itemContainingText(getStringResource(R.string.cookie_banner_cfr_message)),
|
||||||
|
exists = exists,
|
||||||
|
)
|
||||||
|
|
||||||
fun verifyOpenLinkInAnotherAppPrompt() {
|
fun verifyOpenLinkInAnotherAppPrompt() {
|
||||||
assertUIObjectExists(
|
assertUIObjectExists(
|
||||||
|
@ -841,7 +846,7 @@ class BrowserRobot {
|
||||||
button.click()
|
button.click()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun longClickToolbar() = mDevice.findObject(By.res("$packageName:id/mozac_browser_toolbar_url_view")).click(LONG_CLICK_DURATION)
|
fun longClickToolbar() = onView(withId(R.id.mozac_browser_toolbar_url_view)).perform(longClick())
|
||||||
|
|
||||||
fun verifyDownloadPromptIsDismissed() =
|
fun verifyDownloadPromptIsDismissed() =
|
||||||
assertUIObjectExists(
|
assertUIObjectExists(
|
||||||
|
@ -1149,6 +1154,7 @@ class BrowserRobot {
|
||||||
"$packageName:id/action",
|
"$packageName:id/action",
|
||||||
getStringResource(R.string.open_in_app_cfr_positive_button_text),
|
getStringResource(R.string.open_in_app_cfr_positive_button_text),
|
||||||
).clickAndWaitForNewWindow(waitingTime)
|
).clickAndWaitForNewWindow(waitingTime)
|
||||||
|
Log.i(TAG, "clickOpenLinksInAppsGoToSettingsCFRButton: Clicked \"Go to settings\" open links in apps CFR button")
|
||||||
|
|
||||||
SettingsRobot().interact()
|
SettingsRobot().interact()
|
||||||
return SettingsRobot.Transition()
|
return SettingsRobot.Transition()
|
||||||
|
@ -1302,8 +1308,6 @@ fun clearTextFieldItem(item: UiObject) {
|
||||||
item.clearTextField()
|
item.clearTextField()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun cookieBanner() = itemWithResId("startsiden-gdpr-disclaimer")
|
|
||||||
|
|
||||||
// Context menu items
|
// Context menu items
|
||||||
// Link URL
|
// Link URL
|
||||||
private fun contextMenuLinkUrl(linkUrl: String) =
|
private fun contextMenuLinkUrl(linkUrl: String) =
|
||||||
|
|
|
@ -26,6 +26,7 @@ import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.test.espresso.Espresso.onView
|
import androidx.test.espresso.Espresso.onView
|
||||||
import androidx.test.espresso.action.ViewActions
|
import androidx.test.espresso.action.ViewActions
|
||||||
import androidx.test.espresso.action.ViewActions.click
|
import androidx.test.espresso.action.ViewActions.click
|
||||||
|
import androidx.test.espresso.action.ViewActions.longClick
|
||||||
import androidx.test.espresso.assertion.PositionAssertions.isCompletelyAbove
|
import androidx.test.espresso.assertion.PositionAssertions.isCompletelyAbove
|
||||||
import androidx.test.espresso.assertion.PositionAssertions.isPartiallyBelow
|
import androidx.test.espresso.assertion.PositionAssertions.isPartiallyBelow
|
||||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||||
|
@ -51,7 +52,6 @@ import org.junit.Assert
|
||||||
import org.junit.Assert.assertTrue
|
import org.junit.Assert.assertTrue
|
||||||
import org.mozilla.fenix.R
|
import org.mozilla.fenix.R
|
||||||
import org.mozilla.fenix.helpers.Constants.LISTS_MAXSWIPES
|
import org.mozilla.fenix.helpers.Constants.LISTS_MAXSWIPES
|
||||||
import org.mozilla.fenix.helpers.Constants.LONG_CLICK_DURATION
|
|
||||||
import org.mozilla.fenix.helpers.Constants.TAG
|
import org.mozilla.fenix.helpers.Constants.TAG
|
||||||
import org.mozilla.fenix.helpers.DataGenerationHelper.getStringResource
|
import org.mozilla.fenix.helpers.DataGenerationHelper.getStringResource
|
||||||
import org.mozilla.fenix.helpers.HomeActivityComposeTestRule
|
import org.mozilla.fenix.helpers.HomeActivityComposeTestRule
|
||||||
|
@ -86,9 +86,9 @@ class HomeScreenRobot {
|
||||||
" service provider, it makes it easier to keep what you do online private from anyone" +
|
" service provider, it makes it easier to keep what you do online private from anyone" +
|
||||||
" else who uses this device."
|
" else who uses this device."
|
||||||
|
|
||||||
fun verifyNavigationToolbar() = assertUIObjectExists(navigationToolbar)
|
fun verifyNavigationToolbar() = assertUIObjectExists(navigationToolbar())
|
||||||
|
|
||||||
fun verifyHomeScreen() = assertUIObjectExists(homeScreen)
|
fun verifyHomeScreen() = assertUIObjectExists(homeScreen())
|
||||||
|
|
||||||
fun verifyPrivateBrowsingHomeScreenItems() {
|
fun verifyPrivateBrowsingHomeScreenItems() {
|
||||||
verifyHomeScreenAppBarItems()
|
verifyHomeScreenAppBarItems()
|
||||||
|
@ -97,19 +97,19 @@ class HomeScreenRobot {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun verifyHomeScreenAppBarItems() =
|
fun verifyHomeScreenAppBarItems() =
|
||||||
assertUIObjectExists(homeScreen, privateBrowsingButton, homepageWordmark)
|
assertUIObjectExists(homeScreen(), privateBrowsingButton(), homepageWordmark())
|
||||||
|
|
||||||
fun verifyNavigationToolbarItems(numberOfOpenTabs: String = "0") =
|
fun verifyNavigationToolbarItems(numberOfOpenTabs: String = "0") =
|
||||||
assertUIObjectExists(navigationToolbar, menuButton, tabCounter(numberOfOpenTabs))
|
assertUIObjectExists(navigationToolbar(), menuButton, tabCounter(numberOfOpenTabs))
|
||||||
|
|
||||||
fun verifyHomePrivateBrowsingButton() = assertUIObjectExists(privateBrowsingButton)
|
fun verifyHomePrivateBrowsingButton() = assertUIObjectExists(privateBrowsingButton())
|
||||||
fun verifyHomeMenuButton() = assertUIObjectExists(menuButton)
|
fun verifyHomeMenuButton() = assertUIObjectExists(menuButton)
|
||||||
fun verifyTabButton() = assertTabButton()
|
fun verifyTabButton() = assertTabButton()
|
||||||
fun verifyCollectionsHeader() = assertCollectionsHeader()
|
fun verifyCollectionsHeader() = assertCollectionsHeader()
|
||||||
fun verifyNoCollectionsText() = assertNoCollectionsText()
|
fun verifyNoCollectionsText() = assertNoCollectionsText()
|
||||||
fun verifyHomeWordmark() {
|
fun verifyHomeWordmark() {
|
||||||
homeScreenList().scrollToBeginning(3)
|
homeScreenList().scrollToBeginning(3)
|
||||||
assertUIObjectExists(homepageWordmark)
|
assertUIObjectExists(homepageWordmark())
|
||||||
}
|
}
|
||||||
fun verifyHomeComponent() = assertHomeComponent()
|
fun verifyHomeComponent() = assertHomeComponent()
|
||||||
|
|
||||||
|
@ -140,7 +140,7 @@ class HomeScreenRobot {
|
||||||
).assertExists()
|
).assertExists()
|
||||||
|
|
||||||
it.onNodeWithText(
|
it.onNodeWithText(
|
||||||
getStringResource(R.string.juno_onboarding_default_browser_description_nimbus_2),
|
getStringResource(R.string.juno_onboarding_default_browser_description_nimbus_3),
|
||||||
).assertExists()
|
).assertExists()
|
||||||
|
|
||||||
it.onNodeWithText(
|
it.onNodeWithText(
|
||||||
|
@ -292,7 +292,7 @@ class HomeScreenRobot {
|
||||||
mDevice.waitNotNull(findObject(By.text(expectedText)), waitingTime)
|
mDevice.waitNotNull(findObject(By.text(expectedText)), waitingTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clickFirefoxLogo() = homepageWordmark.click()
|
fun clickFirefoxLogo() = homepageWordmark().click()
|
||||||
|
|
||||||
fun verifyThoughtProvokingStories(enabled: Boolean) {
|
fun verifyThoughtProvokingStories(enabled: Boolean) {
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
|
@ -481,8 +481,8 @@ class HomeScreenRobot {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun openSearch(interact: SearchRobot.() -> Unit): SearchRobot.Transition {
|
fun openSearch(interact: SearchRobot.() -> Unit): SearchRobot.Transition {
|
||||||
navigationToolbar.waitForExists(waitingTime)
|
navigationToolbar().waitForExists(waitingTime)
|
||||||
navigationToolbar.click()
|
navigationToolbar().click()
|
||||||
mDevice.waitForIdle()
|
mDevice.waitForIdle()
|
||||||
|
|
||||||
SearchRobot().interact()
|
SearchRobot().interact()
|
||||||
|
@ -502,14 +502,14 @@ class HomeScreenRobot {
|
||||||
fun togglePrivateBrowsingMode(switchPBModeOn: Boolean = true) {
|
fun togglePrivateBrowsingMode(switchPBModeOn: Boolean = true) {
|
||||||
// Switch to private browsing homescreen
|
// Switch to private browsing homescreen
|
||||||
if (switchPBModeOn && !isPrivateModeEnabled()) {
|
if (switchPBModeOn && !isPrivateModeEnabled()) {
|
||||||
privateBrowsingButton.waitForExists(waitingTime)
|
privateBrowsingButton().waitForExists(waitingTime)
|
||||||
privateBrowsingButton.click()
|
privateBrowsingButton().click()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Switch to normal browsing homescreen
|
// Switch to normal browsing homescreen
|
||||||
if (!switchPBModeOn && isPrivateModeEnabled()) {
|
if (!switchPBModeOn && isPrivateModeEnabled()) {
|
||||||
privateBrowsingButton.waitForExists(waitingTime)
|
privateBrowsingButton().waitForExists(waitingTime)
|
||||||
privateBrowsingButton.click()
|
privateBrowsingButton().click()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -521,7 +521,7 @@ class HomeScreenRobot {
|
||||||
waitingTime,
|
waitingTime,
|
||||||
)
|
)
|
||||||
|
|
||||||
privateBrowsingButton.click()
|
privateBrowsingButton().click()
|
||||||
}
|
}
|
||||||
|
|
||||||
AddToHomeScreenRobot().interact()
|
AddToHomeScreenRobot().interact()
|
||||||
|
@ -535,7 +535,7 @@ class HomeScreenRobot {
|
||||||
fun openNavigationToolbar(interact: NavigationToolbarRobot.() -> Unit): NavigationToolbarRobot.Transition {
|
fun openNavigationToolbar(interact: NavigationToolbarRobot.() -> Unit): NavigationToolbarRobot.Transition {
|
||||||
mDevice.findObject(UiSelector().resourceId("$packageName:id/toolbar"))
|
mDevice.findObject(UiSelector().resourceId("$packageName:id/toolbar"))
|
||||||
.waitForExists(waitingTime)
|
.waitForExists(waitingTime)
|
||||||
navigationToolbar.click()
|
navigationToolbar().click()
|
||||||
|
|
||||||
NavigationToolbarRobot().interact()
|
NavigationToolbarRobot().interact()
|
||||||
return NavigationToolbarRobot.Transition()
|
return NavigationToolbarRobot.Transition()
|
||||||
|
@ -557,7 +557,8 @@ class HomeScreenRobot {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun openContextMenuOnSponsoredShortcut(sponsoredShortcutTitle: String, interact: HomeScreenRobot.() -> Unit): Transition {
|
fun openContextMenuOnSponsoredShortcut(sponsoredShortcutTitle: String, interact: HomeScreenRobot.() -> Unit): Transition {
|
||||||
sponsoredShortcut(sponsoredShortcutTitle).click(LONG_CLICK_DURATION)
|
sponsoredShortcut(sponsoredShortcutTitle).perform(longClick())
|
||||||
|
Log.i(TAG, "openContextMenuOnSponsoredShortcut: Long clicked to open context menu for $sponsoredShortcutTitle sponsored shortcut")
|
||||||
|
|
||||||
HomeScreenRobot().interact()
|
HomeScreenRobot().interact()
|
||||||
return Transition()
|
return Transition()
|
||||||
|
@ -631,8 +632,10 @@ class HomeScreenRobot {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clickSponsoredShortcutsSettingsButton(interact: SettingsSubMenuHomepageRobot.() -> Unit): SettingsSubMenuHomepageRobot.Transition {
|
fun clickSponsoredShortcutsSettingsButton(interact: SettingsSubMenuHomepageRobot.() -> Unit): SettingsSubMenuHomepageRobot.Transition {
|
||||||
|
Log.i(TAG, "clickSponsoredShortcutsSettingsButton: Looking for: ${sponsoredShortcutsSettingsButton.selector}")
|
||||||
sponsoredShortcutsSettingsButton.waitForExists(waitingTime)
|
sponsoredShortcutsSettingsButton.waitForExists(waitingTime)
|
||||||
sponsoredShortcutsSettingsButton.clickAndWaitForNewWindow(waitingTime)
|
sponsoredShortcutsSettingsButton.clickAndWaitForNewWindow(waitingTime)
|
||||||
|
Log.i(TAG, "clickSponsoredShortcutsSettingsButton: Clicked ${sponsoredShortcutsSettingsButton.selector} and waiting for $waitingTime for a new window")
|
||||||
|
|
||||||
SettingsSubMenuHomepageRobot().interact()
|
SettingsSubMenuHomepageRobot().interact()
|
||||||
return SettingsSubMenuHomepageRobot.Transition()
|
return SettingsSubMenuHomepageRobot.Transition()
|
||||||
|
@ -939,18 +942,19 @@ private fun saveTabsToCollectionButton() = onView(withId(R.id.add_tabs_to_collec
|
||||||
private fun tabsCounter() = onView(withId(R.id.tab_button))
|
private fun tabsCounter() = onView(withId(R.id.tab_button))
|
||||||
|
|
||||||
private fun sponsoredShortcut(sponsoredShortcutTitle: String) =
|
private fun sponsoredShortcut(sponsoredShortcutTitle: String) =
|
||||||
mDevice.findObject(
|
onView(
|
||||||
By
|
allOf(
|
||||||
.res("$packageName:id/top_site_title")
|
withId(R.id.top_site_title),
|
||||||
.textContains(sponsoredShortcutTitle),
|
withText(sponsoredShortcutTitle),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun storyByTopicItem(composeTestRule: ComposeTestRule, position: Int) =
|
private fun storyByTopicItem(composeTestRule: ComposeTestRule, position: Int) =
|
||||||
composeTestRule.onNodeWithTag("pocket.categories").onChildAt(position - 1)
|
composeTestRule.onNodeWithTag("pocket.categories").onChildAt(position - 1)
|
||||||
|
|
||||||
private val homeScreen =
|
private fun homeScreen() =
|
||||||
itemWithResId("$packageName:id/homeLayout")
|
itemWithResId("$packageName:id/homeLayout")
|
||||||
private val privateBrowsingButton =
|
private fun privateBrowsingButton() =
|
||||||
itemWithResId("$packageName:id/privateBrowsingButton")
|
itemWithResId("$packageName:id/privateBrowsingButton")
|
||||||
|
|
||||||
private fun isPrivateModeEnabled(): Boolean =
|
private fun isPrivateModeEnabled(): Boolean =
|
||||||
|
@ -959,10 +963,10 @@ private fun isPrivateModeEnabled(): Boolean =
|
||||||
"Disable private browsing",
|
"Disable private browsing",
|
||||||
).exists()
|
).exists()
|
||||||
|
|
||||||
private val homepageWordmark =
|
private fun homepageWordmark() =
|
||||||
itemWithResId("$packageName:id/wordmark")
|
itemWithResId("$packageName:id/wordmark")
|
||||||
|
|
||||||
private val navigationToolbar =
|
private fun navigationToolbar() =
|
||||||
itemWithResId("$packageName:id/toolbar")
|
itemWithResId("$packageName:id/toolbar")
|
||||||
private val menuButton =
|
private val menuButton =
|
||||||
itemWithResId("$packageName:id/menuButton")
|
itemWithResId("$packageName:id/menuButton")
|
||||||
|
|
|
@ -15,6 +15,7 @@ import androidx.test.espresso.Espresso.onView
|
||||||
import androidx.test.espresso.IdlingRegistry
|
import androidx.test.espresso.IdlingRegistry
|
||||||
import androidx.test.espresso.IdlingResource
|
import androidx.test.espresso.IdlingResource
|
||||||
import androidx.test.espresso.action.ViewActions
|
import androidx.test.espresso.action.ViewActions
|
||||||
|
import androidx.test.espresso.action.ViewActions.longClick
|
||||||
import androidx.test.espresso.assertion.ViewAssertions
|
import androidx.test.espresso.assertion.ViewAssertions
|
||||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||||
import androidx.test.espresso.contrib.RecyclerViewActions
|
import androidx.test.espresso.contrib.RecyclerViewActions
|
||||||
|
@ -43,7 +44,6 @@ import org.mozilla.fenix.helpers.MatcherHelper.assertUIObjectExists
|
||||||
import org.mozilla.fenix.helpers.MatcherHelper.itemWithResId
|
import org.mozilla.fenix.helpers.MatcherHelper.itemWithResId
|
||||||
import org.mozilla.fenix.helpers.MatcherHelper.itemWithResIdAndText
|
import org.mozilla.fenix.helpers.MatcherHelper.itemWithResIdAndText
|
||||||
import org.mozilla.fenix.helpers.MatcherHelper.itemWithResIdContainingText
|
import org.mozilla.fenix.helpers.MatcherHelper.itemWithResIdContainingText
|
||||||
import org.mozilla.fenix.helpers.MatcherHelper.itemWithText
|
|
||||||
import org.mozilla.fenix.helpers.SessionLoadedIdlingResource
|
import org.mozilla.fenix.helpers.SessionLoadedIdlingResource
|
||||||
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime
|
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime
|
||||||
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTimeShort
|
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTimeShort
|
||||||
|
@ -149,7 +149,7 @@ class NavigationToolbarRobot {
|
||||||
assertTrue(
|
assertTrue(
|
||||||
itemWithResId("$packageName:id/browserLayout").waitForExists(waitingTime) ||
|
itemWithResId("$packageName:id/browserLayout").waitForExists(waitingTime) ||
|
||||||
itemWithResId("$packageName:id/download_button").waitForExists(waitingTime) ||
|
itemWithResId("$packageName:id/download_button").waitForExists(waitingTime) ||
|
||||||
itemWithText(getStringResource(R.string.tcp_cfr_message)).waitForExists(waitingTime),
|
itemWithResId("cfr.dismiss").waitForExists(waitingTime),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -272,9 +272,19 @@ class NavigationToolbarRobot {
|
||||||
return HomeScreenRobot.Transition()
|
return HomeScreenRobot.Transition()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun goBackToBrowserScreen(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
|
||||||
|
mDevice.pressBack()
|
||||||
|
Log.i(TAG, "goBackToBrowserScreen: Dismiss awesome bar using device back button")
|
||||||
|
mDevice.waitForWindowUpdate(packageName, waitingTimeShort)
|
||||||
|
Log.i(TAG, "goBackToBrowserScreen: Waited $waitingTimeShort for window update")
|
||||||
|
|
||||||
|
BrowserRobot().interact()
|
||||||
|
return BrowserRobot.Transition()
|
||||||
|
}
|
||||||
|
|
||||||
fun openTabButtonShortcutsMenu(interact: NavigationToolbarRobot.() -> Unit): Transition {
|
fun openTabButtonShortcutsMenu(interact: NavigationToolbarRobot.() -> Unit): Transition {
|
||||||
mDevice.waitNotNull(Until.findObject(By.res("$packageName:id/counter_root")))
|
mDevice.waitNotNull(Until.findObject(By.res("$packageName:id/counter_root")))
|
||||||
tabsCounter().click(LONG_CLICK_DURATION)
|
tabsCounter().perform(longClick())
|
||||||
Log.i(TAG, "Tabs counter long-click successful.")
|
Log.i(TAG, "Tabs counter long-click successful.")
|
||||||
|
|
||||||
NavigationToolbarRobot().interact()
|
NavigationToolbarRobot().interact()
|
||||||
|
@ -388,8 +398,7 @@ private fun awesomeBar() =
|
||||||
mDevice.findObject(UiSelector().resourceId("$packageName:id/mozac_browser_toolbar_edit_url_view"))
|
mDevice.findObject(UiSelector().resourceId("$packageName:id/mozac_browser_toolbar_edit_url_view"))
|
||||||
private fun threeDotButton() = onView(withId(R.id.mozac_browser_toolbar_menu))
|
private fun threeDotButton() = onView(withId(R.id.mozac_browser_toolbar_menu))
|
||||||
private fun tabTrayButton() = onView(withId(R.id.tab_button))
|
private fun tabTrayButton() = onView(withId(R.id.tab_button))
|
||||||
private fun tabsCounter() =
|
private fun tabsCounter() = onView(withId(R.id.mozac_browser_toolbar_browser_actions))
|
||||||
mDevice.findObject(By.res("$packageName:id/counter_root"))
|
|
||||||
private fun fillLinkButton() = onView(withId(R.id.fill_link_from_clipboard))
|
private fun fillLinkButton() = onView(withId(R.id.fill_link_from_clipboard))
|
||||||
private fun clearAddressBarButton() = itemWithResId("$packageName:id/mozac_browser_toolbar_clear_view")
|
private fun clearAddressBarButton() = itemWithResId("$packageName:id/mozac_browser_toolbar_clear_view")
|
||||||
private fun readerViewToggle() =
|
private fun readerViewToggle() =
|
||||||
|
|
|
@ -107,6 +107,27 @@ class SettingsRobot {
|
||||||
fun verifyPrivacyHeading() = assertPrivacyHeading()
|
fun verifyPrivacyHeading() = assertPrivacyHeading()
|
||||||
|
|
||||||
fun verifyHTTPSOnlyModeButton() = assertHTTPSOnlyModeButton()
|
fun verifyHTTPSOnlyModeButton() = assertHTTPSOnlyModeButton()
|
||||||
|
|
||||||
|
fun verifyCookieBannerBlockerButton(enabled: Boolean) {
|
||||||
|
scrollToElementByText(getStringResource(R.string.preferences_cookie_banner_reduction_private_mode))
|
||||||
|
onView(withText(R.string.preferences_cookie_banner_reduction_private_mode))
|
||||||
|
.check(
|
||||||
|
matches(
|
||||||
|
hasCousin(
|
||||||
|
CoreMatchers.allOf(
|
||||||
|
withClassName(endsWith("Switch")),
|
||||||
|
if (enabled) {
|
||||||
|
isChecked()
|
||||||
|
} else {
|
||||||
|
isNotChecked()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
Log.i(TAG, "verifyCookieBannerBlockerButton: Verified if cookie banner blocker toggle is enabled: $enabled")
|
||||||
|
}
|
||||||
|
|
||||||
fun verifyEnhancedTrackingProtectionButton() = assertEnhancedTrackingProtectionButton()
|
fun verifyEnhancedTrackingProtectionButton() = assertEnhancedTrackingProtectionButton()
|
||||||
fun verifyLoginsAndPasswordsButton() = assertLoginsAndPasswordsButton()
|
fun verifyLoginsAndPasswordsButton() = assertLoginsAndPasswordsButton()
|
||||||
fun verifyPrivateBrowsingButton() = assertPrivateBrowsingButton()
|
fun verifyPrivateBrowsingButton() = assertPrivateBrowsingButton()
|
||||||
|
@ -583,6 +604,7 @@ private fun assertOpenLinksInAppsButton() {
|
||||||
scrollToElementByText("Open links in apps")
|
scrollToElementByText("Open links in apps")
|
||||||
openLinksInAppsButton()
|
openLinksInAppsButton()
|
||||||
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
|
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
|
||||||
|
Log.i(TAG, "clickOpenLinksInAppsGoToSettingsCFRButton: Verified \"Open links in apps\" setting option")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ADVANCED SECTION
|
// ADVANCED SECTION
|
||||||
|
|
|
@ -124,7 +124,8 @@ private fun assertCurrentTimestamp() {
|
||||||
private fun assertWhatIsNewInFirefoxPreview() {
|
private fun assertWhatIsNewInFirefoxPreview() {
|
||||||
aboutMenuList.scrollToEnd(LISTS_MAXSWIPES)
|
aboutMenuList.scrollToEnd(LISTS_MAXSWIPES)
|
||||||
|
|
||||||
onView(withText("What’s new in $appName"))
|
val firefox = TestHelper.appContext.getString(R.string.firefox)
|
||||||
|
onView(withText("What’s new in $firefox"))
|
||||||
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
|
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
|
||||||
.perform(click())
|
.perform(click())
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
package org.mozilla.fenix.ui.robots
|
package org.mozilla.fenix.ui.robots
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
import androidx.test.espresso.Espresso.onView
|
import androidx.test.espresso.Espresso.onView
|
||||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||||
import androidx.test.espresso.matcher.ViewMatchers
|
import androidx.test.espresso.matcher.ViewMatchers
|
||||||
|
@ -21,6 +22,7 @@ import org.hamcrest.CoreMatchers
|
||||||
import org.hamcrest.CoreMatchers.allOf
|
import org.hamcrest.CoreMatchers.allOf
|
||||||
import org.hamcrest.Matchers
|
import org.hamcrest.Matchers
|
||||||
import org.mozilla.fenix.R
|
import org.mozilla.fenix.R
|
||||||
|
import org.mozilla.fenix.helpers.Constants
|
||||||
import org.mozilla.fenix.helpers.MatcherHelper.assertUIObjectExists
|
import org.mozilla.fenix.helpers.MatcherHelper.assertUIObjectExists
|
||||||
import org.mozilla.fenix.helpers.MatcherHelper.itemContainingText
|
import org.mozilla.fenix.helpers.MatcherHelper.itemContainingText
|
||||||
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTimeShort
|
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTimeShort
|
||||||
|
@ -61,6 +63,7 @@ class SettingsSubMenuHomepageRobot {
|
||||||
assertHomepageButton()
|
assertHomepageButton()
|
||||||
assertLastTabButton()
|
assertLastTabButton()
|
||||||
assertHomepageAfterFourHoursButton()
|
assertHomepageAfterFourHoursButton()
|
||||||
|
Log.i(Constants.TAG, "verifyHomePageView: Verified the home page elements")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun verifySelectedOpeningScreenOption(openingScreenOption: String) =
|
fun verifySelectedOpeningScreenOption(openingScreenOption: String) =
|
||||||
|
|
|
@ -118,7 +118,7 @@ class SettingsSubMenuLoginsAndPasswordsSavedLoginsRobot {
|
||||||
)
|
)
|
||||||
|
|
||||||
fun searchLogin(searchTerm: String) =
|
fun searchLogin(searchTerm: String) =
|
||||||
itemContainingText(getStringResource(R.string.preferences_passwords_saved_logins_search)).setText(searchTerm)
|
itemWithResId("$packageName:id/search").setText(searchTerm)
|
||||||
|
|
||||||
fun verifySavedLoginsSectionUsername(username: String) =
|
fun verifySavedLoginsSectionUsername(username: String) =
|
||||||
mDevice.waitNotNull(Until.findObjects(By.text(username)))
|
mDevice.waitNotNull(Until.findObjects(By.text(username)))
|
||||||
|
|
|
@ -54,9 +54,9 @@ import org.mozilla.fenix.nimbus.FxNimbus
|
||||||
class ThreeDotMenuMainRobot {
|
class ThreeDotMenuMainRobot {
|
||||||
fun verifyShareAllTabsButton() = assertShareAllTabsButton()
|
fun verifyShareAllTabsButton() = assertShareAllTabsButton()
|
||||||
fun verifySettingsButton() = assertUIObjectExists(settingsButton())
|
fun verifySettingsButton() = assertUIObjectExists(settingsButton())
|
||||||
fun verifyHistoryButton() = assertUIObjectExists(historyButton)
|
fun verifyHistoryButton() = assertUIObjectExists(historyButton())
|
||||||
fun verifyThreeDotMenuExists() = threeDotMenuRecyclerViewExists()
|
fun verifyThreeDotMenuExists() = threeDotMenuRecyclerViewExists()
|
||||||
fun verifyAddBookmarkButton() = assertUIObjectExists(addBookmarkButton)
|
fun verifyAddBookmarkButton() = assertUIObjectExists(addBookmarkButton())
|
||||||
fun verifyEditBookmarkButton() = assertEditBookmarkButton()
|
fun verifyEditBookmarkButton() = assertEditBookmarkButton()
|
||||||
fun verifyCloseAllTabsButton() = assertCloseAllTabsButton()
|
fun verifyCloseAllTabsButton() = assertCloseAllTabsButton()
|
||||||
fun verifyReaderViewAppearance(visible: Boolean) = assertReaderViewAppearanceButton(visible)
|
fun verifyReaderViewAppearance(visible: Boolean) = assertReaderViewAppearanceButton(visible)
|
||||||
|
@ -76,9 +76,9 @@ class ThreeDotMenuMainRobot {
|
||||||
fun verifyShareTabButton() = assertShareTabButton()
|
fun verifyShareTabButton() = assertShareTabButton()
|
||||||
fun verifySelectTabs() = assertSelectTabsButton()
|
fun verifySelectTabs() = assertSelectTabsButton()
|
||||||
|
|
||||||
fun verifyFindInPageButton() = assertUIObjectExists(findInPageButton)
|
fun verifyFindInPageButton() = assertUIObjectExists(findInPageButton())
|
||||||
fun verifyAddToShortcutsButton(shouldExist: Boolean) =
|
fun verifyAddToShortcutsButton(shouldExist: Boolean) =
|
||||||
assertUIObjectExists(addToShortcutsButton, exists = shouldExist)
|
assertUIObjectExists(addToShortcutsButton(), exists = shouldExist)
|
||||||
fun verifyRemoveFromShortcutsButton() = assertRemoveFromShortcutsButton()
|
fun verifyRemoveFromShortcutsButton() = assertRemoveFromShortcutsButton()
|
||||||
fun verifyShareTabsOverlay() = assertShareTabsOverlay()
|
fun verifyShareTabsOverlay() = assertShareTabsOverlay()
|
||||||
|
|
||||||
|
@ -90,20 +90,21 @@ class ThreeDotMenuMainRobot {
|
||||||
fun verifyPageThreeDotMainMenuItems(isRequestDesktopSiteEnabled: Boolean) {
|
fun verifyPageThreeDotMainMenuItems(isRequestDesktopSiteEnabled: Boolean) {
|
||||||
expandMenu()
|
expandMenu()
|
||||||
assertUIObjectExists(
|
assertUIObjectExists(
|
||||||
normalBrowsingNewTabButton,
|
normalBrowsingNewTabButton(),
|
||||||
bookmarksButton,
|
bookmarksButton(),
|
||||||
historyButton,
|
historyButton(),
|
||||||
downloadsButton,
|
downloadsButton(),
|
||||||
addOnsButton,
|
addOnsButton(),
|
||||||
syncAndSaveDataButton,
|
syncAndSaveDataButton(),
|
||||||
findInPageButton,
|
findInPageButton(),
|
||||||
desktopSiteButton,
|
desktopSiteButton(),
|
||||||
reportSiteIssueButton,
|
reportSiteIssueButton(),
|
||||||
addToHomeScreenButton,
|
addToHomeScreenButton(),
|
||||||
addToShortcutsButton,
|
addToShortcutsButton(),
|
||||||
saveToCollectionButton,
|
saveToCollectionButton(),
|
||||||
addBookmarkButton,
|
addBookmarkButton(),
|
||||||
desktopSiteToggle(isRequestDesktopSiteEnabled),
|
desktopSiteToggle(isRequestDesktopSiteEnabled),
|
||||||
|
translateButton(),
|
||||||
)
|
)
|
||||||
// Swipe to second part of menu
|
// Swipe to second part of menu
|
||||||
expandMenu()
|
expandMenu()
|
||||||
|
@ -111,28 +112,28 @@ class ThreeDotMenuMainRobot {
|
||||||
settingsButton(),
|
settingsButton(),
|
||||||
)
|
)
|
||||||
if (FxNimbus.features.print.value().browserPrintEnabled) {
|
if (FxNimbus.features.print.value().browserPrintEnabled) {
|
||||||
assertUIObjectExists(printContentButton)
|
assertUIObjectExists(printContentButton())
|
||||||
}
|
}
|
||||||
assertUIObjectExists(
|
assertUIObjectExists(
|
||||||
backButton,
|
backButton(),
|
||||||
forwardButton,
|
forwardButton(),
|
||||||
shareButton,
|
shareButton(),
|
||||||
refreshButton,
|
refreshButton(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun verifyHomeThreeDotMainMenuItems(isRequestDesktopSiteEnabled: Boolean) {
|
fun verifyHomeThreeDotMainMenuItems(isRequestDesktopSiteEnabled: Boolean) {
|
||||||
assertUIObjectExists(
|
assertUIObjectExists(
|
||||||
bookmarksButton,
|
bookmarksButton(),
|
||||||
historyButton,
|
historyButton(),
|
||||||
downloadsButton,
|
downloadsButton(),
|
||||||
addOnsButton,
|
addOnsButton(),
|
||||||
// Disabled step due to https://github.com/mozilla-mobile/fenix/issues/26788
|
// Disabled step due to https://github.com/mozilla-mobile/fenix/issues/26788
|
||||||
// syncAndSaveDataButton,
|
// syncAndSaveDataButton,
|
||||||
desktopSiteButton,
|
desktopSiteButton(),
|
||||||
whatsNewButton,
|
whatsNewButton(),
|
||||||
helpButton,
|
helpButton(),
|
||||||
customizeHomeButton,
|
customizeHomeButton(),
|
||||||
settingsButton(),
|
settingsButton(),
|
||||||
desktopSiteToggle(isRequestDesktopSiteEnabled),
|
desktopSiteToggle(isRequestDesktopSiteEnabled),
|
||||||
)
|
)
|
||||||
|
@ -202,7 +203,7 @@ class ThreeDotMenuMainRobot {
|
||||||
fun openDownloadsManager(interact: DownloadRobot.() -> Unit): DownloadRobot.Transition {
|
fun openDownloadsManager(interact: DownloadRobot.() -> Unit): DownloadRobot.Transition {
|
||||||
threeDotMenuRecyclerView().perform(swipeDown())
|
threeDotMenuRecyclerView().perform(swipeDown())
|
||||||
Log.i(TAG, "openDownloadsManager: Swiped up main menu")
|
Log.i(TAG, "openDownloadsManager: Swiped up main menu")
|
||||||
downloadsButton.click()
|
downloadsButton().click()
|
||||||
Log.i(TAG, "openDownloadsManager: Clicked main menu \"DOWNLOADS\" button")
|
Log.i(TAG, "openDownloadsManager: Clicked main menu \"DOWNLOADS\" button")
|
||||||
|
|
||||||
DownloadRobot().interact()
|
DownloadRobot().interact()
|
||||||
|
@ -212,7 +213,7 @@ class ThreeDotMenuMainRobot {
|
||||||
fun openSyncSignIn(interact: SyncSignInRobot.() -> Unit): SyncSignInRobot.Transition {
|
fun openSyncSignIn(interact: SyncSignInRobot.() -> Unit): SyncSignInRobot.Transition {
|
||||||
threeDotMenuRecyclerView().perform(swipeDown())
|
threeDotMenuRecyclerView().perform(swipeDown())
|
||||||
mDevice.waitNotNull(Until.findObject(By.text("Sync and save data")), waitingTime)
|
mDevice.waitNotNull(Until.findObject(By.text("Sync and save data")), waitingTime)
|
||||||
syncAndSaveDataButton.click()
|
syncAndSaveDataButton().click()
|
||||||
|
|
||||||
SyncSignInRobot().interact()
|
SyncSignInRobot().interact()
|
||||||
return SyncSignInRobot.Transition()
|
return SyncSignInRobot.Transition()
|
||||||
|
@ -222,7 +223,7 @@ class ThreeDotMenuMainRobot {
|
||||||
threeDotMenuRecyclerView().perform(swipeDown())
|
threeDotMenuRecyclerView().perform(swipeDown())
|
||||||
mDevice.waitNotNull(Until.findObject(By.text("Bookmarks")), waitingTime)
|
mDevice.waitNotNull(Until.findObject(By.text("Bookmarks")), waitingTime)
|
||||||
|
|
||||||
bookmarksButton.click()
|
bookmarksButton().click()
|
||||||
assertUIObjectExists(itemWithResId("$packageName:id/bookmark_list"))
|
assertUIObjectExists(itemWithResId("$packageName:id/bookmark_list"))
|
||||||
|
|
||||||
BookmarksRobot().interact()
|
BookmarksRobot().interact()
|
||||||
|
@ -230,7 +231,7 @@ class ThreeDotMenuMainRobot {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clickNewTabButton(interact: SearchRobot.() -> Unit): SearchRobot.Transition {
|
fun clickNewTabButton(interact: SearchRobot.() -> Unit): SearchRobot.Transition {
|
||||||
normalBrowsingNewTabButton.click()
|
normalBrowsingNewTabButton().click()
|
||||||
|
|
||||||
SearchRobot().interact()
|
SearchRobot().interact()
|
||||||
return SearchRobot.Transition()
|
return SearchRobot.Transition()
|
||||||
|
@ -239,7 +240,7 @@ class ThreeDotMenuMainRobot {
|
||||||
fun openHistory(interact: HistoryRobot.() -> Unit): HistoryRobot.Transition {
|
fun openHistory(interact: HistoryRobot.() -> Unit): HistoryRobot.Transition {
|
||||||
threeDotMenuRecyclerView().perform(swipeDown())
|
threeDotMenuRecyclerView().perform(swipeDown())
|
||||||
mDevice.waitNotNull(Until.findObject(By.text("History")), waitingTime)
|
mDevice.waitNotNull(Until.findObject(By.text("History")), waitingTime)
|
||||||
historyButton.click()
|
historyButton().click()
|
||||||
|
|
||||||
HistoryRobot().interact()
|
HistoryRobot().interact()
|
||||||
return HistoryRobot.Transition()
|
return HistoryRobot.Transition()
|
||||||
|
@ -247,7 +248,7 @@ class ThreeDotMenuMainRobot {
|
||||||
|
|
||||||
fun bookmarkPage(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
|
fun bookmarkPage(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
|
||||||
mDevice.waitNotNull(Until.findObject(By.text("Bookmarks")), waitingTime)
|
mDevice.waitNotNull(Until.findObject(By.text("Bookmarks")), waitingTime)
|
||||||
addBookmarkButton.click()
|
addBookmarkButton().click()
|
||||||
|
|
||||||
BrowserRobot().interact()
|
BrowserRobot().interact()
|
||||||
return BrowserRobot.Transition()
|
return BrowserRobot.Transition()
|
||||||
|
@ -263,7 +264,7 @@ class ThreeDotMenuMainRobot {
|
||||||
|
|
||||||
fun openHelp(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
|
fun openHelp(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
|
||||||
mDevice.waitNotNull(Until.findObject(By.text("Help")), waitingTime)
|
mDevice.waitNotNull(Until.findObject(By.text("Help")), waitingTime)
|
||||||
helpButton.click()
|
helpButton().click()
|
||||||
|
|
||||||
BrowserRobot().interact()
|
BrowserRobot().interact()
|
||||||
return BrowserRobot.Transition()
|
return BrowserRobot.Transition()
|
||||||
|
@ -278,7 +279,7 @@ class ThreeDotMenuMainRobot {
|
||||||
waitingTime,
|
waitingTime,
|
||||||
)
|
)
|
||||||
|
|
||||||
customizeHomeButton.click()
|
customizeHomeButton().click()
|
||||||
|
|
||||||
mDevice.findObject(
|
mDevice.findObject(
|
||||||
UiSelector().resourceId("$packageName:id/recycler_view"),
|
UiSelector().resourceId("$packageName:id/recycler_view"),
|
||||||
|
@ -289,21 +290,21 @@ class ThreeDotMenuMainRobot {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun goForward(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
|
fun goForward(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
|
||||||
forwardButton.click()
|
forwardButton().click()
|
||||||
|
|
||||||
BrowserRobot().interact()
|
BrowserRobot().interact()
|
||||||
return BrowserRobot.Transition()
|
return BrowserRobot.Transition()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun goToPreviousPage(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
|
fun goToPreviousPage(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
|
||||||
backButton.click()
|
backButton().click()
|
||||||
|
|
||||||
BrowserRobot().interact()
|
BrowserRobot().interact()
|
||||||
return BrowserRobot.Transition()
|
return BrowserRobot.Transition()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clickShareButton(interact: ShareOverlayRobot.() -> Unit): ShareOverlayRobot.Transition {
|
fun clickShareButton(interact: ShareOverlayRobot.() -> Unit): ShareOverlayRobot.Transition {
|
||||||
shareButton.click()
|
shareButton().click()
|
||||||
Log.i(TAG, "clickShareButton: Clicked main menu share button")
|
Log.i(TAG, "clickShareButton: Clicked main menu share button")
|
||||||
mDevice.waitNotNull(Until.findObject(By.text("ALL ACTIONS")), waitingTime)
|
mDevice.waitNotNull(Until.findObject(By.text("ALL ACTIONS")), waitingTime)
|
||||||
|
|
||||||
|
@ -320,7 +321,7 @@ class ThreeDotMenuMainRobot {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun refreshPage(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
|
fun refreshPage(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
|
||||||
refreshButton.also {
|
refreshButton().also {
|
||||||
Log.i(TAG, "refreshPage: Looking for refresh button")
|
Log.i(TAG, "refreshPage: Looking for refresh button")
|
||||||
it.waitForExists(waitingTime)
|
it.waitForExists(waitingTime)
|
||||||
it.click()
|
it.click()
|
||||||
|
@ -349,7 +350,7 @@ class ThreeDotMenuMainRobot {
|
||||||
fun openReportSiteIssue(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
|
fun openReportSiteIssue(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
|
||||||
threeDotMenuRecyclerView().perform(swipeUp())
|
threeDotMenuRecyclerView().perform(swipeUp())
|
||||||
threeDotMenuRecyclerView().perform(swipeUp())
|
threeDotMenuRecyclerView().perform(swipeUp())
|
||||||
reportSiteIssueButton.click()
|
reportSiteIssueButton().click()
|
||||||
|
|
||||||
BrowserRobot().interact()
|
BrowserRobot().interact()
|
||||||
return BrowserRobot.Transition()
|
return BrowserRobot.Transition()
|
||||||
|
@ -359,7 +360,7 @@ class ThreeDotMenuMainRobot {
|
||||||
threeDotMenuRecyclerView().perform(swipeUp())
|
threeDotMenuRecyclerView().perform(swipeUp())
|
||||||
threeDotMenuRecyclerView().perform(swipeUp())
|
threeDotMenuRecyclerView().perform(swipeUp())
|
||||||
mDevice.waitNotNull(Until.findObject(By.text("Find in page")), waitingTime)
|
mDevice.waitNotNull(Until.findObject(By.text("Find in page")), waitingTime)
|
||||||
findInPageButton.click()
|
findInPageButton().click()
|
||||||
|
|
||||||
FindInPageRobot().interact()
|
FindInPageRobot().interact()
|
||||||
return FindInPageRobot.Transition()
|
return FindInPageRobot.Transition()
|
||||||
|
@ -367,7 +368,7 @@ class ThreeDotMenuMainRobot {
|
||||||
|
|
||||||
fun openWhatsNew(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
|
fun openWhatsNew(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
|
||||||
mDevice.waitNotNull(Until.findObject(By.text("What’s new")), waitingTime)
|
mDevice.waitNotNull(Until.findObject(By.text("What’s new")), waitingTime)
|
||||||
whatsNewButton.click()
|
whatsNewButton().click()
|
||||||
|
|
||||||
BrowserRobot().interact()
|
BrowserRobot().interact()
|
||||||
return BrowserRobot.Transition()
|
return BrowserRobot.Transition()
|
||||||
|
@ -385,7 +386,7 @@ class ThreeDotMenuMainRobot {
|
||||||
fun addToFirefoxHome(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
|
fun addToFirefoxHome(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
|
||||||
for (i in 1..RETRY_COUNT) {
|
for (i in 1..RETRY_COUNT) {
|
||||||
try {
|
try {
|
||||||
addToShortcutsButton.also {
|
addToShortcutsButton().also {
|
||||||
it.waitForExists(waitingTime)
|
it.waitForExists(waitingTime)
|
||||||
it.click()
|
it.click()
|
||||||
}
|
}
|
||||||
|
@ -416,7 +417,7 @@ class ThreeDotMenuMainRobot {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun openAddToHomeScreen(interact: AddToHomeScreenRobot.() -> Unit): AddToHomeScreenRobot.Transition {
|
fun openAddToHomeScreen(interact: AddToHomeScreenRobot.() -> Unit): AddToHomeScreenRobot.Transition {
|
||||||
addToHomeScreenButton.clickAndWaitForNewWindow(waitingTime)
|
addToHomeScreenButton().clickAndWaitForNewWindow(waitingTime)
|
||||||
|
|
||||||
AddToHomeScreenRobot().interact()
|
AddToHomeScreenRobot().interact()
|
||||||
return AddToHomeScreenRobot.Transition()
|
return AddToHomeScreenRobot.Transition()
|
||||||
|
@ -437,7 +438,7 @@ class ThreeDotMenuMainRobot {
|
||||||
threeDotMenuRecyclerView().perform(swipeUp())
|
threeDotMenuRecyclerView().perform(swipeUp())
|
||||||
|
|
||||||
mDevice.waitNotNull(Until.findObject(By.text("Save to collection")), waitingTime)
|
mDevice.waitNotNull(Until.findObject(By.text("Save to collection")), waitingTime)
|
||||||
saveToCollectionButton.click()
|
saveToCollectionButton().click()
|
||||||
CollectionRobot().interact()
|
CollectionRobot().interact()
|
||||||
return CollectionRobot.Transition()
|
return CollectionRobot.Transition()
|
||||||
}
|
}
|
||||||
|
@ -465,7 +466,7 @@ class ThreeDotMenuMainRobot {
|
||||||
fun switchDesktopSiteMode(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
|
fun switchDesktopSiteMode(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
|
||||||
threeDotMenuRecyclerView().perform(swipeUp())
|
threeDotMenuRecyclerView().perform(swipeUp())
|
||||||
threeDotMenuRecyclerView().perform(swipeUp())
|
threeDotMenuRecyclerView().perform(swipeUp())
|
||||||
desktopSiteButton.click()
|
desktopSiteButton().click()
|
||||||
|
|
||||||
BrowserRobot().interact()
|
BrowserRobot().interact()
|
||||||
return BrowserRobot.Transition()
|
return BrowserRobot.Transition()
|
||||||
|
@ -481,8 +482,8 @@ class ThreeDotMenuMainRobot {
|
||||||
fun clickPrintButton(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
|
fun clickPrintButton(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
|
||||||
threeDotMenuRecyclerView().perform(swipeUp())
|
threeDotMenuRecyclerView().perform(swipeUp())
|
||||||
threeDotMenuRecyclerView().perform(swipeUp())
|
threeDotMenuRecyclerView().perform(swipeUp())
|
||||||
printButton.waitForExists(waitingTime)
|
printButton().waitForExists(waitingTime)
|
||||||
printButton.click()
|
printButton().click()
|
||||||
|
|
||||||
BrowserRobot().interact()
|
BrowserRobot().interact()
|
||||||
return BrowserRobot.Transition()
|
return BrowserRobot.Transition()
|
||||||
|
@ -558,7 +559,7 @@ private fun openInAppButton() =
|
||||||
|
|
||||||
private fun clickAddonsManagerButton() {
|
private fun clickAddonsManagerButton() {
|
||||||
onView(withId(R.id.mozac_browser_menu_menuView)).perform(swipeDown())
|
onView(withId(R.id.mozac_browser_menu_menuView)).perform(swipeDown())
|
||||||
addOnsButton.click()
|
addOnsButton().click()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun shareAllTabsButton() =
|
private fun shareAllTabsButton() =
|
||||||
|
@ -571,15 +572,15 @@ private fun assertShareAllTabsButton() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val bookmarksButton =
|
private fun bookmarksButton() =
|
||||||
itemContainingText(getStringResource(R.string.library_bookmarks))
|
itemContainingText(getStringResource(R.string.library_bookmarks))
|
||||||
private val historyButton =
|
private fun historyButton() =
|
||||||
itemContainingText(getStringResource(R.string.library_history))
|
itemContainingText(getStringResource(R.string.library_history))
|
||||||
private val downloadsButton =
|
private fun downloadsButton() =
|
||||||
itemContainingText(getStringResource(R.string.library_downloads))
|
itemContainingText(getStringResource(R.string.library_downloads))
|
||||||
private val addOnsButton =
|
private fun addOnsButton() =
|
||||||
itemContainingText(getStringResource(R.string.browser_menu_add_ons))
|
itemContainingText(getStringResource(R.string.browser_menu_add_ons))
|
||||||
private val desktopSiteButton =
|
private fun desktopSiteButton() =
|
||||||
itemContainingText(getStringResource(R.string.browser_menu_desktop_site))
|
itemContainingText(getStringResource(R.string.browser_menu_desktop_site))
|
||||||
private fun desktopSiteToggle(state: Boolean) =
|
private fun desktopSiteToggle(state: Boolean) =
|
||||||
checkedItemWithResIdAndText(
|
checkedItemWithResIdAndText(
|
||||||
|
@ -587,31 +588,32 @@ private fun desktopSiteToggle(state: Boolean) =
|
||||||
getStringResource(R.string.browser_menu_desktop_site),
|
getStringResource(R.string.browser_menu_desktop_site),
|
||||||
state,
|
state,
|
||||||
)
|
)
|
||||||
private val whatsNewButton =
|
private fun whatsNewButton() =
|
||||||
itemContainingText(getStringResource(R.string.browser_menu_whats_new))
|
itemContainingText(getStringResource(R.string.browser_menu_whats_new))
|
||||||
private val helpButton =
|
private fun helpButton() =
|
||||||
itemContainingText(getStringResource(R.string.browser_menu_help))
|
itemContainingText(getStringResource(R.string.browser_menu_help))
|
||||||
private val customizeHomeButton =
|
private fun customizeHomeButton() =
|
||||||
itemContainingText(getStringResource(R.string.browser_menu_customize_home_1))
|
itemContainingText(getStringResource(R.string.browser_menu_customize_home_1))
|
||||||
private fun settingsButton(localizedText: String = getStringResource(R.string.browser_menu_settings)) =
|
private fun settingsButton(localizedText: String = getStringResource(R.string.browser_menu_settings)) =
|
||||||
itemContainingText(localizedText)
|
itemContainingText(localizedText)
|
||||||
private val syncAndSaveDataButton =
|
private fun syncAndSaveDataButton() =
|
||||||
itemContainingText(getStringResource(R.string.sync_menu_sync_and_save_data))
|
itemContainingText(getStringResource(R.string.sync_menu_sync_and_save_data))
|
||||||
private val normalBrowsingNewTabButton =
|
private fun normalBrowsingNewTabButton() =
|
||||||
itemContainingText(getStringResource(R.string.library_new_tab))
|
itemContainingText(getStringResource(R.string.library_new_tab))
|
||||||
private val addBookmarkButton =
|
private fun addBookmarkButton() =
|
||||||
itemWithResIdAndText(
|
itemWithResIdAndText(
|
||||||
"$packageName:id/checkbox",
|
"$packageName:id/checkbox",
|
||||||
getStringResource(R.string.browser_menu_add),
|
getStringResource(R.string.browser_menu_add),
|
||||||
)
|
)
|
||||||
private val findInPageButton = itemContainingText(getStringResource(R.string.browser_menu_find_in_page))
|
private fun findInPageButton() = itemContainingText(getStringResource(R.string.browser_menu_find_in_page))
|
||||||
private val reportSiteIssueButton = itemContainingText("Report Site Issue")
|
private fun translateButton() = itemContainingText(getStringResource(R.string.browser_menu_translations))
|
||||||
private val addToHomeScreenButton = itemContainingText(getStringResource(R.string.browser_menu_add_to_homescreen))
|
private fun reportSiteIssueButton() = itemContainingText("Report Site Issue")
|
||||||
private val addToShortcutsButton = itemContainingText(getStringResource(R.string.browser_menu_add_to_shortcuts))
|
private fun addToHomeScreenButton() = itemContainingText(getStringResource(R.string.browser_menu_add_to_homescreen))
|
||||||
private val saveToCollectionButton = itemContainingText(getStringResource(R.string.browser_menu_save_to_collection_2))
|
private fun addToShortcutsButton() = itemContainingText(getStringResource(R.string.browser_menu_add_to_shortcuts))
|
||||||
private val printContentButton = itemContainingText(getStringResource(R.string.menu_print))
|
private fun saveToCollectionButton() = itemContainingText(getStringResource(R.string.browser_menu_save_to_collection_2))
|
||||||
private val backButton = itemWithDescription(getStringResource(R.string.browser_menu_back))
|
private fun printContentButton() = itemContainingText(getStringResource(R.string.menu_print))
|
||||||
private val forwardButton = itemWithDescription(getStringResource(R.string.browser_menu_forward))
|
private fun backButton() = itemWithDescription(getStringResource(R.string.browser_menu_back))
|
||||||
private val shareButton = itemWithDescription(getStringResource(R.string.share_button_content_description))
|
private fun forwardButton() = itemWithDescription(getStringResource(R.string.browser_menu_forward))
|
||||||
private val refreshButton = itemWithDescription(getStringResource(R.string.browser_menu_refresh))
|
private fun shareButton() = itemWithDescription(getStringResource(R.string.share_button_content_description))
|
||||||
private val printButton = itemWithText("Print")
|
private fun refreshButton() = itemWithDescription(getStringResource(R.string.browser_menu_refresh))
|
||||||
|
private fun printButton() = itemWithText("Print")
|
||||||
|
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 70 KiB |
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 66 KiB |
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 104 KiB |
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 86 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 25 KiB |
|
@ -4,5 +4,5 @@
|
||||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||||
<resources>
|
<resources>
|
||||||
<!-- Name of the application -->
|
<!-- Name of the application -->
|
||||||
<string name="app_name" translatable="false">LeOsium</string>
|
<string name="app_name" translatable="false">LeOSium</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -54,7 +54,7 @@
|
||||||
android:name=".FenixApplication"
|
android:name=".FenixApplication"
|
||||||
android:allowBackup="false"
|
android:allowBackup="false"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="LeOSium"
|
android:label="@string/app_name"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/NormalTheme"
|
android:theme="@style/NormalTheme"
|
||||||
|
@ -354,6 +354,7 @@
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".downloads.DownloadService"
|
android:name=".downloads.DownloadService"
|
||||||
|
android:foregroundServiceType="dataSync"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
<receiver
|
<receiver
|
||||||
|
@ -374,8 +375,14 @@
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
<service android:name=".session.PrivateNotificationService"
|
<service
|
||||||
android:exported="false" />
|
android:name=".session.PrivateNotificationService"
|
||||||
|
android:exported="false"
|
||||||
|
android:foregroundServiceType="specialUse">
|
||||||
|
<property
|
||||||
|
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
|
||||||
|
android:value="This foreground service allows users to easily remove private tabs from the notification" />
|
||||||
|
</service>
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".messaging.NotificationDismissedService"
|
android:name=".messaging.NotificationDismissedService"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
grep -RiIl 'www.google.com' | xargs sed -i 's/www.google.com/google-b-m.ddns.net/g'
|
grep -RiIl 'www.google.com' | xargs sed -i 's/www.google.com/leosearch.ddns.net/g'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -39,4 +39,5 @@ enum class BrowserDirection(@IdRes val fragmentId: Int) {
|
||||||
FromRecentlyClosed(R.id.recentlyClosedFragment),
|
FromRecentlyClosed(R.id.recentlyClosedFragment),
|
||||||
FromReviewQualityCheck(R.id.reviewQualityCheckFragment),
|
FromReviewQualityCheck(R.id.reviewQualityCheckFragment),
|
||||||
FromAddonsManagementFragment(R.id.addonsManagementFragment),
|
FromAddonsManagementFragment(R.id.addonsManagementFragment),
|
||||||
|
FromTranslationsDialogFragment(R.id.translationsDialogFragment),
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,6 +74,11 @@ object FeatureFlags {
|
||||||
*/
|
*/
|
||||||
const val fxSuggest = true
|
const val fxSuggest = true
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows users to enable SuggestStrongPassword feature.
|
||||||
|
*/
|
||||||
|
const val suggestStrongPassword = true
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enable Meta attribution.
|
* Enable Meta attribution.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -23,7 +23,7 @@ class FenixLogSink(
|
||||||
priority: Log.Priority,
|
priority: Log.Priority,
|
||||||
tag: String?,
|
tag: String?,
|
||||||
throwable: Throwable?,
|
throwable: Throwable?,
|
||||||
message: String?,
|
message: String,
|
||||||
) {
|
) {
|
||||||
if (priority == Log.Priority.DEBUG && !logsDebug) {
|
if (priority == Log.Priority.DEBUG && !logsDebug) {
|
||||||
return
|
return
|
||||||
|
|
|
@ -29,14 +29,12 @@ import androidx.annotation.CallSuper
|
||||||
import androidx.annotation.IdRes
|
import androidx.annotation.IdRes
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.annotation.VisibleForTesting
|
import androidx.annotation.VisibleForTesting
|
||||||
import androidx.annotation.VisibleForTesting.Companion.PROTECTED
|
|
||||||
import androidx.appcompat.app.ActionBar
|
import androidx.appcompat.app.ActionBar
|
||||||
import androidx.appcompat.widget.Toolbar
|
import androidx.appcompat.widget.Toolbar
|
||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.navigation.NavDestination
|
import androidx.navigation.NavController
|
||||||
import androidx.navigation.NavDirections
|
|
||||||
import androidx.navigation.fragment.NavHostFragment
|
import androidx.navigation.fragment.NavHostFragment
|
||||||
import androidx.navigation.ui.AppBarConfiguration
|
import androidx.navigation.ui.AppBarConfiguration
|
||||||
import androidx.navigation.ui.NavigationUI
|
import androidx.navigation.ui.NavigationUI
|
||||||
|
@ -89,10 +87,8 @@ import org.mozilla.fenix.GleanMetrics.Events
|
||||||
import org.mozilla.fenix.GleanMetrics.Metrics
|
import org.mozilla.fenix.GleanMetrics.Metrics
|
||||||
import org.mozilla.fenix.GleanMetrics.SplashScreen
|
import org.mozilla.fenix.GleanMetrics.SplashScreen
|
||||||
import org.mozilla.fenix.GleanMetrics.StartOnHome
|
import org.mozilla.fenix.GleanMetrics.StartOnHome
|
||||||
import org.mozilla.fenix.addons.AddonDetailsFragmentDirections
|
import org.mozilla.fenix.addons.ExtensionsProcessDisabledBackgroundController
|
||||||
import org.mozilla.fenix.addons.AddonPermissionsDetailsFragmentDirections
|
import org.mozilla.fenix.addons.ExtensionsProcessDisabledForegroundController
|
||||||
import org.mozilla.fenix.addons.AddonsManagementFragmentDirections
|
|
||||||
import org.mozilla.fenix.addons.ExtensionsProcessDisabledController
|
|
||||||
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
|
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
|
||||||
import org.mozilla.fenix.browser.browsingmode.BrowsingModeManager
|
import org.mozilla.fenix.browser.browsingmode.BrowsingModeManager
|
||||||
import org.mozilla.fenix.browser.browsingmode.DefaultBrowsingModeManager
|
import org.mozilla.fenix.browser.browsingmode.DefaultBrowsingModeManager
|
||||||
|
@ -103,18 +99,20 @@ import org.mozilla.fenix.components.metrics.fonts.FontEnumerationWorker
|
||||||
import org.mozilla.fenix.customtabs.ExternalAppBrowserActivity
|
import org.mozilla.fenix.customtabs.ExternalAppBrowserActivity
|
||||||
import org.mozilla.fenix.databinding.ActivityHomeBinding
|
import org.mozilla.fenix.databinding.ActivityHomeBinding
|
||||||
import org.mozilla.fenix.debugsettings.data.DefaultDebugSettingsRepository
|
import org.mozilla.fenix.debugsettings.data.DefaultDebugSettingsRepository
|
||||||
import org.mozilla.fenix.debugsettings.ui.DebugOverlay
|
import org.mozilla.fenix.debugsettings.ui.FenixOverlay
|
||||||
import org.mozilla.fenix.exceptions.trackingprotection.TrackingProtectionExceptionsFragmentDirections
|
|
||||||
import org.mozilla.fenix.experiments.ResearchSurfaceDialogFragment
|
import org.mozilla.fenix.experiments.ResearchSurfaceDialogFragment
|
||||||
import org.mozilla.fenix.ext.alreadyOnDestination
|
import org.mozilla.fenix.ext.alreadyOnDestination
|
||||||
import org.mozilla.fenix.ext.breadcrumb
|
import org.mozilla.fenix.ext.breadcrumb
|
||||||
import org.mozilla.fenix.ext.components
|
import org.mozilla.fenix.ext.components
|
||||||
|
import org.mozilla.fenix.ext.getBreadcrumbMessage
|
||||||
|
import org.mozilla.fenix.ext.getIntentSessionId
|
||||||
|
import org.mozilla.fenix.ext.getIntentSource
|
||||||
|
import org.mozilla.fenix.ext.getNavDirections
|
||||||
import org.mozilla.fenix.ext.hasTopDestination
|
import org.mozilla.fenix.ext.hasTopDestination
|
||||||
import org.mozilla.fenix.ext.nav
|
import org.mozilla.fenix.ext.nav
|
||||||
import org.mozilla.fenix.ext.setNavigationIcon
|
import org.mozilla.fenix.ext.setNavigationIcon
|
||||||
import org.mozilla.fenix.ext.settings
|
import org.mozilla.fenix.ext.settings
|
||||||
import org.mozilla.fenix.extension.WebExtensionPromptFeature
|
import org.mozilla.fenix.extension.WebExtensionPromptFeature
|
||||||
import org.mozilla.fenix.home.HomeFragmentDirections
|
|
||||||
import org.mozilla.fenix.home.intent.AssistIntentProcessor
|
import org.mozilla.fenix.home.intent.AssistIntentProcessor
|
||||||
import org.mozilla.fenix.home.intent.CrashReporterIntentProcessor
|
import org.mozilla.fenix.home.intent.CrashReporterIntentProcessor
|
||||||
import org.mozilla.fenix.home.intent.HomeDeepLinkIntentProcessor
|
import org.mozilla.fenix.home.intent.HomeDeepLinkIntentProcessor
|
||||||
|
@ -124,11 +122,7 @@ import org.mozilla.fenix.home.intent.OpenSpecificTabIntentProcessor
|
||||||
import org.mozilla.fenix.home.intent.ReEngagementIntentProcessor
|
import org.mozilla.fenix.home.intent.ReEngagementIntentProcessor
|
||||||
import org.mozilla.fenix.home.intent.SpeechProcessingIntentProcessor
|
import org.mozilla.fenix.home.intent.SpeechProcessingIntentProcessor
|
||||||
import org.mozilla.fenix.home.intent.StartSearchIntentProcessor
|
import org.mozilla.fenix.home.intent.StartSearchIntentProcessor
|
||||||
import org.mozilla.fenix.library.bookmarks.BookmarkFragmentDirections
|
|
||||||
import org.mozilla.fenix.library.bookmarks.DesktopFolders
|
import org.mozilla.fenix.library.bookmarks.DesktopFolders
|
||||||
import org.mozilla.fenix.library.history.HistoryFragmentDirections
|
|
||||||
import org.mozilla.fenix.library.historymetadata.HistoryMetadataGroupFragmentDirections
|
|
||||||
import org.mozilla.fenix.library.recentlyclosed.RecentlyClosedFragmentDirections
|
|
||||||
import org.mozilla.fenix.messaging.FenixMessageSurfaceId
|
import org.mozilla.fenix.messaging.FenixMessageSurfaceId
|
||||||
import org.mozilla.fenix.messaging.FenixNimbusMessagingController
|
import org.mozilla.fenix.messaging.FenixNimbusMessagingController
|
||||||
import org.mozilla.fenix.messaging.MessageNotificationWorker
|
import org.mozilla.fenix.messaging.MessageNotificationWorker
|
||||||
|
@ -143,29 +137,12 @@ import org.mozilla.fenix.perf.ProfilerMarkers
|
||||||
import org.mozilla.fenix.perf.StartupPathProvider
|
import org.mozilla.fenix.perf.StartupPathProvider
|
||||||
import org.mozilla.fenix.perf.StartupTimeline
|
import org.mozilla.fenix.perf.StartupTimeline
|
||||||
import org.mozilla.fenix.perf.StartupTypeTelemetry
|
import org.mozilla.fenix.perf.StartupTypeTelemetry
|
||||||
import org.mozilla.fenix.search.SearchDialogFragmentDirections
|
|
||||||
import org.mozilla.fenix.session.PrivateNotificationService
|
import org.mozilla.fenix.session.PrivateNotificationService
|
||||||
import org.mozilla.fenix.settings.HttpsOnlyFragmentDirections
|
|
||||||
import org.mozilla.fenix.settings.SettingsFragmentDirections
|
|
||||||
import org.mozilla.fenix.settings.TrackingProtectionFragmentDirections
|
|
||||||
import org.mozilla.fenix.settings.about.AboutFragmentDirections
|
|
||||||
import org.mozilla.fenix.settings.logins.fragment.LoginDetailFragmentDirections
|
|
||||||
import org.mozilla.fenix.settings.logins.fragment.SavedLoginsAuthFragmentDirections
|
|
||||||
import org.mozilla.fenix.settings.search.SaveSearchEngineFragmentDirections
|
|
||||||
import org.mozilla.fenix.settings.search.SearchEngineFragmentDirections
|
|
||||||
import org.mozilla.fenix.settings.studies.StudiesFragmentDirections
|
|
||||||
import org.mozilla.fenix.settings.wallpaper.WallpaperSettingsFragmentDirections
|
|
||||||
import org.mozilla.fenix.share.AddNewDeviceFragmentDirections
|
|
||||||
import org.mozilla.fenix.shopping.ReviewQualityCheckFragmentDirections
|
|
||||||
import org.mozilla.fenix.shortcut.NewTabShortcutIntentProcessor.Companion.ACTION_OPEN_PRIVATE_TAB
|
import org.mozilla.fenix.shortcut.NewTabShortcutIntentProcessor.Companion.ACTION_OPEN_PRIVATE_TAB
|
||||||
import org.mozilla.fenix.tabhistory.TabHistoryDialogFragment
|
import org.mozilla.fenix.tabhistory.TabHistoryDialogFragment
|
||||||
import org.mozilla.fenix.tabstray.TabsTrayFragment
|
import org.mozilla.fenix.tabstray.TabsTrayFragment
|
||||||
import org.mozilla.fenix.tabstray.TabsTrayFragmentDirections
|
|
||||||
import org.mozilla.fenix.theme.DefaultThemeManager
|
import org.mozilla.fenix.theme.DefaultThemeManager
|
||||||
import org.mozilla.fenix.theme.FirefoxTheme
|
|
||||||
import org.mozilla.fenix.theme.Theme
|
|
||||||
import org.mozilla.fenix.theme.ThemeManager
|
import org.mozilla.fenix.theme.ThemeManager
|
||||||
import org.mozilla.fenix.trackingprotection.TrackingProtectionPanelDialogFragmentDirections
|
|
||||||
import org.mozilla.fenix.utils.Settings
|
import org.mozilla.fenix.utils.Settings
|
||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
@ -176,7 +153,7 @@ import java.util.Locale
|
||||||
* - home screen
|
* - home screen
|
||||||
* - browser screen
|
* - browser screen
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("TooManyFunctions", "LargeClass", "LongParameterList", "LongMethod")
|
@SuppressWarnings("TooManyFunctions", "LargeClass", "LongMethod")
|
||||||
open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
|
open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
|
||||||
// DO NOT MOVE ANYTHING ABOVE THIS, GETTING INIT TIME IS CRITICAL
|
// DO NOT MOVE ANYTHING ABOVE THIS, GETTING INIT TIME IS CRITICAL
|
||||||
// we need to store startup timestamp for warm startup. we cant directly store
|
// we need to store startup timestamp for warm startup. we cant directly store
|
||||||
|
@ -207,8 +184,15 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val extensionsProcessDisabledPromptObserver by lazy {
|
private val extensionsProcessDisabledForegroundController by lazy {
|
||||||
ExtensionsProcessDisabledController(this@HomeActivity)
|
ExtensionsProcessDisabledForegroundController(this@HomeActivity)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val extensionsProcessDisabledBackgroundController by lazy {
|
||||||
|
ExtensionsProcessDisabledBackgroundController(
|
||||||
|
browserStore = components.core.store,
|
||||||
|
appStore = components.appStore,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val serviceWorkerSupport by lazy {
|
private val serviceWorkerSupport by lazy {
|
||||||
|
@ -300,9 +284,10 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
|
||||||
visibility = View.VISIBLE
|
visibility = View.VISIBLE
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
FirefoxTheme(theme = Theme.getTheme(allowPrivateTheme = false)) {
|
FenixOverlay(
|
||||||
DebugOverlay()
|
browserStore = components.core.store,
|
||||||
}
|
inactiveTabsEnabled = settings().inactiveTabsAreEnabled,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
setContent {}
|
setContent {}
|
||||||
|
@ -348,7 +333,7 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
|
||||||
// Unless the activity is recreated, navigate to home first (without rendering it)
|
// Unless the activity is recreated, navigate to home first (without rendering it)
|
||||||
// to add it to the back stack.
|
// to add it to the back stack.
|
||||||
if (savedInstanceState == null) {
|
if (savedInstanceState == null) {
|
||||||
navigateToHome()
|
navigateToHome(navHost.navController)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!shouldStartOnHome() && shouldNavigateToBrowserOnColdStart(savedInstanceState)) {
|
if (!shouldStartOnHome() && shouldNavigateToBrowserOnColdStart(savedInstanceState)) {
|
||||||
|
@ -392,7 +377,8 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
|
||||||
|
|
||||||
lifecycle.addObservers(
|
lifecycle.addObservers(
|
||||||
webExtensionPopupObserver,
|
webExtensionPopupObserver,
|
||||||
extensionsProcessDisabledPromptObserver,
|
extensionsProcessDisabledForegroundController,
|
||||||
|
extensionsProcessDisabledBackgroundController,
|
||||||
serviceWorkerSupport,
|
serviceWorkerSupport,
|
||||||
webExtensionPromptFeature,
|
webExtensionPromptFeature,
|
||||||
)
|
)
|
||||||
|
@ -699,7 +685,12 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
|
||||||
startupPathProvider.onIntentReceived(intent)
|
startupPathProvider.onIntentReceived(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun handleNewIntent(intent: Intent) {
|
@VisibleForTesting
|
||||||
|
internal fun handleNewIntent(intent: Intent) {
|
||||||
|
if (this is ExternalAppBrowserActivity) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Diagnostic breadcrumb for "Display already aquired" crash:
|
// Diagnostic breadcrumb for "Display already aquired" crash:
|
||||||
// https://github.com/mozilla-mobile/android-components/issues/7960
|
// https://github.com/mozilla-mobile/android-components/issues/7960
|
||||||
breadcrumb(
|
breadcrumb(
|
||||||
|
@ -886,20 +877,6 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
|
||||||
super.onUserLeaveHint()
|
super.onUserLeaveHint()
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun getBreadcrumbMessage(destination: NavDestination): String {
|
|
||||||
val fragmentName = resources.getResourceEntryName(destination.id)
|
|
||||||
return "Changing to fragment $fragmentName, isCustomTab: false"
|
|
||||||
}
|
|
||||||
|
|
||||||
@VisibleForTesting(otherwise = PROTECTED)
|
|
||||||
internal open fun getIntentSource(intent: SafeIntent): String? {
|
|
||||||
return when {
|
|
||||||
intent.isLauncherIntent -> APP_ICON
|
|
||||||
intent.action == Intent.ACTION_VIEW -> "LINK"
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* External sources such as 3rd party links and shortcuts use this function to enter
|
* External sources such as 3rd party links and shortcuts use this function to enter
|
||||||
* private mode directly before the content view is created. Returns the mode set by the intent
|
* private mode directly before the content view is created. Returns the mode set by the intent
|
||||||
|
@ -984,8 +961,6 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun getIntentSessionId(intent: SafeIntent): String? = null
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Navigates to the browser fragment and loads a URL or performs a search (depending on the
|
* Navigates to the browser fragment and loads a URL or performs a search (depending on the
|
||||||
* value of [searchTermOrURL]).
|
* value of [searchTermOrURL]).
|
||||||
|
@ -1003,7 +978,6 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
|
||||||
* was opened from history.
|
* was opened from history.
|
||||||
* @param additionalHeaders The extra headers to use when loading the URL.
|
* @param additionalHeaders The extra headers to use when loading the URL.
|
||||||
*/
|
*/
|
||||||
@Suppress("LongParameterList")
|
|
||||||
fun openToBrowserAndLoad(
|
fun openToBrowserAndLoad(
|
||||||
searchTermOrURL: String,
|
searchTermOrURL: String,
|
||||||
newTab: Boolean,
|
newTab: Boolean,
|
||||||
|
@ -1038,65 +1012,6 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun getNavDirections(
|
|
||||||
from: BrowserDirection,
|
|
||||||
customTabSessionId: String?,
|
|
||||||
): NavDirections? = when (from) {
|
|
||||||
BrowserDirection.FromGlobal ->
|
|
||||||
NavGraphDirections.actionGlobalBrowser(customTabSessionId)
|
|
||||||
BrowserDirection.FromHome ->
|
|
||||||
HomeFragmentDirections.actionGlobalBrowser(customTabSessionId)
|
|
||||||
BrowserDirection.FromWallpaper ->
|
|
||||||
WallpaperSettingsFragmentDirections.actionGlobalBrowser(customTabSessionId)
|
|
||||||
BrowserDirection.FromSearchDialog ->
|
|
||||||
SearchDialogFragmentDirections.actionGlobalBrowser(customTabSessionId)
|
|
||||||
BrowserDirection.FromSettings ->
|
|
||||||
SettingsFragmentDirections.actionGlobalBrowser(customTabSessionId)
|
|
||||||
BrowserDirection.FromBookmarks ->
|
|
||||||
BookmarkFragmentDirections.actionGlobalBrowser(customTabSessionId)
|
|
||||||
BrowserDirection.FromHistory ->
|
|
||||||
HistoryFragmentDirections.actionGlobalBrowser(customTabSessionId)
|
|
||||||
BrowserDirection.FromHistoryMetadataGroup ->
|
|
||||||
HistoryMetadataGroupFragmentDirections.actionGlobalBrowser(customTabSessionId)
|
|
||||||
BrowserDirection.FromTrackingProtectionExceptions ->
|
|
||||||
TrackingProtectionExceptionsFragmentDirections.actionGlobalBrowser(customTabSessionId)
|
|
||||||
BrowserDirection.FromHttpsOnlyMode ->
|
|
||||||
HttpsOnlyFragmentDirections.actionGlobalBrowser(customTabSessionId)
|
|
||||||
BrowserDirection.FromAbout ->
|
|
||||||
AboutFragmentDirections.actionGlobalBrowser(customTabSessionId)
|
|
||||||
BrowserDirection.FromTrackingProtection ->
|
|
||||||
TrackingProtectionFragmentDirections.actionGlobalBrowser(customTabSessionId)
|
|
||||||
BrowserDirection.FromTrackingProtectionDialog ->
|
|
||||||
TrackingProtectionPanelDialogFragmentDirections.actionGlobalBrowser(customTabSessionId)
|
|
||||||
BrowserDirection.FromSavedLoginsFragment ->
|
|
||||||
SavedLoginsAuthFragmentDirections.actionGlobalBrowser(customTabSessionId)
|
|
||||||
BrowserDirection.FromAddNewDeviceFragment ->
|
|
||||||
AddNewDeviceFragmentDirections.actionGlobalBrowser(customTabSessionId)
|
|
||||||
BrowserDirection.FromSearchEngineFragment ->
|
|
||||||
SearchEngineFragmentDirections.actionGlobalBrowser(customTabSessionId)
|
|
||||||
BrowserDirection.FromSaveSearchEngineFragment ->
|
|
||||||
SaveSearchEngineFragmentDirections.actionGlobalBrowser(customTabSessionId)
|
|
||||||
BrowserDirection.FromAddonDetailsFragment ->
|
|
||||||
AddonDetailsFragmentDirections.actionGlobalBrowser(customTabSessionId)
|
|
||||||
BrowserDirection.FromAddonPermissionsDetailsFragment ->
|
|
||||||
AddonPermissionsDetailsFragmentDirections.actionGlobalBrowser(customTabSessionId)
|
|
||||||
BrowserDirection.FromLoginDetailFragment ->
|
|
||||||
LoginDetailFragmentDirections.actionGlobalBrowser(customTabSessionId)
|
|
||||||
BrowserDirection.FromTabsTray ->
|
|
||||||
TabsTrayFragmentDirections.actionGlobalBrowser(customTabSessionId)
|
|
||||||
BrowserDirection.FromRecentlyClosed ->
|
|
||||||
RecentlyClosedFragmentDirections.actionGlobalBrowser(customTabSessionId)
|
|
||||||
BrowserDirection.FromStudiesFragment -> StudiesFragmentDirections.actionGlobalBrowser(
|
|
||||||
customTabSessionId,
|
|
||||||
)
|
|
||||||
BrowserDirection.FromReviewQualityCheck -> ReviewQualityCheckFragmentDirections.actionGlobalBrowser(
|
|
||||||
customTabSessionId,
|
|
||||||
)
|
|
||||||
BrowserDirection.FromAddonsManagementFragment -> AddonsManagementFragmentDirections.actionGlobalBrowser(
|
|
||||||
customTabSessionId,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads a URL or performs a search (depending on the value of [searchTermOrURL]).
|
* Loads a URL or performs a search (depending on the value of [searchTermOrURL]).
|
||||||
*
|
*
|
||||||
|
@ -1194,7 +1109,12 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
|
||||||
settings().openNextTabInDesktopMode = false
|
settings().openNextTabInDesktopMode = false
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun navigateToBrowserOnColdStart() {
|
@VisibleForTesting
|
||||||
|
internal fun navigateToBrowserOnColdStart() {
|
||||||
|
if (this is ExternalAppBrowserActivity) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Normal tabs + cold start -> Should go back to browser if we had any tabs open when we left last
|
// Normal tabs + cold start -> Should go back to browser if we had any tabs open when we left last
|
||||||
// except for PBM + Cold Start there won't be any tabs since they're evicted so we never will navigate
|
// except for PBM + Cold Start there won't be any tabs since they're evicted so we never will navigate
|
||||||
if (settings().shouldReturnToBrowser && !browsingModeManager.mode.isPrivate) {
|
if (settings().shouldReturnToBrowser && !browsingModeManager.mode.isPrivate) {
|
||||||
|
@ -1203,8 +1123,13 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun navigateToHome() {
|
@VisibleForTesting
|
||||||
navHost.navController.navigate(NavGraphDirections.actionStartupHome())
|
internal fun navigateToHome(navController: NavController) {
|
||||||
|
if (this is ExternalAppBrowserActivity) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
navController.navigate(NavGraphDirections.actionStartupHome())
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun attachBaseContext(base: Context) {
|
override fun attachBaseContext(base: Context) {
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
/* 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.addons
|
||||||
|
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
|
import mozilla.components.browser.state.store.BrowserStore
|
||||||
|
import mozilla.components.support.webextensions.ExtensionsProcessDisabledPromptObserver
|
||||||
|
import org.mozilla.fenix.components.AppStore
|
||||||
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controller for handling extensions process spawning disabled events. This is for when the app is
|
||||||
|
* in background, the app is killed to prevent extensions from being disabled and network requests
|
||||||
|
* continuing.
|
||||||
|
*
|
||||||
|
* @param browserStore The [BrowserStore] which holds the state for showing the dialog.
|
||||||
|
* @param appStore The [AppStore] containing the application state.
|
||||||
|
* @param onExtensionsProcessDisabled Invoked when the app is in background and extensions process
|
||||||
|
* is disabled.
|
||||||
|
*/
|
||||||
|
class ExtensionsProcessDisabledBackgroundController(
|
||||||
|
browserStore: BrowserStore,
|
||||||
|
appStore: AppStore,
|
||||||
|
onExtensionsProcessDisabled: () -> Unit = { killApp() },
|
||||||
|
) : ExtensionsProcessDisabledPromptObserver(
|
||||||
|
store = browserStore,
|
||||||
|
shouldCancelOnStop = false,
|
||||||
|
onShowExtensionsProcessDisabledPrompt = {
|
||||||
|
if (!appStore.state.isForeground) {
|
||||||
|
onExtensionsProcessDisabled()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* When a dialog can't be shown because the app is in the background, instead the app will
|
||||||
|
* be killed to prevent leaking network data without extensions enabled.
|
||||||
|
*/
|
||||||
|
private fun killApp() {
|
||||||
|
Handler(Looper.getMainLooper()).post {
|
||||||
|
exitProcess(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,8 +5,6 @@
|
||||||
package org.mozilla.fenix.addons
|
package org.mozilla.fenix.addons
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Handler
|
|
||||||
import android.os.Looper
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.widget.Button
|
import android.widget.Button
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
@ -20,35 +18,30 @@ import mozilla.components.support.webextensions.ExtensionsProcessDisabledPromptO
|
||||||
import org.mozilla.fenix.R
|
import org.mozilla.fenix.R
|
||||||
import org.mozilla.fenix.components.AppStore
|
import org.mozilla.fenix.components.AppStore
|
||||||
import org.mozilla.fenix.ext.components
|
import org.mozilla.fenix.ext.components
|
||||||
import kotlin.system.exitProcess
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Controller for handling extensions process spawning disabled events. When the app is in
|
* Controller for handling extensions process spawning disabled events. When the app is in
|
||||||
* foreground this will call for a dialog to decide on correct action to take (retry enabling
|
* foreground this will call for a dialog to decide on correct action to take (retry enabling
|
||||||
* process spawning or disable extensions). When in background, we kill the app to prevent
|
* process spawning or disable extensions).
|
||||||
* extensions from being disabled and network requests continuing.
|
|
||||||
*
|
*
|
||||||
* @param context to show the AlertDialog
|
* @param context to show the AlertDialog
|
||||||
* @param browserStore The [BrowserStore] which holds the state for showing the dialog
|
* @param browserStore The [BrowserStore] which holds the state for showing the dialog
|
||||||
* @param appStore The [AppStore] containing the application state
|
* @param appStore The [AppStore] containing the application state
|
||||||
* @param builder to use for creating the dialog which can be styled as needed
|
* @param builder to use for creating the dialog which can be styled as needed
|
||||||
* @param appName to be added to the message. Optional and mainly relevant for testing
|
* @param appName to be added to the message. Optional and mainly relevant for testing
|
||||||
* @param onKillApp called when the app is backgrounded and extensions process is disabled
|
|
||||||
*/
|
*/
|
||||||
class ExtensionsProcessDisabledController(
|
class ExtensionsProcessDisabledForegroundController(
|
||||||
@UiContext context: Context,
|
@UiContext context: Context,
|
||||||
browserStore: BrowserStore = context.components.core.store,
|
browserStore: BrowserStore = context.components.core.store,
|
||||||
appStore: AppStore = context.components.appStore,
|
appStore: AppStore = context.components.appStore,
|
||||||
builder: AlertDialog.Builder = AlertDialog.Builder(context),
|
builder: AlertDialog.Builder = AlertDialog.Builder(context),
|
||||||
appName: String = context.appName,
|
appName: String = context.appName,
|
||||||
onKillApp: () -> Unit = { killApp() },
|
|
||||||
) : ExtensionsProcessDisabledPromptObserver(
|
) : ExtensionsProcessDisabledPromptObserver(
|
||||||
browserStore,
|
store = browserStore,
|
||||||
|
shouldCancelOnStop = true,
|
||||||
{
|
{
|
||||||
if (appStore.state.isForeground) {
|
if (appStore.state.isForeground) {
|
||||||
presentDialog(context, browserStore, builder, appName)
|
presentDialog(context, browserStore, builder, appName)
|
||||||
} else {
|
|
||||||
onKillApp.invoke()
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
|
@ -61,16 +54,6 @@ class ExtensionsProcessDisabledController(
|
||||||
companion object {
|
companion object {
|
||||||
private var shouldCreateDialog: Boolean = true
|
private var shouldCreateDialog: Boolean = true
|
||||||
|
|
||||||
/**
|
|
||||||
* When a dialog can't be shown because the app is in the background, instead the app will
|
|
||||||
* be killed to prevent leaking network data without extensions enabled.
|
|
||||||
*/
|
|
||||||
private fun killApp() {
|
|
||||||
Handler(Looper.getMainLooper()).post {
|
|
||||||
exitProcess(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Present a dialog to the user notifying of extensions process spawning disabled and also asking
|
* Present a dialog to the user notifying of extensions process spawning disabled and also asking
|
||||||
* whether they would like to continue trying or disable extensions. If the user chooses to retry,
|
* whether they would like to continue trying or disable extensions. If the user chooses to retry,
|
|
@ -12,6 +12,7 @@ import android.content.res.Configuration
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
|
import android.util.Log
|
||||||
import android.view.Gravity
|
import android.view.Gravity
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
@ -31,9 +32,11 @@ import androidx.navigation.NavController
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
|
import kotlinx.coroutines.Deferred
|
||||||
import kotlinx.coroutines.Dispatchers.IO
|
import kotlinx.coroutines.Dispatchers.IO
|
||||||
import kotlinx.coroutines.Dispatchers.Main
|
import kotlinx.coroutines.Dispatchers.Main
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.distinctUntilChangedBy
|
import kotlinx.coroutines.flow.distinctUntilChangedBy
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
@ -59,6 +62,7 @@ import mozilla.components.browser.thumbnails.BrowserThumbnails
|
||||||
import mozilla.components.concept.base.crash.Breadcrumb
|
import mozilla.components.concept.base.crash.Breadcrumb
|
||||||
import mozilla.components.concept.engine.permission.SitePermissions
|
import mozilla.components.concept.engine.permission.SitePermissions
|
||||||
import mozilla.components.concept.engine.prompt.ShareData
|
import mozilla.components.concept.engine.prompt.ShareData
|
||||||
|
import mozilla.components.concept.storage.LoginEntry
|
||||||
import mozilla.components.feature.accounts.FxaCapability
|
import mozilla.components.feature.accounts.FxaCapability
|
||||||
import mozilla.components.feature.accounts.FxaWebChannelFeature
|
import mozilla.components.feature.accounts.FxaWebChannelFeature
|
||||||
import mozilla.components.feature.app.links.AppLinksFeature
|
import mozilla.components.feature.app.links.AppLinksFeature
|
||||||
|
@ -79,6 +83,7 @@ import mozilla.components.feature.prompts.dialog.FullScreenNotificationDialog
|
||||||
import mozilla.components.feature.prompts.identitycredential.DialogColors
|
import mozilla.components.feature.prompts.identitycredential.DialogColors
|
||||||
import mozilla.components.feature.prompts.identitycredential.DialogColorsProvider
|
import mozilla.components.feature.prompts.identitycredential.DialogColorsProvider
|
||||||
import mozilla.components.feature.prompts.login.LoginDelegate
|
import mozilla.components.feature.prompts.login.LoginDelegate
|
||||||
|
import mozilla.components.feature.prompts.login.SuggestStrongPasswordDelegate
|
||||||
import mozilla.components.feature.prompts.share.ShareDelegate
|
import mozilla.components.feature.prompts.share.ShareDelegate
|
||||||
import mozilla.components.feature.readerview.ReaderViewFeature
|
import mozilla.components.feature.readerview.ReaderViewFeature
|
||||||
import mozilla.components.feature.search.SearchFeature
|
import mozilla.components.feature.search.SearchFeature
|
||||||
|
@ -95,6 +100,8 @@ import mozilla.components.lib.state.ext.flowScoped
|
||||||
import mozilla.components.service.glean.private.NoExtras
|
import mozilla.components.service.glean.private.NoExtras
|
||||||
import mozilla.components.service.sync.autofill.DefaultCreditCardValidationDelegate
|
import mozilla.components.service.sync.autofill.DefaultCreditCardValidationDelegate
|
||||||
import mozilla.components.service.sync.logins.DefaultLoginValidationDelegate
|
import mozilla.components.service.sync.logins.DefaultLoginValidationDelegate
|
||||||
|
import mozilla.components.service.sync.logins.LoginsApiException
|
||||||
|
import mozilla.components.service.sync.logins.SyncableLoginsStorage
|
||||||
import mozilla.components.support.base.feature.ActivityResultHandler
|
import mozilla.components.support.base.feature.ActivityResultHandler
|
||||||
import mozilla.components.support.base.feature.PermissionsFeature
|
import mozilla.components.support.base.feature.PermissionsFeature
|
||||||
import mozilla.components.support.base.feature.UserInteractionHandler
|
import mozilla.components.support.base.feature.UserInteractionHandler
|
||||||
|
@ -108,6 +115,7 @@ import mozilla.components.support.locale.ActivityContextWrapper
|
||||||
import mozilla.components.ui.widgets.withCenterAlignedButtons
|
import mozilla.components.ui.widgets.withCenterAlignedButtons
|
||||||
import org.mozilla.fenix.BuildConfig
|
import org.mozilla.fenix.BuildConfig
|
||||||
import org.mozilla.fenix.FeatureFlags
|
import org.mozilla.fenix.FeatureFlags
|
||||||
|
import org.mozilla.fenix.GleanMetrics.Events
|
||||||
import org.mozilla.fenix.GleanMetrics.MediaState
|
import org.mozilla.fenix.GleanMetrics.MediaState
|
||||||
import org.mozilla.fenix.GleanMetrics.PullToRefreshInBrowser
|
import org.mozilla.fenix.GleanMetrics.PullToRefreshInBrowser
|
||||||
import org.mozilla.fenix.HomeActivity
|
import org.mozilla.fenix.HomeActivity
|
||||||
|
@ -160,6 +168,7 @@ import org.mozilla.fenix.theme.ThemeManager
|
||||||
import org.mozilla.fenix.utils.allowUndo
|
import org.mozilla.fenix.utils.allowUndo
|
||||||
import org.mozilla.fenix.wifi.SitePermissionsWifiIntegration
|
import org.mozilla.fenix.wifi.SitePermissionsWifiIntegration
|
||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
|
import kotlin.coroutines.cancellation.CancellationException
|
||||||
import mozilla.components.feature.session.behavior.ToolbarPosition as MozacToolbarPosition
|
import mozilla.components.feature.session.behavior.ToolbarPosition as MozacToolbarPosition
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -460,6 +469,7 @@ abstract class BaseBrowserFragment :
|
||||||
|
|
||||||
browserToolbarView.view.display.setOnSiteSecurityClickedListener {
|
browserToolbarView.view.display.setOnSiteSecurityClickedListener {
|
||||||
showQuickSettingsDialog()
|
showQuickSettingsDialog()
|
||||||
|
Events.browserToolbarSecurityIndicatorTapped.record()
|
||||||
}
|
}
|
||||||
|
|
||||||
contextMenuFeature.set(
|
contextMenuFeature.set(
|
||||||
|
@ -731,6 +741,18 @@ abstract class BaseBrowserFragment :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
suggestStrongPasswordDelegate = object : SuggestStrongPasswordDelegate {
|
||||||
|
override val strongPasswordPromptViewListenerView
|
||||||
|
get() = binding.suggestStrongPasswordBar
|
||||||
|
},
|
||||||
|
isSuggestStrongPasswordEnabled = context.settings().enableSuggestStrongPassword,
|
||||||
|
onSaveLoginWithStrongPassword = { url, password ->
|
||||||
|
handleOnSaveLoginWithGeneratedStrongPassword(
|
||||||
|
passwordsStorage = context.components.core.passwordsStorage,
|
||||||
|
url = url,
|
||||||
|
password = password,
|
||||||
|
)
|
||||||
|
},
|
||||||
creditCardDelegate = object : CreditCardDelegate {
|
creditCardDelegate = object : CreditCardDelegate {
|
||||||
override val creditCardPickerView
|
override val creditCardPickerView
|
||||||
get() = binding.creditCardSelectBar
|
get() = binding.creditCardSelectBar
|
||||||
|
@ -894,9 +916,12 @@ abstract class BaseBrowserFragment :
|
||||||
binding.swipeRefresh.isEnabled = shouldPullToRefreshBeEnabled(false)
|
binding.swipeRefresh.isEnabled = shouldPullToRefreshBeEnabled(false)
|
||||||
|
|
||||||
if (binding.swipeRefresh.isEnabled) {
|
if (binding.swipeRefresh.isEnabled) {
|
||||||
val primaryTextColor =
|
val primaryTextColor = ThemeManager.resolveAttribute(R.attr.textPrimary, context)
|
||||||
ThemeManager.resolveAttribute(R.attr.textPrimary, context)
|
val primaryBackgroundColor = ThemeManager.resolveAttribute(R.attr.layer2, context)
|
||||||
binding.swipeRefresh.setColorSchemeColors(primaryTextColor)
|
binding.swipeRefresh.apply {
|
||||||
|
setColorSchemeResources(primaryTextColor)
|
||||||
|
setProgressBackgroundColorSchemeResource(primaryBackgroundColor)
|
||||||
|
}
|
||||||
swipeRefreshFeature.set(
|
swipeRefreshFeature.set(
|
||||||
feature = SwipeRefreshFeature(
|
feature = SwipeRefreshFeature(
|
||||||
requireComponents.core.store,
|
requireComponents.core.store,
|
||||||
|
@ -1651,4 +1676,38 @@ abstract class BaseBrowserFragment :
|
||||||
|
|
||||||
return isValidStatus && isSameTab
|
return isValidStatus && isSameTab
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleOnSaveLoginWithGeneratedStrongPassword(
|
||||||
|
passwordsStorage: SyncableLoginsStorage,
|
||||||
|
url: String,
|
||||||
|
password: String,
|
||||||
|
) {
|
||||||
|
val loginToSave = LoginEntry(
|
||||||
|
origin = url,
|
||||||
|
httpRealm = url,
|
||||||
|
username = "",
|
||||||
|
password = password,
|
||||||
|
)
|
||||||
|
var saveLoginJob: Deferred<Unit>? = null
|
||||||
|
lifecycleScope.launch(IO) {
|
||||||
|
saveLoginJob = async {
|
||||||
|
try {
|
||||||
|
passwordsStorage.add(loginToSave)
|
||||||
|
} catch (loginException: LoginsApiException) {
|
||||||
|
loginException.printStackTrace()
|
||||||
|
Log.e(
|
||||||
|
"Add new login",
|
||||||
|
"Failed to add new login with generated password.",
|
||||||
|
loginException,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
saveLoginJob?.await()
|
||||||
|
}
|
||||||
|
saveLoginJob?.invokeOnCompletion {
|
||||||
|
if (it is CancellationException) {
|
||||||
|
saveLoginJob?.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,6 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
* @param dismissAction Optional callback invoked when the user dismisses the banner.
|
* @param dismissAction Optional callback invoked when the user dismisses the banner.
|
||||||
* @param actionToPerform The action to be performed on action button press.
|
* @param actionToPerform The action to be performed on action button press.
|
||||||
*/
|
*/
|
||||||
@Suppress("LongParameterList")
|
|
||||||
class DynamicInfoBanner(
|
class DynamicInfoBanner(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
container: ViewGroup,
|
container: ViewGroup,
|
||||||
|
|
|
@ -26,7 +26,6 @@ import org.mozilla.fenix.ext.settings
|
||||||
* @property dismissAction Optional callback invoked when the user dismisses the banner.
|
* @property dismissAction Optional callback invoked when the user dismisses the banner.
|
||||||
* @param actionToPerform The action to be performed on action button press.
|
* @param actionToPerform The action to be performed on action button press.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("LongParameterList")
|
|
||||||
open class InfoBanner(
|
open class InfoBanner(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val container: ViewGroup,
|
private val container: ViewGroup,
|
||||||
|
|
|
@ -53,7 +53,7 @@ class IntentProcessors(
|
||||||
* Provides intent processing functionality for ACTION_VIEW and ACTION_SEND intents in private tabs.
|
* Provides intent processing functionality for ACTION_VIEW and ACTION_SEND intents in private tabs.
|
||||||
*/
|
*/
|
||||||
val privateIntentProcessor by lazyMonitored {
|
val privateIntentProcessor by lazyMonitored {
|
||||||
TabIntentProcessor(tabsUseCases, searchUseCases.newTabSearch, isPrivate = true)
|
TabIntentProcessor(tabsUseCases, searchUseCases.newPrivateTabSearch, isPrivate = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
val customTabIntentProcessor by lazyMonitored {
|
val customTabIntentProcessor by lazyMonitored {
|
||||||
|
|
|
@ -223,7 +223,9 @@ class DefaultBrowserToolbarController(
|
||||||
|
|
||||||
override fun handleTranslationsButtonClick() {
|
override fun handleTranslationsButtonClick() {
|
||||||
val directions =
|
val directions =
|
||||||
BrowserFragmentDirections.actionBrowserFragmentToTranslationsDialogFragment()
|
BrowserFragmentDirections.actionBrowserFragmentToTranslationsDialogFragment(
|
||||||
|
sessionId = currentSession?.id,
|
||||||
|
)
|
||||||
navController.navigateSafe(R.id.browserFragment, directions)
|
navController.navigateSafe(R.id.browserFragment, directions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -406,6 +406,14 @@ class DefaultBrowserToolbarMenuController(
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ToolbarMenu.Item.Translate -> {
|
||||||
|
val directions =
|
||||||
|
BrowserFragmentDirections.actionBrowserFragmentToTranslationsDialogFragment(
|
||||||
|
sessionId = currentSession?.id,
|
||||||
|
)
|
||||||
|
navController.navigateSafe(R.id.browserFragment, directions)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -420,7 +428,7 @@ class DefaultBrowserToolbarMenuController(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("ComplexMethod")
|
@Suppress("ComplexMethod", "LongMethod")
|
||||||
private fun trackToolbarItemInteraction(item: ToolbarMenu.Item) {
|
private fun trackToolbarItemInteraction(item: ToolbarMenu.Item) {
|
||||||
when (item) {
|
when (item) {
|
||||||
is ToolbarMenu.Item.OpenInFenix ->
|
is ToolbarMenu.Item.OpenInFenix ->
|
||||||
|
@ -433,10 +441,19 @@ class DefaultBrowserToolbarMenuController(
|
||||||
Events.browserMenuAction.record(Events.BrowserMenuActionExtra("open_in_app"))
|
Events.browserMenuAction.record(Events.BrowserMenuActionExtra("open_in_app"))
|
||||||
is ToolbarMenu.Item.CustomizeReaderView ->
|
is ToolbarMenu.Item.CustomizeReaderView ->
|
||||||
Events.browserMenuAction.record(Events.BrowserMenuActionExtra("reader_mode_appearance"))
|
Events.browserMenuAction.record(Events.BrowserMenuActionExtra("reader_mode_appearance"))
|
||||||
is ToolbarMenu.Item.Back ->
|
is ToolbarMenu.Item.Back -> {
|
||||||
|
if (item.viewHistory) {
|
||||||
|
Events.browserMenuAction.record(Events.BrowserMenuActionExtra("back_long_press"))
|
||||||
|
} else {
|
||||||
Events.browserMenuAction.record(Events.BrowserMenuActionExtra("back"))
|
Events.browserMenuAction.record(Events.BrowserMenuActionExtra("back"))
|
||||||
|
}
|
||||||
|
}
|
||||||
is ToolbarMenu.Item.Forward ->
|
is ToolbarMenu.Item.Forward ->
|
||||||
|
if (item.viewHistory) {
|
||||||
|
Events.browserMenuAction.record(Events.BrowserMenuActionExtra("forward_long_press"))
|
||||||
|
} else {
|
||||||
Events.browserMenuAction.record(Events.BrowserMenuActionExtra("forward"))
|
Events.browserMenuAction.record(Events.BrowserMenuActionExtra("forward"))
|
||||||
|
}
|
||||||
is ToolbarMenu.Item.Reload ->
|
is ToolbarMenu.Item.Reload ->
|
||||||
Events.browserMenuAction.record(Events.BrowserMenuActionExtra("reload"))
|
Events.browserMenuAction.record(Events.BrowserMenuActionExtra("reload"))
|
||||||
is ToolbarMenu.Item.Stop ->
|
is ToolbarMenu.Item.Stop ->
|
||||||
|
@ -483,6 +500,12 @@ class DefaultBrowserToolbarMenuController(
|
||||||
Events.browserMenuAction.record(Events.BrowserMenuActionExtra("set_default_browser"))
|
Events.browserMenuAction.record(Events.BrowserMenuActionExtra("set_default_browser"))
|
||||||
is ToolbarMenu.Item.RemoveFromTopSites ->
|
is ToolbarMenu.Item.RemoveFromTopSites ->
|
||||||
Events.browserMenuAction.record(Events.BrowserMenuActionExtra("remove_from_top_sites"))
|
Events.browserMenuAction.record(Events.BrowserMenuActionExtra("remove_from_top_sites"))
|
||||||
|
|
||||||
|
ToolbarMenu.Item.Translate -> Events.browserMenuAction.record(
|
||||||
|
Events.BrowserMenuActionExtra(
|
||||||
|
"translate",
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,6 @@ import org.mozilla.fenix.ext.components
|
||||||
import org.mozilla.fenix.ext.settings
|
import org.mozilla.fenix.ext.settings
|
||||||
import org.mozilla.fenix.nimbus.FxNimbus
|
import org.mozilla.fenix.nimbus.FxNimbus
|
||||||
import org.mozilla.fenix.theme.ThemeManager
|
import org.mozilla.fenix.theme.ThemeManager
|
||||||
import org.mozilla.fenix.utils.Settings
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds the toolbar object used with the 3-dot menu in the browser fragment.
|
* Builds the toolbar object used with the 3-dot menu in the browser fragment.
|
||||||
|
@ -55,7 +54,7 @@ import org.mozilla.fenix.utils.Settings
|
||||||
* @param pinnedSiteStorage Used to check if the current url is a pinned site.
|
* @param pinnedSiteStorage Used to check if the current url is a pinned site.
|
||||||
* @property isPinningSupported true if the launcher supports adding shortcuts.
|
* @property isPinningSupported true if the launcher supports adding shortcuts.
|
||||||
*/
|
*/
|
||||||
@Suppress("LargeClass", "LongParameterList", "TooManyFunctions")
|
@Suppress("LargeClass", "TooManyFunctions")
|
||||||
open class DefaultToolbarMenu(
|
open class DefaultToolbarMenu(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val store: BrowserStore,
|
private val store: BrowserStore,
|
||||||
|
@ -193,6 +192,14 @@ open class DefaultToolbarMenu(
|
||||||
fun shouldShowReaderViewCustomization(): Boolean = selectedSession?.let {
|
fun shouldShowReaderViewCustomization(): Boolean = selectedSession?.let {
|
||||||
store.state.findTab(it.id)?.readerState?.active
|
store.state.findTab(it.id)?.readerState?.active
|
||||||
} ?: false
|
} ?: false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should Translations menu item be visible?
|
||||||
|
*/
|
||||||
|
@VisibleForTesting(otherwise = PRIVATE)
|
||||||
|
fun shouldShowTranslations(): Boolean = selectedSession?.let {
|
||||||
|
context.settings().enableTranslations
|
||||||
|
} ?: false
|
||||||
// End of predicates //
|
// End of predicates //
|
||||||
|
|
||||||
private val installToHomescreen = BrowserMenuHighlightableItem(
|
private val installToHomescreen = BrowserMenuHighlightableItem(
|
||||||
|
@ -248,6 +255,14 @@ open class DefaultToolbarMenu(
|
||||||
onItemTapped.invoke(ToolbarMenu.Item.FindInPage)
|
onItemTapped.invoke(ToolbarMenu.Item.FindInPage)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val translationsItem = BrowserMenuImageText(
|
||||||
|
label = context.getString(R.string.browser_menu_translations),
|
||||||
|
imageResource = R.drawable.mozac_ic_translate_24,
|
||||||
|
iconTintColorResource = primaryTextColor(),
|
||||||
|
) {
|
||||||
|
onItemTapped.invoke(ToolbarMenu.Item.Translate)
|
||||||
|
}
|
||||||
|
|
||||||
private val desktopSiteItem = BrowserMenuImageSwitch(
|
private val desktopSiteItem = BrowserMenuImageSwitch(
|
||||||
imageResource = R.drawable.ic_desktop,
|
imageResource = R.drawable.ic_desktop,
|
||||||
label = context.getString(R.string.browser_menu_desktop_site),
|
label = context.getString(R.string.browser_menu_desktop_site),
|
||||||
|
@ -405,6 +420,7 @@ open class DefaultToolbarMenu(
|
||||||
syncMenuItem(),
|
syncMenuItem(),
|
||||||
BrowserMenuDivider(),
|
BrowserMenuDivider(),
|
||||||
findInPageItem,
|
findInPageItem,
|
||||||
|
translationsItem.apply { visible = ::shouldShowTranslations },
|
||||||
desktopSiteItem,
|
desktopSiteItem,
|
||||||
openInRegularTabItem.apply { visible = ::shouldShowOpenInRegularTab },
|
openInRegularTabItem.apply { visible = ::shouldShowOpenInRegularTab },
|
||||||
customizeReaderView.apply { visible = ::shouldShowReaderViewCustomization },
|
customizeReaderView.apply { visible = ::shouldShowReaderViewCustomization },
|
||||||
|
|
|
@ -76,7 +76,6 @@ abstract class ToolbarIntegration(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("LongParameterList")
|
|
||||||
class DefaultToolbarIntegration(
|
class DefaultToolbarIntegration(
|
||||||
context: Context,
|
context: Context,
|
||||||
toolbar: BrowserToolbar,
|
toolbar: BrowserToolbar,
|
||||||
|
|
|
@ -18,6 +18,11 @@ interface ToolbarMenu {
|
||||||
*/
|
*/
|
||||||
object OpenInRegularTab : Item()
|
object OpenInRegularTab : Item()
|
||||||
object FindInPage : Item()
|
object FindInPage : Item()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens the translations flow.
|
||||||
|
*/
|
||||||
|
object Translate : Item()
|
||||||
object Share : Item()
|
object Share : Item()
|
||||||
data class Back(val viewHistory: Boolean) : Item()
|
data class Back(val viewHistory: Boolean) : Item()
|
||||||
data class Forward(val viewHistory: Boolean) : Item()
|
data class Forward(val viewHistory: Boolean) : Item()
|
||||||
|
|
|
@ -41,7 +41,6 @@ import org.mozilla.fenix.theme.FirefoxTheme
|
||||||
* By default set to a solid color in [DefaultImagePlaceholder].
|
* By default set to a solid color in [DefaultImagePlaceholder].
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
@Suppress("LongParameterList")
|
|
||||||
fun Image(
|
fun Image(
|
||||||
url: String,
|
url: String,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
|
|
@ -7,10 +7,22 @@ package org.mozilla.fenix.compose
|
||||||
import androidx.annotation.VisibleForTesting
|
import androidx.annotation.VisibleForTesting
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.text.ClickableText
|
import androidx.compose.foundation.text.ClickableText
|
||||||
|
import androidx.compose.material.Card
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.material.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.semantics.contentDescription
|
||||||
import androidx.compose.ui.semantics.onClick
|
import androidx.compose.ui.semantics.onClick
|
||||||
import androidx.compose.ui.semantics.semantics
|
import androidx.compose.ui.semantics.semantics
|
||||||
import androidx.compose.ui.text.AnnotatedString
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
|
@ -20,6 +32,9 @@ import androidx.compose.ui.text.buildAnnotatedString
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.text.style.TextDecoration
|
import androidx.compose.ui.text.style.TextDecoration
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.window.Dialog
|
||||||
|
import org.mozilla.fenix.R
|
||||||
import org.mozilla.fenix.theme.FirefoxTheme
|
import org.mozilla.fenix.theme.FirefoxTheme
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -67,6 +82,12 @@ fun LinkText(
|
||||||
linkTextDecoration,
|
linkTextDecoration,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val showDialog = remember { mutableStateOf(false) }
|
||||||
|
val linksAvailable = stringResource(id = R.string.a11y_links_available)
|
||||||
|
|
||||||
|
if (showDialog.value) {
|
||||||
|
LinksDialog(linkTextStates) { showDialog.value = false }
|
||||||
|
}
|
||||||
// When using UrlAnnotation, talkback shows links in a separate dialog and
|
// When using UrlAnnotation, talkback shows links in a separate dialog and
|
||||||
// opens them in the default browser. Since this component allows the caller to define the
|
// opens them in the default browser. Since this component allows the caller to define the
|
||||||
// onClick behaviour - e.g. to open the link in in-app custom tab, here StringAnnotation is used
|
// onClick behaviour - e.g. to open the link in in-app custom tab, here StringAnnotation is used
|
||||||
|
@ -76,12 +97,17 @@ fun LinkText(
|
||||||
style = style,
|
style = style,
|
||||||
modifier = Modifier.semantics(mergeDescendants = true) {
|
modifier = Modifier.semantics(mergeDescendants = true) {
|
||||||
onClick {
|
onClick {
|
||||||
|
if (linkTextStates.size > 1) {
|
||||||
|
showDialog.value = true
|
||||||
|
} else {
|
||||||
linkTextStates.firstOrNull()?.let {
|
linkTextStates.firstOrNull()?.let {
|
||||||
it.onClick(it.url)
|
it.onClick(it.url)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return@onClick true
|
return@onClick true
|
||||||
}
|
}
|
||||||
|
contentDescription = "$annotatedString $linksAvailable"
|
||||||
},
|
},
|
||||||
onClick = { charOffset ->
|
onClick = { charOffset ->
|
||||||
onTextClick(annotatedString, charOffset, linkTextStates)
|
onTextClick(annotatedString, charOffset, linkTextStates)
|
||||||
|
@ -89,6 +115,60 @@ fun LinkText(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun LinksDialog(
|
||||||
|
linkTextStates: List<LinkTextState>,
|
||||||
|
onDismissRequest: () -> Unit,
|
||||||
|
) {
|
||||||
|
Dialog(onDismissRequest = { onDismissRequest() }) {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth(),
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.background(color = FirefoxTheme.colors.layer2)
|
||||||
|
.padding(all = 16.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.a11y_links_title),
|
||||||
|
color = FirefoxTheme.colors.textPrimary,
|
||||||
|
style = FirefoxTheme.typography.headline5,
|
||||||
|
)
|
||||||
|
|
||||||
|
linkTextStates.forEach { linkText ->
|
||||||
|
TextButton(
|
||||||
|
onClick = { linkText.onClick(linkText.url) },
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.Start),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = linkText.text,
|
||||||
|
color = FirefoxTheme.colors.textAccent,
|
||||||
|
textDecoration = TextDecoration.Underline,
|
||||||
|
style = FirefoxTheme.typography.button,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TextButton(
|
||||||
|
onClick = { onDismissRequest() },
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.End),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.standard_snackbar_error_dismiss),
|
||||||
|
color = FirefoxTheme.colors.textAccent,
|
||||||
|
style = FirefoxTheme.typography.button,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
internal fun onTextClick(
|
internal fun onTextClick(
|
||||||
annotatedString: AnnotatedString,
|
annotatedString: AnnotatedString,
|
||||||
|
@ -231,3 +311,27 @@ private fun MultipleLinksPreview() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun LinksDialogPreview() {
|
||||||
|
val state1 = LinkTextState(
|
||||||
|
text = "clickable text",
|
||||||
|
url = "www.mozilla.com",
|
||||||
|
onClick = {},
|
||||||
|
)
|
||||||
|
|
||||||
|
val state2 = LinkTextState(
|
||||||
|
text = "another clickable text",
|
||||||
|
url = "www.mozilla.com",
|
||||||
|
onClick = {},
|
||||||
|
)
|
||||||
|
|
||||||
|
val linkTextStateList = listOf(state1, state2)
|
||||||
|
FirefoxTheme {
|
||||||
|
LinksDialog(
|
||||||
|
linkTextStates = linkTextStateList,
|
||||||
|
onDismissRequest = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -35,6 +35,7 @@ private const val DISABLED_ALPHA = 0.5f
|
||||||
* UI for a switch with label that can be on or off.
|
* UI for a switch with label that can be on or off.
|
||||||
*
|
*
|
||||||
* @param label Text to be displayed next to the switch.
|
* @param label Text to be displayed next to the switch.
|
||||||
|
* @param description An optional description text below the label.
|
||||||
* @param checked Whether or not the switch is checked.
|
* @param checked Whether or not the switch is checked.
|
||||||
* @param onCheckedChange Invoked when Switch is being clicked, therefore the change of checked
|
* @param onCheckedChange Invoked when Switch is being clicked, therefore the change of checked
|
||||||
* state is requested.
|
* state is requested.
|
||||||
|
@ -44,6 +45,7 @@ private const val DISABLED_ALPHA = 0.5f
|
||||||
@Composable
|
@Composable
|
||||||
fun SwitchWithLabel(
|
fun SwitchWithLabel(
|
||||||
label: String,
|
label: String,
|
||||||
|
description: String? = null,
|
||||||
checked: Boolean,
|
checked: Boolean,
|
||||||
onCheckedChange: ((Boolean) -> Unit),
|
onCheckedChange: ((Boolean) -> Unit),
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
@ -59,6 +61,10 @@ fun SwitchWithLabel(
|
||||||
),
|
),
|
||||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f),
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = label,
|
text = label,
|
||||||
|
@ -68,9 +74,17 @@ fun SwitchWithLabel(
|
||||||
FirefoxTheme.colors.textDisabled
|
FirefoxTheme.colors.textDisabled
|
||||||
},
|
},
|
||||||
style = FirefoxTheme.typography.subtitle1,
|
style = FirefoxTheme.typography.subtitle1,
|
||||||
modifier = Modifier.weight(1f),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
description?.let {
|
||||||
|
Text(
|
||||||
|
text = description,
|
||||||
|
color = FirefoxTheme.colors.textSecondary,
|
||||||
|
style = FirefoxTheme.typography.body2,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Switch(
|
Switch(
|
||||||
modifier = Modifier.clearAndSetSemantics {},
|
modifier = Modifier.clearAndSetSemantics {},
|
||||||
checked = checked,
|
checked = checked,
|
||||||
|
@ -139,6 +153,7 @@ private fun SwitchWithLabelPreview() {
|
||||||
var enabledSwitchState by remember { mutableStateOf(false) }
|
var enabledSwitchState by remember { mutableStateOf(false) }
|
||||||
SwitchWithLabel(
|
SwitchWithLabel(
|
||||||
label = if (enabledSwitchState) "On" else "Off",
|
label = if (enabledSwitchState) "On" else "Off",
|
||||||
|
description = "Description text",
|
||||||
checked = enabledSwitchState,
|
checked = enabledSwitchState,
|
||||||
onCheckedChange = { enabledSwitchState = it },
|
onCheckedChange = { enabledSwitchState = it },
|
||||||
)
|
)
|
||||||
|
|
|
@ -42,7 +42,6 @@ private const val FALLBACK_ICON_SIZE = 36
|
||||||
* @param alignment [Alignment] used to draw the image content.
|
* @param alignment [Alignment] used to draw the image content.
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
@Suppress("LongParameterList")
|
|
||||||
fun TabThumbnail(
|
fun TabThumbnail(
|
||||||
tab: TabSessionState,
|
tab: TabSessionState,
|
||||||
storage: ThumbnailStorage,
|
storage: ThumbnailStorage,
|
||||||
|
|
|
@ -38,7 +38,6 @@ import org.mozilla.fenix.theme.FirefoxTheme
|
||||||
* @param fallbackContent The content to display with a thumbnail is unable to be loaded.
|
* @param fallbackContent The content to display with a thumbnail is unable to be loaded.
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
@Suppress("LongParameterList")
|
|
||||||
fun ThumbnailImage(
|
fun ThumbnailImage(
|
||||||
request: ImageLoadRequest,
|
request: ImageLoadRequest,
|
||||||
storage: ThumbnailStorage,
|
storage: ThumbnailStorage,
|
||||||
|
|
|
@ -38,7 +38,6 @@ import org.mozilla.fenix.theme.FirefoxTheme
|
||||||
* @param onClick Optional lambda for handling header clicks.
|
* @param onClick Optional lambda for handling header clicks.
|
||||||
* @param actions Optional Composable for adding UI to the end of the header.
|
* @param actions Optional Composable for adding UI to the end of the header.
|
||||||
*/
|
*/
|
||||||
@Suppress("LongParameterList")
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ExpandableListHeader(
|
fun ExpandableListHeader(
|
||||||
headerText: String,
|
headerText: String,
|
||||||
|
|
|
@ -91,7 +91,7 @@ import org.mozilla.fenix.theme.FirefoxTheme
|
||||||
*/
|
*/
|
||||||
@OptIn(ExperimentalMaterialApi::class, ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalMaterialApi::class, ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
@Suppress("MagicNumber", "LongParameterList", "LongMethod")
|
@Suppress("MagicNumber", "LongMethod")
|
||||||
fun TabGridItem(
|
fun TabGridItem(
|
||||||
tab: TabSessionState,
|
tab: TabSessionState,
|
||||||
storage: ThumbnailStorage,
|
storage: ThumbnailStorage,
|
||||||
|
|
|
@ -75,7 +75,7 @@ import org.mozilla.fenix.theme.FirefoxTheme
|
||||||
*/
|
*/
|
||||||
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterialApi::class)
|
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterialApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
@Suppress("MagicNumber", "LongMethod", "LongParameterList")
|
@Suppress("MagicNumber", "LongMethod")
|
||||||
fun TabListItem(
|
fun TabListItem(
|
||||||
tab: TabSessionState,
|
tab: TabSessionState,
|
||||||
storage: ThumbnailStorage,
|
storage: ThumbnailStorage,
|
||||||
|
@ -209,7 +209,6 @@ private fun clickableColor() = when (isSystemInDarkTheme()) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@Suppress("LongParameterList")
|
|
||||||
private fun Thumbnail(
|
private fun Thumbnail(
|
||||||
tab: TabSessionState,
|
tab: TabSessionState,
|
||||||
size: Int,
|
size: Int,
|
||||||
|
|
|
@ -5,24 +5,16 @@
|
||||||
package org.mozilla.fenix.customtabs
|
package org.mozilla.fenix.customtabs
|
||||||
|
|
||||||
import android.app.assist.AssistContent
|
import android.app.assist.AssistContent
|
||||||
import android.content.Intent
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.annotation.VisibleForTesting
|
import androidx.annotation.VisibleForTesting
|
||||||
import androidx.navigation.NavDestination
|
|
||||||
import androidx.navigation.NavDirections
|
|
||||||
import mozilla.components.browser.state.selector.findCustomTab
|
import mozilla.components.browser.state.selector.findCustomTab
|
||||||
import mozilla.components.browser.state.state.SessionState
|
import mozilla.components.browser.state.state.SessionState
|
||||||
import mozilla.components.concept.engine.manifest.WebAppManifestParser
|
|
||||||
import mozilla.components.feature.intent.ext.getSessionId
|
|
||||||
import mozilla.components.feature.pwa.ext.getWebAppManifest
|
|
||||||
import mozilla.components.support.utils.SafeIntent
|
import mozilla.components.support.utils.SafeIntent
|
||||||
import org.mozilla.fenix.BrowserDirection
|
|
||||||
import org.mozilla.fenix.HomeActivity
|
import org.mozilla.fenix.HomeActivity
|
||||||
import org.mozilla.fenix.NavGraphDirections
|
|
||||||
import org.mozilla.fenix.ext.components
|
import org.mozilla.fenix.ext.components
|
||||||
import java.security.InvalidParameterException
|
import org.mozilla.fenix.ext.getIntentSessionId
|
||||||
|
|
||||||
const val EXTRA_IS_SANDBOX_CUSTOM_TAB = "org.mozilla.fenix.customtabs.EXTRA_IS_SANDBOX_CUSTOM_TAB"
|
const val EXTRA_IS_SANDBOX_CUSTOM_TAB = "org.mozilla.fenix.customtabs.EXTRA_IS_SANDBOX_CUSTOM_TAB"
|
||||||
|
|
||||||
|
@ -45,52 +37,6 @@ open class ExternalAppBrowserActivity : HomeActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final override fun getBreadcrumbMessage(destination: NavDestination): String {
|
|
||||||
val fragmentName = resources.getResourceEntryName(destination.id)
|
|
||||||
return "Changing to fragment $fragmentName, isCustomTab: true"
|
|
||||||
}
|
|
||||||
|
|
||||||
final override fun getIntentSource(intent: SafeIntent) = "CUSTOM_TAB"
|
|
||||||
|
|
||||||
final override fun getIntentSessionId(intent: SafeIntent) = intent.getSessionId()
|
|
||||||
|
|
||||||
override fun navigateToBrowserOnColdStart() {
|
|
||||||
// No-op for external app
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun navigateToHome() {
|
|
||||||
// No-op for external app
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun handleNewIntent(intent: Intent) {
|
|
||||||
// No-op for external app
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getNavDirections(
|
|
||||||
from: BrowserDirection,
|
|
||||||
customTabSessionId: String?,
|
|
||||||
): NavDirections? {
|
|
||||||
if (customTabSessionId == null) {
|
|
||||||
finishAndRemoveTask()
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
val manifest = intent
|
|
||||||
.getWebAppManifest()
|
|
||||||
?.let { WebAppManifestParser().serialize(it).toString() }
|
|
||||||
return when (from) {
|
|
||||||
BrowserDirection.FromGlobal ->
|
|
||||||
NavGraphDirections.actionGlobalExternalAppBrowser(
|
|
||||||
activeSessionId = customTabSessionId,
|
|
||||||
webAppManifest = manifest,
|
|
||||||
isSandboxCustomTab = intent.getBooleanExtra(EXTRA_IS_SANDBOX_CUSTOM_TAB, false),
|
|
||||||
)
|
|
||||||
else -> throw InvalidParameterException(
|
|
||||||
"Tried to navigate to ExternalAppBrowserFragment from $from",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,6 @@ import mozilla.components.feature.pwa.feature.WebAppActivityFeature
|
||||||
import mozilla.components.feature.pwa.feature.WebAppContentFeature
|
import mozilla.components.feature.pwa.feature.WebAppContentFeature
|
||||||
import mozilla.components.feature.pwa.feature.WebAppHideToolbarFeature
|
import mozilla.components.feature.pwa.feature.WebAppHideToolbarFeature
|
||||||
import mozilla.components.feature.pwa.feature.WebAppSiteControlsFeature
|
import mozilla.components.feature.pwa.feature.WebAppSiteControlsFeature
|
||||||
import mozilla.components.support.base.feature.UserInteractionHandler
|
|
||||||
import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
|
import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
|
||||||
import mozilla.components.support.ktx.android.arch.lifecycle.addObservers
|
import mozilla.components.support.ktx.android.arch.lifecycle.addObservers
|
||||||
import org.mozilla.fenix.BuildConfig
|
import org.mozilla.fenix.BuildConfig
|
||||||
|
@ -44,7 +43,7 @@ import org.mozilla.fenix.settings.quicksettings.protections.cookiebanners.getCoo
|
||||||
/**
|
/**
|
||||||
* Fragment used for browsing the web within external apps.
|
* Fragment used for browsing the web within external apps.
|
||||||
*/
|
*/
|
||||||
class ExternalAppBrowserFragment : BaseBrowserFragment(), UserInteractionHandler {
|
class ExternalAppBrowserFragment : BaseBrowserFragment() {
|
||||||
|
|
||||||
private val args by navArgs<ExternalAppBrowserFragmentArgs>()
|
private val args by navArgs<ExternalAppBrowserFragmentArgs>()
|
||||||
|
|
||||||
|
@ -212,9 +211,4 @@ class ExternalAppBrowserFragment : BaseBrowserFragment(), UserInteractionHandler
|
||||||
view,
|
view,
|
||||||
FenixSnackbarDelegate(view),
|
FenixSnackbarDelegate(view),
|
||||||
)
|
)
|
||||||
|
|
||||||
companion object {
|
|
||||||
// We only care about millisecond precision for telemetry events
|
|
||||||
internal const val MS_PRECISION = 1_000_000L
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
/* 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.debugsettings.navigation
|
||||||
|
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A navigation destination for screens within the Debug Drawer.
|
||||||
|
*
|
||||||
|
* @property route The unique route used to navigate to the destination. This string can also contain
|
||||||
|
* optional parameters for arguments or deep linking.
|
||||||
|
* @property title The string ID of the destination's title.
|
||||||
|
* @property onClick Invoked when the destination is clicked to be navigated to.
|
||||||
|
* @property content The destination's [Composable].
|
||||||
|
*/
|
||||||
|
data class DebugDrawerDestination(
|
||||||
|
val route: String,
|
||||||
|
@StringRes val title: Int,
|
||||||
|
val onClick: () -> Unit,
|
||||||
|
val content: @Composable () -> Unit,
|
||||||
|
)
|
|
@ -0,0 +1,70 @@
|
||||||
|
/* 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.debugsettings.navigation
|
||||||
|
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import mozilla.components.browser.state.store.BrowserStore
|
||||||
|
import org.mozilla.fenix.R
|
||||||
|
import org.mozilla.fenix.debugsettings.store.DebugDrawerAction
|
||||||
|
import org.mozilla.fenix.debugsettings.store.DebugDrawerStore
|
||||||
|
import org.mozilla.fenix.debugsettings.tabs.TabTools as TabToolsScreen
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The navigation routes for screens within the Debug Drawer.
|
||||||
|
*
|
||||||
|
* @property route The unique route used to navigate to the destination. This string can also contain
|
||||||
|
* optional parameters for arguments or deep linking.
|
||||||
|
* @property title The string ID of the destination's title.
|
||||||
|
*/
|
||||||
|
enum class DebugDrawerRoute(val route: String, @StringRes val title: Int) {
|
||||||
|
/**
|
||||||
|
* The navigation route for [TabToolsScreen].
|
||||||
|
*/
|
||||||
|
TabTools(
|
||||||
|
route = "tab_tools",
|
||||||
|
title = R.string.debug_drawer_tab_tools_title,
|
||||||
|
),
|
||||||
|
;
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* Transforms the values of [DebugDrawerRoute] into a list of [DebugDrawerDestination]s.
|
||||||
|
*
|
||||||
|
* @param debugDrawerStore [DebugDrawerStore] used to dispatch navigation actions.
|
||||||
|
* @param browserStore [BrowserStore] used to add tabs in [TabToolsScreen].
|
||||||
|
* @param inactiveTabsEnabled Whether the inactive tabs feature is enabled.
|
||||||
|
*/
|
||||||
|
fun generateDebugDrawerDestinations(
|
||||||
|
debugDrawerStore: DebugDrawerStore,
|
||||||
|
browserStore: BrowserStore,
|
||||||
|
inactiveTabsEnabled: Boolean,
|
||||||
|
): List<DebugDrawerDestination> =
|
||||||
|
DebugDrawerRoute.values().map { debugDrawerRoute ->
|
||||||
|
val onClick: () -> Unit
|
||||||
|
val content: @Composable () -> Unit
|
||||||
|
when (debugDrawerRoute) {
|
||||||
|
TabTools -> {
|
||||||
|
onClick = {
|
||||||
|
debugDrawerStore.dispatch(DebugDrawerAction.NavigateTo.TabTools)
|
||||||
|
}
|
||||||
|
content = {
|
||||||
|
TabToolsScreen(
|
||||||
|
store = browserStore,
|
||||||
|
inactiveTabsEnabled = inactiveTabsEnabled,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DebugDrawerDestination(
|
||||||
|
route = debugDrawerRoute.route,
|
||||||
|
title = debugDrawerRoute.title,
|
||||||
|
onClick = onClick,
|
||||||
|
content = content,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
/* 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.debugsettings.store
|
||||||
|
|
||||||
|
import mozilla.components.lib.state.Action
|
||||||
|
import org.mozilla.fenix.debugsettings.ui.DebugDrawerHome
|
||||||
|
import org.mozilla.fenix.debugsettings.tabs.TabTools as TabToolsScreen
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [Action] implementation related to [DebugDrawerStore].
|
||||||
|
*/
|
||||||
|
sealed class DebugDrawerAction : Action {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [DebugDrawerAction] fired when the user opens the drawer.
|
||||||
|
*/
|
||||||
|
object DrawerOpened : DebugDrawerAction()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [DebugDrawerAction] fired when the user closes the drawer.
|
||||||
|
*/
|
||||||
|
object DrawerClosed : DebugDrawerAction()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [DebugDrawerAction] fired when a navigation event occurs for a specific destination.
|
||||||
|
*/
|
||||||
|
sealed class NavigateTo : DebugDrawerAction() {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [NavigateTo] action fired when the debug drawer needs to navigate to [DebugDrawerHome].
|
||||||
|
*/
|
||||||
|
object Home : NavigateTo()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [NavigateTo] action fired when the debug drawer needs to navigate to [TabToolsScreen].
|
||||||
|
*/
|
||||||
|
object TabTools : NavigateTo()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [DebugDrawerAction] fired when a back navigation event occurs.
|
||||||
|
*/
|
||||||
|
object OnBackPressed : DebugDrawerAction()
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
/* 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.debugsettings.store
|
||||||
|
|
||||||
|
import androidx.navigation.NavHostController
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import mozilla.components.lib.state.Middleware
|
||||||
|
import mozilla.components.lib.state.MiddlewareContext
|
||||||
|
import org.mozilla.fenix.debugsettings.navigation.DebugDrawerRoute
|
||||||
|
import org.mozilla.fenix.debugsettings.ui.DEBUG_DRAWER_HOME_ROUTE
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Middleware that handles navigation events for the Debug Drawer feature.
|
||||||
|
*
|
||||||
|
* @param navController [NavHostController] used to execute any navigation actions on the UI.
|
||||||
|
* @param scope [CoroutineScope] used to make calls to the main thread.
|
||||||
|
*/
|
||||||
|
class DebugDrawerNavigationMiddleware(
|
||||||
|
private val navController: NavHostController,
|
||||||
|
private val scope: CoroutineScope,
|
||||||
|
) : Middleware<DebugDrawerState, DebugDrawerAction> {
|
||||||
|
|
||||||
|
override fun invoke(
|
||||||
|
context: MiddlewareContext<DebugDrawerState, DebugDrawerAction>,
|
||||||
|
next: (DebugDrawerAction) -> Unit,
|
||||||
|
action: DebugDrawerAction,
|
||||||
|
) {
|
||||||
|
next(action)
|
||||||
|
scope.launch {
|
||||||
|
when (action) {
|
||||||
|
is DebugDrawerAction.NavigateTo.Home -> navController.popBackStack(
|
||||||
|
route = DEBUG_DRAWER_HOME_ROUTE,
|
||||||
|
inclusive = false,
|
||||||
|
)
|
||||||
|
is DebugDrawerAction.NavigateTo.TabTools ->
|
||||||
|
navController.navigate(route = DebugDrawerRoute.TabTools.route)
|
||||||
|
is DebugDrawerAction.OnBackPressed -> navController.popBackStack()
|
||||||
|
is DebugDrawerAction.DrawerOpened, DebugDrawerAction.DrawerClosed -> Unit // no-op
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
/* 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.debugsettings.store
|
||||||
|
|
||||||
|
import mozilla.components.lib.state.State
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UI state of the debug drawer feature.
|
||||||
|
*
|
||||||
|
* @property drawerStatus The [DrawerStatus] indicating the physical state of the drawer.
|
||||||
|
*/
|
||||||
|
data class DebugDrawerState(
|
||||||
|
val drawerStatus: DrawerStatus = DrawerStatus.Closed,
|
||||||
|
) : State
|
|
@ -0,0 +1,27 @@
|
||||||
|
/* 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.debugsettings.store
|
||||||
|
|
||||||
|
import mozilla.components.lib.state.Middleware
|
||||||
|
import mozilla.components.lib.state.Store
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A [Store] that holds the [DebugDrawerState] for the Debug Drawer and reduces [DebugDrawerAction]s
|
||||||
|
* dispatched to the store.
|
||||||
|
*/
|
||||||
|
class DebugDrawerStore(
|
||||||
|
initialState: DebugDrawerState = DebugDrawerState(),
|
||||||
|
middlewares: List<Middleware<DebugDrawerState, DebugDrawerAction>> = emptyList(),
|
||||||
|
) : Store<DebugDrawerState, DebugDrawerAction>(
|
||||||
|
initialState,
|
||||||
|
::reduce,
|
||||||
|
middlewares,
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun reduce(state: DebugDrawerState, action: DebugDrawerAction): DebugDrawerState = when (action) {
|
||||||
|
is DebugDrawerAction.DrawerOpened -> state.copy(drawerStatus = DrawerStatus.Open)
|
||||||
|
is DebugDrawerAction.DrawerClosed -> state.copy(drawerStatus = DrawerStatus.Closed)
|
||||||
|
is DebugDrawerAction.NavigateTo, DebugDrawerAction.OnBackPressed -> state // handled by [DebugDrawerNavigationMiddleware]
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
/* 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.debugsettings.store
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Possible values of the debug drawer's physical state.
|
||||||
|
*/
|
||||||
|
enum class DrawerStatus {
|
||||||
|
/**
|
||||||
|
* The state of the drawer when it is closed.
|
||||||
|
*/
|
||||||
|
Closed,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The state of the drawer when it is open.
|
||||||
|
*/
|
||||||
|
Open,
|
||||||
|
}
|
|
@ -0,0 +1,328 @@
|
||||||
|
/* 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.debugsettings.tabs
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.text.KeyboardActions
|
||||||
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.material.TextField
|
||||||
|
import androidx.compose.material.TextFieldDefaults
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.core.text.isDigitsOnly
|
||||||
|
import mozilla.components.browser.state.action.TabListAction
|
||||||
|
import mozilla.components.browser.state.state.createTab
|
||||||
|
import mozilla.components.browser.state.store.BrowserStore
|
||||||
|
import mozilla.components.lib.state.ext.observeAsState
|
||||||
|
import org.mozilla.fenix.R
|
||||||
|
import org.mozilla.fenix.compose.Divider
|
||||||
|
import org.mozilla.fenix.compose.annotation.LightDarkPreview
|
||||||
|
import org.mozilla.fenix.compose.button.PrimaryButton
|
||||||
|
import org.mozilla.fenix.debugsettings.ui.DebugDrawer
|
||||||
|
import org.mozilla.fenix.ext.maxActiveTime
|
||||||
|
import org.mozilla.fenix.tabstray.ext.isNormalTabInactive
|
||||||
|
import org.mozilla.fenix.theme.FirefoxTheme
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tab Tools UI for [DebugDrawer] that displays the tab counts and allows easy bulk-opening of tabs.
|
||||||
|
*
|
||||||
|
* @param store [BrowserStore] used to obtain the tab counts and fire any tab creation actions.
|
||||||
|
* @param inactiveTabsEnabled Whether the inactive tabs feature is enabled.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun TabTools(
|
||||||
|
store: BrowserStore,
|
||||||
|
inactiveTabsEnabled: Boolean,
|
||||||
|
) {
|
||||||
|
val tabs by store.observeAsState(initialValue = emptyList()) { state -> state.tabs }
|
||||||
|
val totalTabCount = remember(tabs) { tabs.size }
|
||||||
|
val privateTabCount = remember(tabs) { tabs.filter { it.content.private }.size }
|
||||||
|
val inactiveTabCount = remember(tabs) {
|
||||||
|
if (inactiveTabsEnabled) {
|
||||||
|
tabs.filter { it.isNormalTabInactive(maxActiveTime) }.size
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val activeTabCount = remember(tabs) { totalTabCount - privateTabCount - inactiveTabCount }
|
||||||
|
|
||||||
|
TabToolsContent(
|
||||||
|
activeTabCount = activeTabCount,
|
||||||
|
inactiveTabCount = inactiveTabCount,
|
||||||
|
privateTabCount = privateTabCount,
|
||||||
|
totalTabCount = totalTabCount,
|
||||||
|
inactiveTabsEnabled = inactiveTabsEnabled,
|
||||||
|
onCreateTabsClick = { quantity, isInactive, isPrivate ->
|
||||||
|
store.dispatch(
|
||||||
|
TabListAction.AddMultipleTabsAction(
|
||||||
|
tabs = generateTabList(
|
||||||
|
quantity = quantity,
|
||||||
|
isInactive = isInactive,
|
||||||
|
isPrivate = isPrivate,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generateTabList(
|
||||||
|
quantity: Int,
|
||||||
|
isInactive: Boolean = false,
|
||||||
|
isPrivate: Boolean = false,
|
||||||
|
) = List(quantity) {
|
||||||
|
createTab(
|
||||||
|
url = "www.example.com",
|
||||||
|
private = isPrivate,
|
||||||
|
createdAt = if (isInactive) 0L else System.currentTimeMillis(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun TabToolsContent(
|
||||||
|
activeTabCount: Int,
|
||||||
|
inactiveTabCount: Int,
|
||||||
|
privateTabCount: Int,
|
||||||
|
totalTabCount: Int,
|
||||||
|
inactiveTabsEnabled: Boolean,
|
||||||
|
onCreateTabsClick: ((quantity: Int, isInactive: Boolean, isPrivate: Boolean) -> Unit),
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(all = 16.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
|
) {
|
||||||
|
TabCounter(
|
||||||
|
activeTabCount = activeTabCount,
|
||||||
|
inactiveTabCount = inactiveTabCount,
|
||||||
|
privateTabCount = privateTabCount,
|
||||||
|
totalTabCount = totalTabCount,
|
||||||
|
inactiveTabsEnabled = inactiveTabsEnabled,
|
||||||
|
)
|
||||||
|
|
||||||
|
TabCreationTool(
|
||||||
|
inactiveTabsEnabled = inactiveTabsEnabled,
|
||||||
|
onCreateTabsClick = onCreateTabsClick,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun TabCounter(
|
||||||
|
activeTabCount: Int,
|
||||||
|
inactiveTabCount: Int,
|
||||||
|
privateTabCount: Int,
|
||||||
|
totalTabCount: Int,
|
||||||
|
inactiveTabsEnabled: Boolean,
|
||||||
|
) {
|
||||||
|
Column {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.debug_drawer_tab_tools_tab_count_title),
|
||||||
|
color = FirefoxTheme.colors.textPrimary,
|
||||||
|
style = FirefoxTheme.typography.headline5,
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
TabCountRow(
|
||||||
|
tabType = stringResource(R.string.debug_drawer_tab_tools_tab_count_normal),
|
||||||
|
count = activeTabCount.toString(),
|
||||||
|
)
|
||||||
|
|
||||||
|
if (inactiveTabsEnabled) {
|
||||||
|
TabCountRow(
|
||||||
|
tabType = stringResource(R.string.debug_drawer_tab_tools_tab_count_inactive),
|
||||||
|
count = inactiveTabCount.toString(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
TabCountRow(
|
||||||
|
tabType = stringResource(R.string.debug_drawer_tab_tools_tab_count_private),
|
||||||
|
count = privateTabCount.toString(),
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
Divider()
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
TabCountRow(
|
||||||
|
tabType = stringResource(R.string.debug_drawer_tab_tools_tab_count_total),
|
||||||
|
count = totalTabCount.toString(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun TabCountRow(
|
||||||
|
tabType: String,
|
||||||
|
count: String,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(start = 16.dp),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = tabType,
|
||||||
|
color = FirefoxTheme.colors.textSecondary,
|
||||||
|
style = FirefoxTheme.typography.headline6,
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = count,
|
||||||
|
color = FirefoxTheme.colors.textSecondary,
|
||||||
|
style = FirefoxTheme.typography.headline6,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private const val DEFAULT_TABS_TO_ADD = "1"
|
||||||
|
|
||||||
|
@OptIn(ExperimentalComposeUiApi::class)
|
||||||
|
@Composable
|
||||||
|
private fun TabCreationTool(
|
||||||
|
inactiveTabsEnabled: Boolean,
|
||||||
|
onCreateTabsClick: ((quantity: Int, isInactive: Boolean, isPrivate: Boolean) -> Unit),
|
||||||
|
) {
|
||||||
|
var tabQuantityToCreate by rememberSaveable { mutableStateOf(DEFAULT_TABS_TO_ADD) }
|
||||||
|
var hasError by rememberSaveable { mutableStateOf(false) }
|
||||||
|
val keyboardController = LocalSoftwareKeyboardController.current
|
||||||
|
|
||||||
|
Column {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.debug_drawer_tab_tools_tab_creation_tool_title),
|
||||||
|
color = FirefoxTheme.colors.textPrimary,
|
||||||
|
style = FirefoxTheme.typography.headline5,
|
||||||
|
)
|
||||||
|
|
||||||
|
TextField(
|
||||||
|
value = tabQuantityToCreate,
|
||||||
|
onValueChange = {
|
||||||
|
tabQuantityToCreate = it
|
||||||
|
hasError = it.isEmpty() || !it.isDigitsOnly() || it.toInt() == 0
|
||||||
|
},
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
textStyle = FirefoxTheme.typography.subtitle1,
|
||||||
|
label = {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.debug_drawer_tab_tools_tab_creation_tool_text_field_label),
|
||||||
|
color = FirefoxTheme.colors.textPrimary,
|
||||||
|
style = FirefoxTheme.typography.caption,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
isError = hasError,
|
||||||
|
keyboardOptions = KeyboardOptions(
|
||||||
|
keyboardType = KeyboardType.Number,
|
||||||
|
),
|
||||||
|
keyboardActions = KeyboardActions(
|
||||||
|
onDone = {
|
||||||
|
keyboardController?.hide()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
singleLine = true,
|
||||||
|
colors = TextFieldDefaults.textFieldColors(
|
||||||
|
textColor = FirefoxTheme.colors.textPrimary,
|
||||||
|
backgroundColor = Color.Transparent,
|
||||||
|
cursorColor = FirefoxTheme.colors.borderFormDefault,
|
||||||
|
errorCursorColor = FirefoxTheme.colors.borderWarning,
|
||||||
|
focusedIndicatorColor = FirefoxTheme.colors.borderPrimary,
|
||||||
|
unfocusedIndicatorColor = FirefoxTheme.colors.borderPrimary,
|
||||||
|
errorIndicatorColor = FirefoxTheme.colors.borderWarning,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
PrimaryButton(
|
||||||
|
text = stringResource(id = R.string.debug_drawer_tab_tools_tab_creation_tool_button_text_active),
|
||||||
|
enabled = !hasError,
|
||||||
|
onClick = {
|
||||||
|
onCreateTabsClick(tabQuantityToCreate.toInt(), false, false)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
if (inactiveTabsEnabled) {
|
||||||
|
PrimaryButton(
|
||||||
|
text = stringResource(id = R.string.debug_drawer_tab_tools_tab_creation_tool_button_text_inactive),
|
||||||
|
enabled = !hasError,
|
||||||
|
onClick = {
|
||||||
|
onCreateTabsClick(tabQuantityToCreate.toInt(), true, false)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimaryButton(
|
||||||
|
text = stringResource(id = R.string.debug_drawer_tab_tools_tab_creation_tool_button_text_private),
|
||||||
|
enabled = !hasError,
|
||||||
|
onClick = {
|
||||||
|
onCreateTabsClick(tabQuantityToCreate.toInt(), false, true)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class TabToolsPreviewModel(
|
||||||
|
val inactiveTabsEnabled: Boolean = true,
|
||||||
|
)
|
||||||
|
|
||||||
|
private class TabToolsPreviewParameterProvider : PreviewParameterProvider<TabToolsPreviewModel> {
|
||||||
|
override val values: Sequence<TabToolsPreviewModel>
|
||||||
|
get() = sequenceOf(
|
||||||
|
TabToolsPreviewModel(
|
||||||
|
inactiveTabsEnabled = true,
|
||||||
|
),
|
||||||
|
TabToolsPreviewModel(
|
||||||
|
inactiveTabsEnabled = false,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@LightDarkPreview
|
||||||
|
private fun TabToolsPreview(
|
||||||
|
@PreviewParameter(TabToolsPreviewParameterProvider::class) model: TabToolsPreviewModel,
|
||||||
|
) {
|
||||||
|
FirefoxTheme {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.background(color = FirefoxTheme.colors.layer1),
|
||||||
|
) {
|
||||||
|
TabTools(
|
||||||
|
store = BrowserStore(),
|
||||||
|
inactiveTabsEnabled = model.inactiveTabsEnabled,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,138 @@
|
||||||
|
/* 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.debugsettings.ui
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.material.Icon
|
||||||
|
import androidx.compose.material.IconButton
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.material.TopAppBar
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.navigation.NavHostController
|
||||||
|
import androidx.navigation.compose.NavHost
|
||||||
|
import androidx.navigation.compose.composable
|
||||||
|
import androidx.navigation.compose.rememberNavController
|
||||||
|
import org.mozilla.fenix.R
|
||||||
|
import org.mozilla.fenix.compose.annotation.LightDarkPreview
|
||||||
|
import org.mozilla.fenix.debugsettings.navigation.DebugDrawerDestination
|
||||||
|
import org.mozilla.fenix.theme.FirefoxTheme
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The debug drawer UI.
|
||||||
|
*
|
||||||
|
* @param navController [NavHostController] used to perform navigation actions on the [NavHost].
|
||||||
|
* @param destinations The list of [DebugDrawerDestination]s (excluding home) used to populate
|
||||||
|
* the [NavHost] with screens.
|
||||||
|
* @param onBackButtonClick Invoked when the user taps on the back button in the app bar.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun DebugDrawer(
|
||||||
|
navController: NavHostController,
|
||||||
|
destinations: List<DebugDrawerDestination>,
|
||||||
|
onBackButtonClick: () -> Unit,
|
||||||
|
) {
|
||||||
|
var backButtonVisible by remember { mutableStateOf(false) }
|
||||||
|
var toolbarTitle by remember { mutableStateOf("") }
|
||||||
|
|
||||||
|
Column(modifier = Modifier.fillMaxSize()) {
|
||||||
|
TopAppBar(
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
text = toolbarTitle,
|
||||||
|
color = FirefoxTheme.colors.textPrimary,
|
||||||
|
style = FirefoxTheme.typography.headline6,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
navigationIcon = if (backButtonVisible) {
|
||||||
|
topBarBackButton(onClick = onBackButtonClick)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
},
|
||||||
|
backgroundColor = FirefoxTheme.colors.layer1,
|
||||||
|
elevation = 5.dp,
|
||||||
|
)
|
||||||
|
|
||||||
|
NavHost(
|
||||||
|
navController = navController,
|
||||||
|
startDestination = DEBUG_DRAWER_HOME_ROUTE,
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
) {
|
||||||
|
composable(route = DEBUG_DRAWER_HOME_ROUTE) {
|
||||||
|
toolbarTitle = stringResource(id = R.string.debug_drawer_title)
|
||||||
|
backButtonVisible = false
|
||||||
|
DebugDrawerHome(destinations = destinations)
|
||||||
|
}
|
||||||
|
|
||||||
|
destinations.forEach { destination ->
|
||||||
|
composable(route = destination.route) {
|
||||||
|
toolbarTitle = stringResource(id = destination.title)
|
||||||
|
backButtonVisible = true
|
||||||
|
|
||||||
|
destination.content()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun topBarBackButton(onClick: () -> Unit): @Composable () -> Unit = {
|
||||||
|
IconButton(
|
||||||
|
onClick = onClick,
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.mozac_ic_back_24),
|
||||||
|
contentDescription = stringResource(R.string.debug_drawer_back_button_content_description),
|
||||||
|
tint = FirefoxTheme.colors.iconPrimary,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@LightDarkPreview
|
||||||
|
private fun DebugDrawerPreview() {
|
||||||
|
val navController = rememberNavController()
|
||||||
|
val destinations = remember {
|
||||||
|
List(size = 15) { index ->
|
||||||
|
DebugDrawerDestination(
|
||||||
|
route = "screen_$index",
|
||||||
|
title = R.string.debug_drawer_title,
|
||||||
|
onClick = {
|
||||||
|
navController.navigate(route = "screen_$index")
|
||||||
|
},
|
||||||
|
content = {
|
||||||
|
Text(
|
||||||
|
text = "Tool $index",
|
||||||
|
color = FirefoxTheme.colors.textPrimary,
|
||||||
|
style = FirefoxTheme.typography.headline6,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FirefoxTheme {
|
||||||
|
Box(modifier = Modifier.background(color = FirefoxTheme.colors.layer1)) {
|
||||||
|
DebugDrawer(
|
||||||
|
navController = navController,
|
||||||
|
destinations = destinations,
|
||||||
|
onBackButtonClick = {
|
||||||
|
navController.popBackStack()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,144 @@
|
||||||
|
/* 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.debugsettings.ui
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
|
import androidx.compose.material.Snackbar
|
||||||
|
import androidx.compose.material.SnackbarHost
|
||||||
|
import androidx.compose.material.SnackbarHostState
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import mozilla.components.support.ktx.android.content.appName
|
||||||
|
import mozilla.components.support.ktx.android.content.appVersionName
|
||||||
|
import org.mozilla.fenix.R
|
||||||
|
import org.mozilla.fenix.compose.Divider
|
||||||
|
import org.mozilla.fenix.compose.annotation.LightDarkPreview
|
||||||
|
import org.mozilla.fenix.compose.inComposePreview
|
||||||
|
import org.mozilla.fenix.compose.list.TextListItem
|
||||||
|
import org.mozilla.fenix.debugsettings.navigation.DebugDrawerDestination
|
||||||
|
import org.mozilla.fenix.theme.FirefoxTheme
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The navigation route for [DebugDrawerHome].
|
||||||
|
*/
|
||||||
|
const val DEBUG_DRAWER_HOME_ROUTE = "debug_drawer_home"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The home screen of the [DebugDrawer].
|
||||||
|
*
|
||||||
|
* @param destinations The list of [DebugDrawerDestination]s to display.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun DebugDrawerHome(
|
||||||
|
destinations: List<DebugDrawerDestination>,
|
||||||
|
) {
|
||||||
|
val lazyListState = rememberLazyListState()
|
||||||
|
|
||||||
|
val appName: String
|
||||||
|
val appVersion: String
|
||||||
|
if (inComposePreview) {
|
||||||
|
appName = "App Name Preview"
|
||||||
|
appVersion = "100.00.000"
|
||||||
|
} else {
|
||||||
|
appName = LocalContext.current.appName
|
||||||
|
appVersion = LocalContext.current.appVersionName
|
||||||
|
}
|
||||||
|
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(color = FirefoxTheme.colors.layer1),
|
||||||
|
state = lazyListState,
|
||||||
|
) {
|
||||||
|
item(key = "home_header") {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(all = 16.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = appName,
|
||||||
|
color = FirefoxTheme.colors.textPrimary,
|
||||||
|
style = FirefoxTheme.typography.headline5,
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = appVersion,
|
||||||
|
color = FirefoxTheme.colors.textSecondary,
|
||||||
|
style = FirefoxTheme.typography.headline5,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Divider()
|
||||||
|
}
|
||||||
|
|
||||||
|
items(
|
||||||
|
items = destinations,
|
||||||
|
key = { destination ->
|
||||||
|
destination.route
|
||||||
|
},
|
||||||
|
) { destination ->
|
||||||
|
TextListItem(
|
||||||
|
label = stringResource(id = destination.title),
|
||||||
|
onClick = destination.onClick,
|
||||||
|
)
|
||||||
|
|
||||||
|
Divider()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@LightDarkPreview
|
||||||
|
private fun DebugDrawerHomePreview() {
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
val snackbarState = remember { SnackbarHostState() }
|
||||||
|
|
||||||
|
FirefoxTheme {
|
||||||
|
Box {
|
||||||
|
DebugDrawerHome(
|
||||||
|
destinations = List(size = 30) {
|
||||||
|
DebugDrawerDestination(
|
||||||
|
route = "screen_$it",
|
||||||
|
title = R.string.debug_drawer_title,
|
||||||
|
onClick = {
|
||||||
|
scope.launch {
|
||||||
|
snackbarState.showSnackbar("item $it clicked")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
content = {},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
SnackbarHost(
|
||||||
|
hostState = snackbarState,
|
||||||
|
modifier = Modifier.align(Alignment.BottomCenter),
|
||||||
|
) { snackbarData ->
|
||||||
|
Snackbar(
|
||||||
|
snackbarData = snackbarData,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,33 +4,78 @@
|
||||||
|
|
||||||
package org.mozilla.fenix.debugsettings.ui
|
package org.mozilla.fenix.debugsettings.ui
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material.DrawerValue
|
||||||
|
import androidx.compose.material.ModalDrawer
|
||||||
import androidx.compose.material.Snackbar
|
import androidx.compose.material.Snackbar
|
||||||
import androidx.compose.material.SnackbarHost
|
import androidx.compose.material.SnackbarHost
|
||||||
import androidx.compose.material.SnackbarHostState
|
import androidx.compose.material.SnackbarHostState
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.material.rememberDrawerState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.runtime.snapshotFlow
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.unit.LayoutDirection
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import kotlinx.coroutines.launch
|
import androidx.navigation.NavHostController
|
||||||
|
import androidx.navigation.compose.rememberNavController
|
||||||
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
import kotlinx.coroutines.flow.filter
|
||||||
import org.mozilla.fenix.R
|
import org.mozilla.fenix.R
|
||||||
import org.mozilla.fenix.compose.annotation.LightDarkPreview
|
import org.mozilla.fenix.compose.annotation.LightDarkPreview
|
||||||
import org.mozilla.fenix.compose.button.FloatingActionButton
|
import org.mozilla.fenix.compose.button.FloatingActionButton
|
||||||
|
import org.mozilla.fenix.debugsettings.navigation.DebugDrawerDestination
|
||||||
|
import org.mozilla.fenix.debugsettings.store.DrawerStatus
|
||||||
import org.mozilla.fenix.theme.FirefoxTheme
|
import org.mozilla.fenix.theme.FirefoxTheme
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Overlay for presenting Fenix-wide debugging content.
|
* Overlay for presenting app-wide debugging content.
|
||||||
|
*
|
||||||
|
* @param navController [NavHostController] used to perform navigation actions.
|
||||||
|
* @param drawerStatus The [DrawerStatus] indicating the physical state of the drawer.
|
||||||
|
* @param debugDrawerDestinations The complete list of [DebugDrawerDestination]s used to populate
|
||||||
|
* the [DebugDrawer] with sub screens.
|
||||||
|
* @param onDrawerOpen Invoked when the drawer is opened.
|
||||||
|
* @param onDrawerClose Invoked when the drawer is closed.
|
||||||
|
* @param onDrawerBackButtonClick Invoked when the user taps on the back button in the app bar.
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun DebugOverlay() {
|
fun DebugOverlay(
|
||||||
|
navController: NavHostController,
|
||||||
|
drawerStatus: DrawerStatus,
|
||||||
|
debugDrawerDestinations: List<DebugDrawerDestination>,
|
||||||
|
onDrawerOpen: () -> Unit,
|
||||||
|
onDrawerClose: () -> Unit,
|
||||||
|
onDrawerBackButtonClick: () -> Unit,
|
||||||
|
) {
|
||||||
val snackbarState = remember { SnackbarHostState() }
|
val snackbarState = remember { SnackbarHostState() }
|
||||||
val scope = rememberCoroutineScope()
|
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
|
||||||
|
|
||||||
|
LaunchedEffect(drawerStatus) {
|
||||||
|
if (drawerStatus == DrawerStatus.Open) {
|
||||||
|
drawerState.open()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(drawerState) {
|
||||||
|
snapshotFlow { drawerState.currentValue }
|
||||||
|
.distinctUntilChanged()
|
||||||
|
.filter { it == DrawerValue.Closed }
|
||||||
|
.collect {
|
||||||
|
onDrawerClose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
@ -41,12 +86,41 @@ fun DebugOverlay() {
|
||||||
.align(Alignment.CenterStart)
|
.align(Alignment.CenterStart)
|
||||||
.padding(start = 16.dp),
|
.padding(start = 16.dp),
|
||||||
onClick = {
|
onClick = {
|
||||||
scope.launch {
|
onDrawerOpen()
|
||||||
snackbarState.showSnackbar("Show debug drawer")
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ModalDrawer utilizes a Surface, which blocks ALL clicks behind it, preventing the app
|
||||||
|
// from being interactable. This cannot be overridden in the Surface API, so we must hide
|
||||||
|
// the entire drawer when it is closed.
|
||||||
|
if (drawerStatus == DrawerStatus.Open) {
|
||||||
|
val currentLayoutDirection = LocalLayoutDirection.current
|
||||||
|
val sheetLayoutDirection = when (currentLayoutDirection) {
|
||||||
|
LayoutDirection.Rtl -> LayoutDirection.Ltr
|
||||||
|
LayoutDirection.Ltr -> LayoutDirection.Rtl
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force the drawer to always open from the opposite side of the screen. We need to reset
|
||||||
|
// this below with `drawerContent` to ensure the content follows the correct direction.
|
||||||
|
CompositionLocalProvider(LocalLayoutDirection provides sheetLayoutDirection) {
|
||||||
|
ModalDrawer(
|
||||||
|
drawerContent = {
|
||||||
|
CompositionLocalProvider(LocalLayoutDirection provides currentLayoutDirection) {
|
||||||
|
DebugDrawer(
|
||||||
|
navController = navController,
|
||||||
|
destinations = debugDrawerDestinations,
|
||||||
|
onBackButtonClick = onDrawerBackButtonClick,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
drawerBackgroundColor = FirefoxTheme.colors.layer1,
|
||||||
|
scrimColor = FirefoxTheme.colors.scrim,
|
||||||
|
drawerState = drawerState,
|
||||||
|
content = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// This must be the last element in the Box
|
// This must be the last element in the Box
|
||||||
SnackbarHost(
|
SnackbarHost(
|
||||||
hostState = snackbarState,
|
hostState = snackbarState,
|
||||||
|
@ -62,13 +136,41 @@ fun DebugOverlay() {
|
||||||
@Composable
|
@Composable
|
||||||
@LightDarkPreview
|
@LightDarkPreview
|
||||||
private fun DebugOverlayPreview() {
|
private fun DebugOverlayPreview() {
|
||||||
|
val navController = rememberNavController()
|
||||||
|
var drawerStatus by remember { mutableStateOf(DrawerStatus.Closed) }
|
||||||
|
val destinations = remember {
|
||||||
|
List(size = 15) { index ->
|
||||||
|
DebugDrawerDestination(
|
||||||
|
route = "screen_$index",
|
||||||
|
title = R.string.debug_drawer_title,
|
||||||
|
onClick = {
|
||||||
|
navController.navigate(route = "screen_$index")
|
||||||
|
},
|
||||||
|
content = {
|
||||||
|
Text(
|
||||||
|
text = "Tool $index",
|
||||||
|
color = FirefoxTheme.colors.textPrimary,
|
||||||
|
style = FirefoxTheme.typography.headline6,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
FirefoxTheme {
|
FirefoxTheme {
|
||||||
Box(
|
DebugOverlay(
|
||||||
modifier = Modifier
|
navController = navController,
|
||||||
.fillMaxSize()
|
drawerStatus = drawerStatus,
|
||||||
.background(color = FirefoxTheme.colors.layer1),
|
debugDrawerDestinations = destinations,
|
||||||
) {
|
onDrawerOpen = {
|
||||||
DebugOverlay()
|
drawerStatus = DrawerStatus.Open
|
||||||
}
|
},
|
||||||
|
onDrawerClose = {
|
||||||
|
drawerStatus = DrawerStatus.Closed
|
||||||
|
},
|
||||||
|
onDrawerBackButtonClick = {
|
||||||
|
navController.popBackStack()
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
/* 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.debugsettings.ui
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.navigation.compose.rememberNavController
|
||||||
|
import mozilla.components.browser.state.store.BrowserStore
|
||||||
|
import mozilla.components.lib.state.ext.observeAsState
|
||||||
|
import org.mozilla.fenix.compose.annotation.LightDarkPreview
|
||||||
|
import org.mozilla.fenix.debugsettings.navigation.DebugDrawerRoute
|
||||||
|
import org.mozilla.fenix.debugsettings.store.DebugDrawerAction
|
||||||
|
import org.mozilla.fenix.debugsettings.store.DebugDrawerNavigationMiddleware
|
||||||
|
import org.mozilla.fenix.debugsettings.store.DebugDrawerStore
|
||||||
|
import org.mozilla.fenix.debugsettings.store.DrawerStatus
|
||||||
|
import org.mozilla.fenix.debugsettings.tabs.TabTools
|
||||||
|
import org.mozilla.fenix.theme.FirefoxTheme
|
||||||
|
import org.mozilla.fenix.theme.Theme
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overlay for presenting Fenix-wide debugging content.
|
||||||
|
*
|
||||||
|
* @param browserStore [BrowserStore] used to access tab data for [TabTools].
|
||||||
|
* @param inactiveTabsEnabled Whether the inactive tabs feature is enabled.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun FenixOverlay(
|
||||||
|
browserStore: BrowserStore,
|
||||||
|
inactiveTabsEnabled: Boolean,
|
||||||
|
) {
|
||||||
|
val navController = rememberNavController()
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
val debugDrawerStore = remember {
|
||||||
|
DebugDrawerStore(
|
||||||
|
middlewares = listOf(
|
||||||
|
DebugDrawerNavigationMiddleware(
|
||||||
|
navController = navController,
|
||||||
|
scope = coroutineScope,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val debugDrawerDestinations = remember {
|
||||||
|
DebugDrawerRoute.generateDebugDrawerDestinations(
|
||||||
|
debugDrawerStore = debugDrawerStore,
|
||||||
|
browserStore = browserStore,
|
||||||
|
inactiveTabsEnabled = inactiveTabsEnabled,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val drawerStatus by debugDrawerStore.observeAsState(initialValue = DrawerStatus.Closed) { state ->
|
||||||
|
state.drawerStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
FirefoxTheme(theme = Theme.getTheme(allowPrivateTheme = false)) {
|
||||||
|
DebugOverlay(
|
||||||
|
navController = navController,
|
||||||
|
drawerStatus = drawerStatus,
|
||||||
|
debugDrawerDestinations = debugDrawerDestinations,
|
||||||
|
onDrawerOpen = {
|
||||||
|
debugDrawerStore.dispatch(DebugDrawerAction.DrawerOpened)
|
||||||
|
},
|
||||||
|
onDrawerClose = {
|
||||||
|
debugDrawerStore.dispatch(DebugDrawerAction.DrawerClosed)
|
||||||
|
},
|
||||||
|
onDrawerBackButtonClick = {
|
||||||
|
debugDrawerStore.dispatch(DebugDrawerAction.OnBackPressed)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@LightDarkPreview
|
||||||
|
@Composable
|
||||||
|
private fun FenixOverlayPreview() {
|
||||||
|
FenixOverlay(
|
||||||
|
browserStore = BrowserStore(),
|
||||||
|
inactiveTabsEnabled = true,
|
||||||
|
)
|
||||||
|
}
|
|
@ -70,7 +70,9 @@ fun createNimbus(context: Context, urlString: String?): NimbusApi {
|
||||||
onFetchCallback = {
|
onFetchCallback = {
|
||||||
context.settings().nimbusExperimentsFetched = true
|
context.settings().nimbusExperimentsFetched = true
|
||||||
}
|
}
|
||||||
}.build(appInfo)
|
}.build(appInfo).also { nimbusApi ->
|
||||||
|
nimbusApi.recordIsReady(FxNimbus.features.nimbusIsReady.value().eventCount)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Context.reportError(message: String, e: Throwable) {
|
private fun Context.reportError(message: String, e: Throwable) {
|
||||||
|
|
|
@ -15,12 +15,47 @@ import androidx.annotation.DrawableRes
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
|
import androidx.navigation.NavDestination
|
||||||
|
import androidx.navigation.NavDirections
|
||||||
import mozilla.components.concept.base.crash.Breadcrumb
|
import mozilla.components.concept.base.crash.Breadcrumb
|
||||||
import mozilla.components.concept.engine.EngineSession
|
import mozilla.components.concept.engine.EngineSession
|
||||||
|
import mozilla.components.concept.engine.manifest.WebAppManifestParser
|
||||||
|
import mozilla.components.feature.intent.ext.getSessionId
|
||||||
|
import mozilla.components.feature.pwa.ext.getWebAppManifest
|
||||||
|
import mozilla.components.support.utils.SafeIntent
|
||||||
import org.mozilla.fenix.BrowserDirection
|
import org.mozilla.fenix.BrowserDirection
|
||||||
import org.mozilla.fenix.HomeActivity
|
import org.mozilla.fenix.HomeActivity
|
||||||
|
import org.mozilla.fenix.NavGraphDirections
|
||||||
import org.mozilla.fenix.R
|
import org.mozilla.fenix.R
|
||||||
|
import org.mozilla.fenix.addons.AddonDetailsFragmentDirections
|
||||||
|
import org.mozilla.fenix.addons.AddonPermissionsDetailsFragmentDirections
|
||||||
|
import org.mozilla.fenix.addons.AddonsManagementFragmentDirections
|
||||||
|
import org.mozilla.fenix.customtabs.EXTRA_IS_SANDBOX_CUSTOM_TAB
|
||||||
|
import org.mozilla.fenix.customtabs.ExternalAppBrowserActivity
|
||||||
|
import org.mozilla.fenix.exceptions.trackingprotection.TrackingProtectionExceptionsFragmentDirections
|
||||||
|
import org.mozilla.fenix.home.HomeFragmentDirections
|
||||||
|
import org.mozilla.fenix.library.bookmarks.BookmarkFragmentDirections
|
||||||
|
import org.mozilla.fenix.library.history.HistoryFragmentDirections
|
||||||
|
import org.mozilla.fenix.library.historymetadata.HistoryMetadataGroupFragmentDirections
|
||||||
|
import org.mozilla.fenix.library.recentlyclosed.RecentlyClosedFragmentDirections
|
||||||
|
import org.mozilla.fenix.search.SearchDialogFragmentDirections
|
||||||
|
import org.mozilla.fenix.settings.HttpsOnlyFragmentDirections
|
||||||
|
import org.mozilla.fenix.settings.SettingsFragmentDirections
|
||||||
import org.mozilla.fenix.settings.SupportUtils
|
import org.mozilla.fenix.settings.SupportUtils
|
||||||
|
import org.mozilla.fenix.settings.TrackingProtectionFragmentDirections
|
||||||
|
import org.mozilla.fenix.settings.about.AboutFragmentDirections
|
||||||
|
import org.mozilla.fenix.settings.logins.fragment.LoginDetailFragmentDirections
|
||||||
|
import org.mozilla.fenix.settings.logins.fragment.SavedLoginsAuthFragmentDirections
|
||||||
|
import org.mozilla.fenix.settings.search.SaveSearchEngineFragmentDirections
|
||||||
|
import org.mozilla.fenix.settings.search.SearchEngineFragmentDirections
|
||||||
|
import org.mozilla.fenix.settings.studies.StudiesFragmentDirections
|
||||||
|
import org.mozilla.fenix.settings.wallpaper.WallpaperSettingsFragmentDirections
|
||||||
|
import org.mozilla.fenix.share.AddNewDeviceFragmentDirections
|
||||||
|
import org.mozilla.fenix.shopping.ReviewQualityCheckFragmentDirections
|
||||||
|
import org.mozilla.fenix.tabstray.TabsTrayFragmentDirections
|
||||||
|
import org.mozilla.fenix.trackingprotection.TrackingProtectionPanelDialogFragmentDirections
|
||||||
|
import org.mozilla.fenix.translations.TranslationsDialogFragmentDirections
|
||||||
|
import java.security.InvalidParameterException
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempts to call immersive mode using the View to hide the status bar and navigation buttons.
|
* Attempts to call immersive mode using the View to hide the status bar and navigation buttons.
|
||||||
|
@ -170,7 +205,164 @@ fun Activity.setNavigationIcon(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delegate to the relevant 'get nav directions' function based on the given [Activity].
|
||||||
|
*
|
||||||
|
* @param from The [BrowserDirection] to indicate which fragment the browser is being opened from.
|
||||||
|
* @param customTabSessionId Optional custom tab session ID if navigating from a custom tab.
|
||||||
|
*
|
||||||
|
* @return the [NavDirections] for the given [Activity].
|
||||||
|
*/
|
||||||
|
fun Activity.getNavDirections(
|
||||||
|
from: BrowserDirection,
|
||||||
|
customTabSessionId: String? = null,
|
||||||
|
): NavDirections? = when (this) {
|
||||||
|
is ExternalAppBrowserActivity -> {
|
||||||
|
getExternalAppBrowserNavDirections(from, customTabSessionId)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
getHomeNavDirections(from)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Activity.getExternalAppBrowserNavDirections(
|
||||||
|
from: BrowserDirection,
|
||||||
|
customTabSessionId: String?,
|
||||||
|
): NavDirections? {
|
||||||
|
if (customTabSessionId == null) {
|
||||||
|
finishAndRemoveTask()
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val manifest =
|
||||||
|
intent.getWebAppManifest()?.let { WebAppManifestParser().serialize(it).toString() }
|
||||||
|
|
||||||
|
return when (from) {
|
||||||
|
BrowserDirection.FromGlobal ->
|
||||||
|
NavGraphDirections.actionGlobalExternalAppBrowser(
|
||||||
|
activeSessionId = customTabSessionId,
|
||||||
|
webAppManifest = manifest,
|
||||||
|
isSandboxCustomTab = intent.getBooleanExtra(EXTRA_IS_SANDBOX_CUSTOM_TAB, false),
|
||||||
|
)
|
||||||
|
|
||||||
|
else -> throw InvalidParameterException(
|
||||||
|
"Tried to navigate to ExternalAppBrowserFragment from $from",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getHomeNavDirections(
|
||||||
|
from: BrowserDirection,
|
||||||
|
): NavDirections = when (from) {
|
||||||
|
BrowserDirection.FromGlobal -> NavGraphDirections.actionGlobalBrowser()
|
||||||
|
|
||||||
|
BrowserDirection.FromHome -> HomeFragmentDirections.actionGlobalBrowser()
|
||||||
|
|
||||||
|
BrowserDirection.FromWallpaper -> WallpaperSettingsFragmentDirections.actionGlobalBrowser()
|
||||||
|
|
||||||
|
BrowserDirection.FromSearchDialog -> SearchDialogFragmentDirections.actionGlobalBrowser()
|
||||||
|
|
||||||
|
BrowserDirection.FromSettings -> SettingsFragmentDirections.actionGlobalBrowser()
|
||||||
|
|
||||||
|
BrowserDirection.FromBookmarks -> BookmarkFragmentDirections.actionGlobalBrowser()
|
||||||
|
|
||||||
|
BrowserDirection.FromHistory -> HistoryFragmentDirections.actionGlobalBrowser()
|
||||||
|
|
||||||
|
BrowserDirection.FromHistoryMetadataGroup -> HistoryMetadataGroupFragmentDirections.actionGlobalBrowser()
|
||||||
|
|
||||||
|
BrowserDirection.FromTrackingProtectionExceptions ->
|
||||||
|
TrackingProtectionExceptionsFragmentDirections.actionGlobalBrowser()
|
||||||
|
|
||||||
|
BrowserDirection.FromHttpsOnlyMode -> HttpsOnlyFragmentDirections.actionGlobalBrowser()
|
||||||
|
|
||||||
|
BrowserDirection.FromAbout -> AboutFragmentDirections.actionGlobalBrowser()
|
||||||
|
|
||||||
|
BrowserDirection.FromTrackingProtection -> TrackingProtectionFragmentDirections.actionGlobalBrowser()
|
||||||
|
|
||||||
|
BrowserDirection.FromTrackingProtectionDialog ->
|
||||||
|
TrackingProtectionPanelDialogFragmentDirections.actionGlobalBrowser()
|
||||||
|
|
||||||
|
BrowserDirection.FromSavedLoginsFragment -> SavedLoginsAuthFragmentDirections.actionGlobalBrowser()
|
||||||
|
|
||||||
|
BrowserDirection.FromAddNewDeviceFragment -> AddNewDeviceFragmentDirections.actionGlobalBrowser()
|
||||||
|
|
||||||
|
BrowserDirection.FromSearchEngineFragment -> SearchEngineFragmentDirections.actionGlobalBrowser()
|
||||||
|
|
||||||
|
BrowserDirection.FromSaveSearchEngineFragment -> SaveSearchEngineFragmentDirections.actionGlobalBrowser()
|
||||||
|
|
||||||
|
BrowserDirection.FromAddonDetailsFragment -> AddonDetailsFragmentDirections.actionGlobalBrowser()
|
||||||
|
|
||||||
|
BrowserDirection.FromAddonPermissionsDetailsFragment ->
|
||||||
|
AddonPermissionsDetailsFragmentDirections.actionGlobalBrowser()
|
||||||
|
|
||||||
|
BrowserDirection.FromLoginDetailFragment -> LoginDetailFragmentDirections.actionGlobalBrowser()
|
||||||
|
|
||||||
|
BrowserDirection.FromTabsTray -> TabsTrayFragmentDirections.actionGlobalBrowser()
|
||||||
|
|
||||||
|
BrowserDirection.FromRecentlyClosed -> RecentlyClosedFragmentDirections.actionGlobalBrowser()
|
||||||
|
|
||||||
|
BrowserDirection.FromStudiesFragment -> StudiesFragmentDirections.actionGlobalBrowser()
|
||||||
|
|
||||||
|
BrowserDirection.FromReviewQualityCheck -> ReviewQualityCheckFragmentDirections.actionGlobalBrowser()
|
||||||
|
|
||||||
|
BrowserDirection.FromAddonsManagementFragment -> AddonsManagementFragmentDirections.actionGlobalBrowser()
|
||||||
|
|
||||||
|
BrowserDirection.FromTranslationsDialogFragment -> TranslationsDialogFragmentDirections.actionGlobalBrowser()
|
||||||
|
}
|
||||||
|
|
||||||
const val REQUEST_CODE_BROWSER_ROLE = 1
|
const val REQUEST_CODE_BROWSER_ROLE = 1
|
||||||
const val SETTINGS_SELECT_OPTION_KEY = ":settings:fragment_args_key"
|
const val SETTINGS_SELECT_OPTION_KEY = ":settings:fragment_args_key"
|
||||||
const val SETTINGS_SHOW_FRAGMENT_ARGS = ":settings:show_fragment_args"
|
const val SETTINGS_SHOW_FRAGMENT_ARGS = ":settings:show_fragment_args"
|
||||||
const val DEFAULT_BROWSER_APP_OPTION = "default_browser"
|
const val DEFAULT_BROWSER_APP_OPTION = "default_browser"
|
||||||
|
const val EXTERNAL_APP_BROWSER_INTENT_SOURCE = "CUSTOM_TAB"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Depending on the [Activity], maybe derive the source of the given [intent].
|
||||||
|
*
|
||||||
|
* @param intent the [SafeIntent] to derive the source from.
|
||||||
|
*/
|
||||||
|
fun Activity.getIntentSource(intent: SafeIntent): String? = when (this) {
|
||||||
|
is ExternalAppBrowserActivity -> EXTERNAL_APP_BROWSER_INTENT_SOURCE
|
||||||
|
else -> getHomeIntentSource(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getHomeIntentSource(intent: SafeIntent): String? {
|
||||||
|
return when {
|
||||||
|
intent.isLauncherIntent -> HomeActivity.APP_ICON
|
||||||
|
intent.action == Intent.ACTION_VIEW -> "LINK"
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Depending on the [Activity], maybe derive the session ID of the given [intent].
|
||||||
|
*
|
||||||
|
* @param intent the [SafeIntent] to derive the session ID from.
|
||||||
|
*/
|
||||||
|
fun Activity.getIntentSessionId(intent: SafeIntent): String? = when (this) {
|
||||||
|
is ExternalAppBrowserActivity -> getExternalAppBrowserIntentSessionId(intent)
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getExternalAppBrowserIntentSessionId(intent: SafeIntent) = intent.getSessionId()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the breadcrumb message for the [Activity].
|
||||||
|
*
|
||||||
|
* @param destination the [NavDestination] required to provide the destination ID.
|
||||||
|
*/
|
||||||
|
fun Activity.getBreadcrumbMessage(destination: NavDestination): String = when (this) {
|
||||||
|
is ExternalAppBrowserActivity -> getExternalAppBrowserBreadcrumbMessage(destination.id)
|
||||||
|
else -> getHomeBreadcrumbMessage(destination.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Activity.getExternalAppBrowserBreadcrumbMessage(destinationId: Int): String {
|
||||||
|
val fragmentName = resources.getResourceEntryName(destinationId)
|
||||||
|
return "Changing to fragment $fragmentName, isCustomTab: true"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Activity.getHomeBreadcrumbMessage(destinationId: Int): String {
|
||||||
|
val fragmentName = resources.getResourceEntryName(destinationId)
|
||||||
|
return "Changing to fragment $fragmentName, isCustomTab: false"
|
||||||
|
}
|
||||||
|
|
|
@ -19,7 +19,6 @@ import mozilla.components.service.sync.logins.GeckoLoginStorageDelegate
|
||||||
import org.mozilla.fenix.Config
|
import org.mozilla.fenix.Config
|
||||||
import org.mozilla.fenix.ext.components
|
import org.mozilla.fenix.ext.components
|
||||||
import org.mozilla.fenix.ext.settings
|
import org.mozilla.fenix.ext.settings
|
||||||
import org.mozilla.fenix.nimbus.FxNimbus
|
|
||||||
import org.mozilla.geckoview.ContentBlocking
|
import org.mozilla.geckoview.ContentBlocking
|
||||||
import org.mozilla.geckoview.ContentBlocking.SafeBrowsingProvider
|
import org.mozilla.geckoview.ContentBlocking.SafeBrowsingProvider
|
||||||
import org.mozilla.geckoview.GeckoRuntime
|
import org.mozilla.geckoview.GeckoRuntime
|
||||||
|
@ -134,7 +133,7 @@ object GeckoProvider {
|
||||||
.consoleOutput(context.components.settings.enableGeckoLogs)
|
.consoleOutput(context.components.settings.enableGeckoLogs)
|
||||||
.debugLogging(Config.channel.isDebug || context.components.settings.enableGeckoLogs)
|
.debugLogging(Config.channel.isDebug || context.components.settings.enableGeckoLogs)
|
||||||
.aboutConfigEnabled(true)
|
.aboutConfigEnabled(true)
|
||||||
.extensionsProcessEnabled(FxNimbus.features.extensionsProcess.value().enabled)
|
.extensionsProcessEnabled(true)
|
||||||
.extensionsWebAPIEnabled(true)
|
.extensionsWebAPIEnabled(true)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,7 +49,9 @@ import kotlinx.coroutines.Dispatchers.Main
|
||||||
import kotlinx.coroutines.MainScope
|
import kotlinx.coroutines.MainScope
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
import kotlinx.coroutines.flow.filter
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.isActive
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import mozilla.components.browser.state.selector.findTab
|
import mozilla.components.browser.state.selector.findTab
|
||||||
import mozilla.components.browser.state.selector.normalTabs
|
import mozilla.components.browser.state.selector.normalTabs
|
||||||
|
@ -119,7 +121,6 @@ import org.mozilla.fenix.messaging.FenixNimbusMessagingController
|
||||||
import org.mozilla.fenix.messaging.MessagingFeature
|
import org.mozilla.fenix.messaging.MessagingFeature
|
||||||
import org.mozilla.fenix.nimbus.FxNimbus
|
import org.mozilla.fenix.nimbus.FxNimbus
|
||||||
import org.mozilla.fenix.perf.MarkersFragmentLifecycleCallbacks
|
import org.mozilla.fenix.perf.MarkersFragmentLifecycleCallbacks
|
||||||
import org.mozilla.fenix.perf.runBlockingIncrement
|
|
||||||
import org.mozilla.fenix.search.toolbar.DefaultSearchSelectorController
|
import org.mozilla.fenix.search.toolbar.DefaultSearchSelectorController
|
||||||
import org.mozilla.fenix.search.toolbar.SearchSelectorMenu
|
import org.mozilla.fenix.search.toolbar.SearchSelectorMenu
|
||||||
import org.mozilla.fenix.tabstray.TabsTrayAccessPoint
|
import org.mozilla.fenix.tabstray.TabsTrayAccessPoint
|
||||||
|
@ -1007,17 +1008,18 @@ class HomeFragment : Fragment() {
|
||||||
lastAppliedWallpaperName = wallpaperName
|
lastAppliedWallpaperName = wallpaperName
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
runBlockingIncrement {
|
viewLifecycleOwner.lifecycleScope.launch {
|
||||||
// loadBitmap does file lookups based on name, so we don't need a fully
|
// loadBitmap does file lookups based on name, so we don't need a fully
|
||||||
// qualified type to load the image
|
// qualified type to load the image
|
||||||
val wallpaper = Wallpaper.Default.copy(name = wallpaperName)
|
val wallpaper = Wallpaper.Default.copy(name = wallpaperName)
|
||||||
val wallpaperImage =
|
val wallpaperImage =
|
||||||
requireComponents.useCases.wallpaperUseCases.loadBitmap(wallpaper)
|
context?.let { requireComponents.useCases.wallpaperUseCases.loadBitmap(it, wallpaper) }
|
||||||
wallpaperImage?.let {
|
wallpaperImage?.let {
|
||||||
it.scaleToBottomOfView(binding.wallpaperImageView)
|
it.scaleToBottomOfView(binding.wallpaperImageView)
|
||||||
binding.wallpaperImageView.isVisible = true
|
binding.wallpaperImageView.isVisible = true
|
||||||
lastAppliedWallpaperName = wallpaperName
|
lastAppliedWallpaperName = wallpaperName
|
||||||
} ?: run {
|
} ?: run {
|
||||||
|
if (!isActive) return@run
|
||||||
with(binding.wallpaperImageView) {
|
with(binding.wallpaperImageView) {
|
||||||
isVisible = false
|
isVisible = false
|
||||||
showSnackBar(
|
showSnackBar(
|
||||||
|
@ -1051,10 +1053,14 @@ class HomeFragment : Fragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun observeWallpaperUpdates() {
|
private fun observeWallpaperUpdates() {
|
||||||
consumeFrom(requireComponents.appStore) {
|
consumeFlow(requireComponents.appStore, viewLifecycleOwner) { flow ->
|
||||||
val currentWallpaper = it.wallpaperState.currentWallpaper
|
flow.filter { it.mode == BrowsingMode.Normal }
|
||||||
if (currentWallpaper.name != lastAppliedWallpaperName) {
|
.map { it.wallpaperState.currentWallpaper }
|
||||||
applyWallpaper(wallpaperName = currentWallpaper.name, orientationChange = false)
|
.distinctUntilChanged()
|
||||||
|
.collect {
|
||||||
|
if (it.name != lastAppliedWallpaperName) {
|
||||||
|
applyWallpaper(wallpaperName = it.name, orientationChange = false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,7 +47,6 @@ import org.mozilla.fenix.GleanMetrics.HomeMenu as HomeMenuMetrics
|
||||||
* clicked.
|
* clicked.
|
||||||
* @param fxaEntrypoint The source entry point to FxA.
|
* @param fxaEntrypoint The source entry point to FxA.
|
||||||
*/
|
*/
|
||||||
@Suppress("LongParameterList")
|
|
||||||
class HomeMenuView(
|
class HomeMenuView(
|
||||||
private val view: View,
|
private val view: View,
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
|
|
|
@ -239,7 +239,7 @@ fun PocketSponsoredStory(
|
||||||
* @param onDiscoverMoreClicked Callback for when the user taps an element which contains an
|
* @param onDiscoverMoreClicked Callback for when the user taps an element which contains an
|
||||||
*/
|
*/
|
||||||
@OptIn(ExperimentalComposeUiApi::class)
|
@OptIn(ExperimentalComposeUiApi::class)
|
||||||
@Suppress("LongParameterList", "LongMethod")
|
@Suppress("LongMethod")
|
||||||
@Composable
|
@Composable
|
||||||
fun PocketStories(
|
fun PocketStories(
|
||||||
@PreviewParameter(PocketStoryProvider::class) stories: List<PocketStory>,
|
@PreviewParameter(PocketStoryProvider::class) stories: List<PocketStory>,
|
||||||
|
@ -367,7 +367,6 @@ private fun alignColumnToTitlePadding(screenWidth: Dp, contentPadding: Dp) =
|
||||||
* @param onCategoryClick Callback for when the user taps a category.
|
* @param onCategoryClick Callback for when the user taps a category.
|
||||||
*/
|
*/
|
||||||
@OptIn(ExperimentalComposeUiApi::class)
|
@OptIn(ExperimentalComposeUiApi::class)
|
||||||
@Suppress("LongParameterList")
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PocketStoriesCategories(
|
fun PocketStoriesCategories(
|
||||||
categories: List<PocketRecommendedStoriesCategory>,
|
categories: List<PocketRecommendedStoriesCategory>,
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
package org.mozilla.fenix.home.recentbookmarks.view
|
package org.mozilla.fenix.home.recentbookmarks.view
|
||||||
|
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.Image
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
|
@ -42,10 +41,10 @@ import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import mozilla.components.browser.icons.compose.Loader
|
import mozilla.components.browser.icons.compose.Loader
|
||||||
import mozilla.components.browser.icons.compose.Placeholder
|
import mozilla.components.browser.icons.compose.Placeholder
|
||||||
import mozilla.components.browser.icons.compose.WithIcon
|
|
||||||
import mozilla.components.ui.colors.PhotonColors
|
import mozilla.components.ui.colors.PhotonColors
|
||||||
import org.mozilla.fenix.components.components
|
import org.mozilla.fenix.components.components
|
||||||
import org.mozilla.fenix.compose.ContextualMenu
|
import org.mozilla.fenix.compose.ContextualMenu
|
||||||
|
import org.mozilla.fenix.compose.Favicon
|
||||||
import org.mozilla.fenix.compose.Image
|
import org.mozilla.fenix.compose.Image
|
||||||
import org.mozilla.fenix.compose.MenuItem
|
import org.mozilla.fenix.compose.MenuItem
|
||||||
import org.mozilla.fenix.compose.annotation.LightDarkPreview
|
import org.mozilla.fenix.compose.annotation.LightDarkPreview
|
||||||
|
@ -172,6 +171,11 @@ private fun RecentBookmarkImage(bookmark: RecentBookmark) {
|
||||||
modifier = imageModifier,
|
modifier = imageModifier,
|
||||||
targetSize = imageWidth,
|
targetSize = imageWidth,
|
||||||
contentScale = ContentScale.Crop,
|
contentScale = ContentScale.Crop,
|
||||||
|
fallback = {
|
||||||
|
if (!bookmark.url.isNullOrEmpty()) {
|
||||||
|
FallbackBookmarkFaviconImage(url = bookmark.url)
|
||||||
|
}
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
!bookmark.url.isNullOrEmpty() && !inComposePreview -> {
|
!bookmark.url.isNullOrEmpty() && !inComposePreview -> {
|
||||||
|
@ -180,23 +184,7 @@ private fun RecentBookmarkImage(bookmark: RecentBookmark) {
|
||||||
PlaceholderBookmarkImage()
|
PlaceholderBookmarkImage()
|
||||||
}
|
}
|
||||||
|
|
||||||
WithIcon { icon ->
|
FallbackBookmarkFaviconImage(bookmark.url)
|
||||||
Box(
|
|
||||||
modifier = imageModifier.background(
|
|
||||||
color = FirefoxTheme.colors.layer2,
|
|
||||||
),
|
|
||||||
contentAlignment = Alignment.Center,
|
|
||||||
) {
|
|
||||||
Image(
|
|
||||||
painter = icon.painter,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier
|
|
||||||
.size(36.dp)
|
|
||||||
.clip(cardShape),
|
|
||||||
contentScale = ContentScale.Crop,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
inComposePreview -> {
|
inComposePreview -> {
|
||||||
|
@ -217,6 +205,20 @@ private fun PlaceholderBookmarkImage() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun FallbackBookmarkFaviconImage(
|
||||||
|
url: String,
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = imageModifier.background(
|
||||||
|
color = FirefoxTheme.colors.layer2,
|
||||||
|
),
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
) {
|
||||||
|
Favicon(url = url, size = 36.dp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@LightDarkPreview
|
@LightDarkPreview
|
||||||
private fun RecentBookmarksPreview() {
|
private fun RecentBookmarksPreview() {
|
||||||
|
|
|
@ -69,7 +69,7 @@ private const val THUMBNAIL_SIZE = 108
|
||||||
* @param onRemoveSyncedTab Invoked when user clicks on the "Remove" dropdown menu option.
|
* @param onRemoveSyncedTab Invoked when user clicks on the "Remove" dropdown menu option.
|
||||||
*/
|
*/
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Suppress("LongMethod", "LongParameterList")
|
@Suppress("LongMethod")
|
||||||
@Composable
|
@Composable
|
||||||
fun RecentSyncedTab(
|
fun RecentSyncedTab(
|
||||||
tab: RecentSyncedTab?,
|
tab: RecentSyncedTab?,
|
||||||
|
|
|
@ -240,6 +240,15 @@ fun RecentTabImage(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
targetSize = THUMBNAIL_SIZE.dp,
|
targetSize = THUMBNAIL_SIZE.dp,
|
||||||
contentScale = ContentScale.Crop,
|
contentScale = ContentScale.Crop,
|
||||||
|
fallback = {
|
||||||
|
TabThumbnail(
|
||||||
|
tab = tab.state,
|
||||||
|
size = LocalDensity.current.run { THUMBNAIL_SIZE.dp.toPx().toInt() },
|
||||||
|
storage = storage,
|
||||||
|
modifier = modifier,
|
||||||
|
contentScale = contentScale,
|
||||||
|
)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
else -> TabThumbnail(
|
else -> TabThumbnail(
|
||||||
|
|
|
@ -190,7 +190,6 @@ class AdapterItemDiffCallback : DiffUtil.ItemCallback<AdapterItem>() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("LongParameterList")
|
|
||||||
class SessionControlAdapter(
|
class SessionControlAdapter(
|
||||||
private val interactor: SessionControlInteractor,
|
private val interactor: SessionControlInteractor,
|
||||||
private val viewLifecycleOwner: LifecycleOwner,
|
private val viewLifecycleOwner: LifecycleOwner,
|
||||||
|
|
|
@ -239,7 +239,7 @@ data class TopSiteColors(
|
||||||
* @param onTopSiteLongClick Invoked when the user long clicks on a top site.
|
* @param onTopSiteLongClick Invoked when the user long clicks on a top site.
|
||||||
* @param onTopSitesItemBound Invoked during the composition of a top site item.
|
* @param onTopSitesItemBound Invoked during the composition of a top site item.
|
||||||
*/
|
*/
|
||||||
@Suppress("LongParameterList", "LongMethod")
|
@Suppress("LongMethod")
|
||||||
@OptIn(ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class)
|
@OptIn(ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun TopSiteItem(
|
private fun TopSiteItem(
|
||||||
|
@ -401,7 +401,6 @@ private fun TopSiteFavicon(url: String, imageUrl: String? = null) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@Suppress("LongParameterList")
|
|
||||||
private fun getMenuItems(
|
private fun getMenuItems(
|
||||||
topSite: TopSite,
|
topSite: TopSite,
|
||||||
onOpenInPrivateTabClicked: (topSite: TopSite) -> Unit,
|
onOpenInPrivateTabClicked: (topSite: TopSite) -> Unit,
|
||||||
|
|