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 {
|
||||
applicationId "com.leos"
|
||||
minSdk = 26
|
||||
minSdkVersion config.minSdkVersion
|
||||
compileSdk config.compileSdkVersion
|
||||
targetSdkVersion config.targetSdkVersion
|
||||
versionCode 1
|
||||
|
@ -63,7 +63,7 @@ android {
|
|||
// This should be the base URL used to call the AMO API.
|
||||
buildConfigField "String", "AMO_SERVER_URL", "\"https://services.addons.mozilla.org\""
|
||||
|
||||
def deepLinkSchemeValue = "leosium"
|
||||
def deepLinkSchemeValue = "fenix-dev"
|
||||
buildConfigField "String", "DEEP_LINK_SCHEME", "\"$deepLinkSchemeValue\""
|
||||
|
||||
// This allows overriding the target activity for MozillaOnline builds, which happens
|
||||
|
@ -82,6 +82,8 @@ android {
|
|||
"targetActivity": targetActivity,
|
||||
"deepLinkScheme": deepLinkSchemeValue
|
||||
]
|
||||
|
||||
buildConfigField "String[]", "SUPPORTED_LOCALE_ARRAY", getSupportedLocales()
|
||||
}
|
||||
|
||||
def releaseTemplate = {
|
||||
|
@ -109,14 +111,14 @@ android {
|
|||
debug {
|
||||
shrinkResources false
|
||||
minifyEnabled false
|
||||
applicationIdSuffix ".leosium.debug"
|
||||
applicationIdSuffix ".fenix.debug"
|
||||
resValue "bool", "IS_DEBUG", "true"
|
||||
pseudoLocalesEnabled true
|
||||
}
|
||||
nightly releaseTemplate >> {
|
||||
applicationIdSuffix ".fenix"
|
||||
buildConfigField "boolean", "USE_RELEASE_VERSIONING", "true"
|
||||
def deepLinkSchemeValue = "leosium.debug"
|
||||
def deepLinkSchemeValue = "fenix-nightly"
|
||||
buildConfigField "String", "DEEP_LINK_SCHEME", "\"$deepLinkSchemeValue\""
|
||||
manifestPlaceholders.putAll([
|
||||
"deepLinkScheme": deepLinkSchemeValue
|
||||
|
@ -124,7 +126,7 @@ android {
|
|||
}
|
||||
beta releaseTemplate >> {
|
||||
buildConfigField "boolean", "USE_RELEASE_VERSIONING", "true"
|
||||
applicationIdSuffix ".leosium.beta"
|
||||
applicationIdSuffix ".firefox_beta"
|
||||
def deepLinkSchemeValue = "fenix-beta"
|
||||
buildConfigField "String", "DEEP_LINK_SCHEME", "\"$deepLinkSchemeValue\""
|
||||
manifestPlaceholders.putAll([
|
||||
|
@ -141,7 +143,7 @@ android {
|
|||
}
|
||||
release releaseTemplate >> {
|
||||
buildConfigField "boolean", "USE_RELEASE_VERSIONING", "true"
|
||||
applicationIdSuffix ".leosium"
|
||||
applicationIdSuffix ".firefox"
|
||||
def deepLinkSchemeValue = "fenix"
|
||||
buildConfigField "String", "DEEP_LINK_SCHEME", "\"$deepLinkSchemeValue\""
|
||||
manifestPlaceholders.putAll([
|
||||
|
@ -239,7 +241,14 @@ android {
|
|||
|
||||
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 {
|
||||
buildConfigField "boolean", "LEAKCANARY", "false"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Generate Kotlin code for the Fenix Glean metrics.
|
||||
|
@ -670,13 +678,16 @@ dependencies {
|
|||
implementation ComponentsDependencies.androidx_fragment
|
||||
implementation FenixDependencies.androidx_navigation_fragment
|
||||
implementation FenixDependencies.androidx_navigation_ui
|
||||
implementation ComponentsDependencies.androidx_compose_navigation
|
||||
implementation ComponentsDependencies.androidx_recyclerview
|
||||
|
||||
implementation ComponentsDependencies.androidx_lifecycle_common
|
||||
implementation ComponentsDependencies.androidx_lifecycle_livedata
|
||||
implementation ComponentsDependencies.androidx_lifecycle_process
|
||||
implementation ComponentsDependencies.androidx_lifecycle_runtime
|
||||
|
||||
implementation ComponentsDependencies.androidx_lifecycle_viewmodel
|
||||
implementation ComponentsDependencies.androidx_lifecycle_service
|
||||
|
||||
implementation ComponentsDependencies.androidx_core
|
||||
implementation ComponentsDependencies.androidx_core_ktx
|
||||
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 {
|
||||
|
||||
// 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.
|
||||
ext.gleanExpireByVersion = 26
|
||||
|
|
2610
app/github.txt
|
@ -135,12 +135,12 @@ events:
|
|||
description: |
|
||||
A string containing the name of the item the user tapped. These items
|
||||
include:
|
||||
add_to_homescreen, add_to_top_sites, addons_manager, back, bookmark,
|
||||
bookmarks, desktop_view_off, desktop_view_on, downloads,
|
||||
find_in_page, forward, history, new_tab, open_in_app, open_in_fenix,
|
||||
quit, reader_mode_appearance, reload, remove_from_top_sites,
|
||||
add_to_homescreen, add_to_top_sites, addons_manager, back, back_long_press,
|
||||
bookmark, bookmarks, desktop_view_off, desktop_view_on, downloads,
|
||||
find_in_page, forward, forward_long_press, history, new_tab, open_in_app,
|
||||
open_in_fenix, quit, reader_mode_appearance, reload, remove_from_top_sites,
|
||||
save_to_collection, set_default_browser, settings, share, stop,
|
||||
sync_account, and print_content.
|
||||
sync_account, translate and print_content.
|
||||
type: string
|
||||
bugs:
|
||||
- https://github.com/mozilla-mobile/fenix/issues/1024
|
||||
|
@ -475,6 +475,23 @@ events:
|
|||
notification_emails:
|
||||
- android-probes@mozilla.com
|
||||
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:
|
||||
type: event
|
||||
description: |
|
||||
|
@ -489,6 +506,22 @@ events:
|
|||
notification_emails:
|
||||
- android-probes@mozilla.com
|
||||
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:
|
||||
type: event
|
||||
description: |
|
||||
|
@ -506,6 +539,22 @@ events:
|
|||
metadata:
|
||||
tags:
|
||||
- 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:
|
||||
type: event
|
||||
description: |
|
||||
|
|
|
@ -214,14 +214,6 @@ features:
|
|||
type: Boolean
|
||||
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:
|
||||
description: A feature measuring campaign growth data
|
||||
variables:
|
||||
|
@ -341,6 +333,26 @@ features:
|
|||
type: Map<String, String>
|
||||
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:
|
||||
description: A feature that provides Firefox Suggest search suggestions.
|
||||
variables:
|
||||
|
@ -361,6 +373,17 @@ features:
|
|||
- channel: nightly
|
||||
value:
|
||||
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:
|
||||
objects: {}
|
||||
|
||||
|
|
|
@ -25,8 +25,7 @@ features:
|
|||
card-type: default-browser
|
||||
title: juno_onboarding_default_browser_title_nimbus_2
|
||||
ordering: 10
|
||||
body: juno_onboarding_default_browser_description_nimbus_2
|
||||
link-text: juno_onboarding_default_browser_description_link_text
|
||||
body: juno_onboarding_default_browser_description_nimbus_3
|
||||
image-res: ic_onboarding_welcome
|
||||
primary-button-label: juno_onboarding_default_browser_positive_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.
|
||||
# This should never be defaulted.
|
||||
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:
|
||||
type: Image
|
||||
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.
|
||||
// Otherwise the code should be kept identical
|
||||
@Suppress("LongParameterList")
|
||||
private fun interceptFxaRequest(
|
||||
engineSession: EngineSession,
|
||||
uri: String,
|
||||
|
|
|
@ -146,11 +146,12 @@
|
|||
},
|
||||
"jinja2": {
|
||||
"hashes": [
|
||||
"sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852",
|
||||
"sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"
|
||||
"sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa",
|
||||
"sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"
|
||||
],
|
||||
"index": "pypi",
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==3.1.2"
|
||||
"version": "==3.1.3"
|
||||
},
|
||||
"markupsafe": {
|
||||
"hashes": [
|
||||
|
|
|
@ -6,19 +6,15 @@ package org.mozilla.fenix.extensions
|
|||
|
||||
import android.content.Context
|
||||
import mozilla.components.concept.engine.EngineSession
|
||||
import org.json.JSONObject
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.mozilla.experiments.nimbus.HardcodedNimbusFeatures
|
||||
import org.mozilla.fenix.ext.components
|
||||
import org.mozilla.fenix.gecko.GeckoProvider
|
||||
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 {
|
||||
private lateinit var context: Context
|
||||
|
@ -27,49 +23,12 @@ class ExtensionProcessTest {
|
|||
@Before
|
||||
fun setUp() {
|
||||
context = TestHelper.appContext
|
||||
policy =
|
||||
context.components.core.trackingProtectionPolicyFactory.createTrackingProtectionPolicy()
|
||||
policy = context.components.core.trackingProtectionPolicyFactory.createTrackingProtectionPolicy()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_extension_process_can_be_enabled_by_nimbus() {
|
||||
val hardcodedNimbus = HardcodedNimbusFeatures(
|
||||
context,
|
||||
"extensions-process" to JSONObject(
|
||||
"""
|
||||
{
|
||||
"enabled": true
|
||||
}
|
||||
""".trimIndent(),
|
||||
),
|
||||
)
|
||||
|
||||
hardcodedNimbus.connectWith(FxNimbus)
|
||||
|
||||
fun test_extension_process_is_enabled() {
|
||||
val runtime = GeckoProvider.createRuntimeSettings(context, policy)
|
||||
|
||||
assertTrue(FxNimbus.features.extensionsProcess.value().enabled)
|
||||
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 the test in its testBlock.
|
||||
* 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) {
|
||||
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.
|
||||
*/
|
||||
|
|
|
@ -22,6 +22,8 @@ import org.junit.Assert
|
|||
import org.mozilla.fenix.ext.components
|
||||
import org.mozilla.fenix.helpers.TestHelper.mDevice
|
||||
import org.mozilla.fenix.utils.IntentUtils
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalTime
|
||||
|
||||
object DataGenerationHelper {
|
||||
val appContext: Context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
|
@ -75,6 +77,28 @@ object DataGenerationHelper {
|
|||
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.
|
||||
*/
|
||||
|
|
|
@ -82,6 +82,11 @@ interface FeatureSettingsHelper {
|
|||
*/
|
||||
var composeTopSitesEnabled: Boolean
|
||||
|
||||
/**
|
||||
* Enable or disable translations flow.
|
||||
*/
|
||||
var isTranslationsEnabled: Boolean
|
||||
|
||||
fun applyFlagUpdates()
|
||||
|
||||
fun resetAllFeatureFlags()
|
||||
|
|
|
@ -37,6 +37,7 @@ class FeatureSettingsHelperDelegate() : FeatureSettingsHelper {
|
|||
etpPolicy = getETPPolicy(settings),
|
||||
tabsTrayRewriteEnabled = settings.enableTabsTrayToCompose,
|
||||
composeTopSitesEnabled = settings.enableComposeTopSites,
|
||||
translationsEnabled = settings.enableTranslations,
|
||||
)
|
||||
|
||||
/**
|
||||
|
@ -66,6 +67,7 @@ class FeatureSettingsHelperDelegate() : FeatureSettingsHelper {
|
|||
override var etpPolicy: ETPPolicy by updatedFeatureFlags::etpPolicy
|
||||
override var tabsTrayRewriteEnabled: Boolean by updatedFeatureFlags::tabsTrayRewriteEnabled
|
||||
override var composeTopSitesEnabled: Boolean by updatedFeatureFlags::composeTopSitesEnabled
|
||||
override var isTranslationsEnabled: Boolean by updatedFeatureFlags::translationsEnabled
|
||||
|
||||
override fun applyFlagUpdates() {
|
||||
applyFeatureFlags(updatedFeatureFlags)
|
||||
|
@ -91,6 +93,7 @@ class FeatureSettingsHelperDelegate() : FeatureSettingsHelper {
|
|||
settings.shouldShowOpenInAppBanner = featureFlags.isOpenInAppBannerEnabled
|
||||
settings.enableTabsTrayToCompose = featureFlags.tabsTrayRewriteEnabled
|
||||
settings.enableComposeTopSites = featureFlags.composeTopSitesEnabled
|
||||
settings.enableTranslations = featureFlags.translationsEnabled
|
||||
setETPPolicy(featureFlags.etpPolicy)
|
||||
}
|
||||
}
|
||||
|
@ -110,6 +113,7 @@ private data class FeatureFlags(
|
|||
var etpPolicy: ETPPolicy,
|
||||
var tabsTrayRewriteEnabled: Boolean,
|
||||
var composeTopSitesEnabled: Boolean,
|
||||
var translationsEnabled: Boolean,
|
||||
)
|
||||
|
||||
internal fun getETPPolicy(settings: Settings): ETPPolicy {
|
||||
|
|
|
@ -165,6 +165,7 @@ class HomeActivityIntentTestRule internal constructor(
|
|||
etpPolicy: ETPPolicy = getETPPolicy(settings),
|
||||
tabsTrayRewriteEnabled: Boolean = false,
|
||||
composeTopSitesEnabled: Boolean = false,
|
||||
translationsEnabled: Boolean = false,
|
||||
) : this(initialTouchMode, launchActivity, skipOnboarding) {
|
||||
this.isHomeOnboardingDialogEnabled = isHomeOnboardingDialogEnabled
|
||||
this.isPocketEnabled = isPocketEnabled
|
||||
|
@ -179,6 +180,7 @@ class HomeActivityIntentTestRule internal constructor(
|
|||
this.etpPolicy = etpPolicy
|
||||
this.tabsTrayRewriteEnabled = tabsTrayRewriteEnabled
|
||||
this.composeTopSitesEnabled = composeTopSitesEnabled
|
||||
this.isTranslationsEnabled = translationsEnabled
|
||||
}
|
||||
|
||||
private val longTapUserPreference = getLongPressTimeout()
|
||||
|
@ -260,6 +262,7 @@ class HomeActivityIntentTestRule internal constructor(
|
|||
skipOnboarding: Boolean = false,
|
||||
tabsTrayRewriteEnabled: Boolean = false,
|
||||
composeTopSitesEnabled: Boolean = false,
|
||||
translationsEnabled: Boolean = false,
|
||||
) = HomeActivityIntentTestRule(
|
||||
initialTouchMode = initialTouchMode,
|
||||
launchActivity = launchActivity,
|
||||
|
@ -271,6 +274,7 @@ class HomeActivityIntentTestRule internal constructor(
|
|||
isWallpaperOnboardingEnabled = false,
|
||||
isOpenInAppBannerEnabled = false,
|
||||
composeTopSitesEnabled = composeTopSitesEnabled,
|
||||
translationsEnabled = translationsEnabled,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
package org.mozilla.fenix.helpers
|
||||
|
||||
import android.util.Log
|
||||
import androidx.test.espresso.IdlingResourceTimeoutException
|
||||
import androidx.test.espresso.NoMatchingViewException
|
||||
import androidx.test.uiautomator.UiObjectNotFoundException
|
||||
|
@ -13,7 +14,9 @@ import org.junit.rules.TestRule
|
|||
import org.junit.runner.Description
|
||||
import org.junit.runners.model.Statement
|
||||
import org.mozilla.fenix.components.PermissionStorage
|
||||
import org.mozilla.fenix.ext.settings
|
||||
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.TestHelper.appContext
|
||||
|
||||
|
@ -32,68 +35,83 @@ class RetryTestRule(private val retryCount: Int = 5) : TestRule {
|
|||
return statement {
|
||||
for (i in 1..retryCount) {
|
||||
try {
|
||||
Log.i(TAG, "RetryTestRule: Started try #$i.")
|
||||
base.evaluate()
|
||||
break
|
||||
} catch (t: AssertionError) {
|
||||
setNetworkEnabled(true)
|
||||
unregisterAllIdlingResources()
|
||||
runBlocking {
|
||||
appContext.settings().alwaysOpenTheHomepageWhenOpeningTheApp = true
|
||||
permissionStorage.deleteAllSitePermissions()
|
||||
}
|
||||
if (i == retryCount) {
|
||||
Log.i(TAG, "RetryTestRule: Max numbers of retries reached.")
|
||||
throw t
|
||||
}
|
||||
} catch (t: AssertionFailedError) {
|
||||
unregisterAllIdlingResources()
|
||||
runBlocking {
|
||||
appContext.settings().alwaysOpenTheHomepageWhenOpeningTheApp = true
|
||||
permissionStorage.deleteAllSitePermissions()
|
||||
}
|
||||
if (i == retryCount) {
|
||||
Log.i(TAG, "RetryTestRule: Max numbers of retries reached.")
|
||||
throw t
|
||||
}
|
||||
} catch (t: UiObjectNotFoundException) {
|
||||
setNetworkEnabled(true)
|
||||
unregisterAllIdlingResources()
|
||||
runBlocking {
|
||||
appContext.settings().alwaysOpenTheHomepageWhenOpeningTheApp = true
|
||||
permissionStorage.deleteAllSitePermissions()
|
||||
}
|
||||
if (i == retryCount) {
|
||||
Log.i(TAG, "RetryTestRule: Max numbers of retries reached.")
|
||||
throw t
|
||||
}
|
||||
} catch (t: NoMatchingViewException) {
|
||||
setNetworkEnabled(true)
|
||||
unregisterAllIdlingResources()
|
||||
runBlocking {
|
||||
appContext.settings().alwaysOpenTheHomepageWhenOpeningTheApp = true
|
||||
permissionStorage.deleteAllSitePermissions()
|
||||
}
|
||||
if (i == retryCount) {
|
||||
Log.i(TAG, "RetryTestRule: Max numbers of retries reached.")
|
||||
throw t
|
||||
}
|
||||
} catch (t: IdlingResourceTimeoutException) {
|
||||
setNetworkEnabled(true)
|
||||
unregisterAllIdlingResources()
|
||||
runBlocking {
|
||||
appContext.settings().alwaysOpenTheHomepageWhenOpeningTheApp = true
|
||||
permissionStorage.deleteAllSitePermissions()
|
||||
}
|
||||
if (i == retryCount) {
|
||||
Log.i(TAG, "RetryTestRule: Max numbers of retries reached.")
|
||||
throw t
|
||||
}
|
||||
} catch (t: RuntimeException) {
|
||||
setNetworkEnabled(true)
|
||||
unregisterAllIdlingResources()
|
||||
runBlocking {
|
||||
appContext.settings().alwaysOpenTheHomepageWhenOpeningTheApp = true
|
||||
permissionStorage.deleteAllSitePermissions()
|
||||
}
|
||||
if (i == retryCount) {
|
||||
Log.i(TAG, "RetryTestRule: Max numbers of retries reached.")
|
||||
throw t
|
||||
}
|
||||
} catch (t: NullPointerException) {
|
||||
setNetworkEnabled(true)
|
||||
unregisterAllIdlingResources()
|
||||
runBlocking {
|
||||
appContext.settings().alwaysOpenTheHomepageWhenOpeningTheApp = true
|
||||
permissionStorage.deleteAllSitePermissions()
|
||||
}
|
||||
if (i == retryCount) {
|
||||
Log.i(TAG, "RetryTestRule: Max numbers of retries reached.")
|
||||
throw t
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,10 +45,12 @@ class OnboardingMapperTest {
|
|||
|
||||
@Test
|
||||
fun showNotificationTrue_showAddWidgetFalse_pagesToDisplay_returnsSortedListOfAllConvertedPages_withoutAddWidgetPage() {
|
||||
val expected = listOf(defaultBrowserPageUiData, syncPageUiData, notificationPageUiData)
|
||||
val expected = listOf(defaultBrowserPageUiDataWithPrivacyCaption, syncPageUiData, notificationPageUiData)
|
||||
assertEquals(
|
||||
expected,
|
||||
unsortedAllKnownCardData.toPageUiData(
|
||||
privacyCaption = privacyCaption,
|
||||
showDefaultBrowserPage = true,
|
||||
showNotificationPage = true,
|
||||
showAddWidgetPage = false,
|
||||
jexlConditions = jexlConditions,
|
||||
|
@ -59,10 +61,12 @@ class OnboardingMapperTest {
|
|||
|
||||
@Test
|
||||
fun showNotificationFalse_showAddWidgetFalse_pagesToDisplay_returnsSortedListOfConvertedPages_withoutNotificationPage_and_addWidgetPage() {
|
||||
val expected = listOf(defaultBrowserPageUiData, syncPageUiData)
|
||||
val expected = listOf(defaultBrowserPageUiDataWithPrivacyCaption, syncPageUiData)
|
||||
assertEquals(
|
||||
expected,
|
||||
unsortedAllKnownCardData.toPageUiData(
|
||||
privacyCaption = privacyCaption,
|
||||
showDefaultBrowserPage = true,
|
||||
showNotificationPage = false,
|
||||
showAddWidgetPage = false,
|
||||
jexlConditions = jexlConditions,
|
||||
|
@ -72,11 +76,76 @@ class OnboardingMapperTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun showNotificationFalse_showAddWidgetTrue_pagesToDisplay_returnsSortedListOfAllConvertedPages_withoutNotificationPage() {
|
||||
val expected = listOf(defaultBrowserPageUiData, addSearchWidgetPageUiData, syncPageUiData)
|
||||
fun pagesToDisplay_returnsSortedListOfConvertedPages_withPrivacyCaption_alwaysOnFirstPage() {
|
||||
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(
|
||||
expected,
|
||||
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,
|
||||
showAddWidgetPage = true,
|
||||
jexlConditions = jexlConditions,
|
||||
|
@ -88,7 +157,7 @@ class OnboardingMapperTest {
|
|||
@Test
|
||||
fun showNotificationTrue_and_showAddWidgetTrue_pagesToDisplay_returnsSortedListOfConvertedPages() {
|
||||
val expected = listOf(
|
||||
defaultBrowserPageUiData,
|
||||
defaultBrowserPageUiDataWithPrivacyCaption,
|
||||
addSearchWidgetPageUiData,
|
||||
syncPageUiData,
|
||||
notificationPageUiData,
|
||||
|
@ -96,6 +165,8 @@ class OnboardingMapperTest {
|
|||
assertEquals(
|
||||
expected,
|
||||
unsortedAllKnownCardData.toPageUiData(
|
||||
privacyCaption = privacyCaption,
|
||||
showDefaultBrowserPage = true,
|
||||
showNotificationPage = true,
|
||||
showAddWidgetPage = true,
|
||||
jexlConditions = jexlConditions,
|
||||
|
@ -107,11 +178,13 @@ class OnboardingMapperTest {
|
|||
@Test
|
||||
fun cardConditionsMatchJexlConditions_shouldDisplayCard_returnsConvertedPage() {
|
||||
val jexlConditions = mapOf("ALWAYS" to "true", "NEVER" to "false")
|
||||
val expected = listOf(defaultBrowserPageUiData)
|
||||
val expected = listOf(defaultBrowserPageUiDataWithPrivacyCaption)
|
||||
|
||||
assertEquals(
|
||||
expected,
|
||||
listOf(defaultBrowserCardData).toPageUiData(
|
||||
privacyCaption = privacyCaption,
|
||||
showDefaultBrowserPage = true,
|
||||
showNotificationPage = false,
|
||||
showAddWidgetPage = false,
|
||||
jexlConditions = jexlConditions,
|
||||
|
@ -128,6 +201,8 @@ class OnboardingMapperTest {
|
|||
assertEquals(
|
||||
expected,
|
||||
listOf(addSearchWidgetCardDataNoConditions).toPageUiData(
|
||||
privacyCaption = privacyCaption,
|
||||
showDefaultBrowserPage = true,
|
||||
showNotificationPage = false,
|
||||
showAddWidgetPage = false,
|
||||
jexlConditions = jexlConditions,
|
||||
|
@ -144,6 +219,8 @@ class OnboardingMapperTest {
|
|||
assertEquals(
|
||||
expected,
|
||||
listOf(defaultBrowserCardData).toPageUiData(
|
||||
privacyCaption = privacyCaption,
|
||||
showDefaultBrowserPage = true,
|
||||
showNotificationPage = false,
|
||||
showAddWidgetPage = false,
|
||||
jexlConditions = jexlConditions,
|
||||
|
@ -155,11 +232,13 @@ class OnboardingMapperTest {
|
|||
@Test
|
||||
fun prerequisitesMatchJexlConditions_shouldDisplayCard_returnsConvertedPage() {
|
||||
val jexlConditions = mapOf("ALWAYS" to "true")
|
||||
val expected = listOf(defaultBrowserPageUiData)
|
||||
val expected = listOf(defaultBrowserPageUiDataWithPrivacyCaption)
|
||||
|
||||
assertEquals(
|
||||
expected,
|
||||
listOf(defaultBrowserCardData).toPageUiData(
|
||||
privacyCaption = privacyCaption,
|
||||
showDefaultBrowserPage = true,
|
||||
showNotificationPage = false,
|
||||
showAddWidgetPage = false,
|
||||
jexlConditions = jexlConditions,
|
||||
|
@ -176,6 +255,8 @@ class OnboardingMapperTest {
|
|||
assertEquals(
|
||||
expected,
|
||||
listOf(defaultBrowserCardData).toPageUiData(
|
||||
privacyCaption = privacyCaption,
|
||||
showDefaultBrowserPage = true,
|
||||
showNotificationPage = false,
|
||||
showAddWidgetPage = false,
|
||||
jexlConditions = jexlConditions,
|
||||
|
@ -192,6 +273,8 @@ class OnboardingMapperTest {
|
|||
assertEquals(
|
||||
expected,
|
||||
listOf(addSearchWidgetCardDataNoConditions).toPageUiData(
|
||||
privacyCaption = privacyCaption,
|
||||
showDefaultBrowserPage = true,
|
||||
showNotificationPage = false,
|
||||
showAddWidgetPage = false,
|
||||
jexlConditions = jexlConditions,
|
||||
|
@ -203,11 +286,13 @@ class OnboardingMapperTest {
|
|||
@Test
|
||||
fun noDisqualifiers_shouldDisplayCard_returnsConvertedPage() {
|
||||
val jexlConditions = mapOf("ALWAYS" to "true", "NEVER" to "false")
|
||||
val expected = listOf(defaultBrowserPageUiData)
|
||||
val expected = listOf(defaultBrowserPageUiDataWithPrivacyCaption)
|
||||
|
||||
assertEquals(
|
||||
expected,
|
||||
listOf(defaultBrowserCardDataNoDisqualifiers).toPageUiData(
|
||||
privacyCaption = privacyCaption,
|
||||
showDefaultBrowserPage = true,
|
||||
showNotificationPage = false,
|
||||
showAddWidgetPage = false,
|
||||
jexlConditions = jexlConditions,
|
||||
|
@ -219,11 +304,13 @@ class OnboardingMapperTest {
|
|||
@Test
|
||||
fun disqualifiersMatchJexlConditions_shouldDisplayCard_returnsConvertedPage() {
|
||||
val jexlConditions = mapOf("NEVER" to "false")
|
||||
val expected = listOf(syncPageUiData)
|
||||
val expected = listOf(syncPageUiDataWithPrivacyCaption)
|
||||
|
||||
assertEquals(
|
||||
expected,
|
||||
listOf(syncCardData).toPageUiData(
|
||||
privacyCaption = privacyCaption,
|
||||
showDefaultBrowserPage = true,
|
||||
showNotificationPage = false,
|
||||
showAddWidgetPage = false,
|
||||
jexlConditions = jexlConditions,
|
||||
|
@ -240,6 +327,8 @@ class OnboardingMapperTest {
|
|||
assertEquals(
|
||||
expected,
|
||||
listOf(notificationCardData).toPageUiData(
|
||||
privacyCaption = privacyCaption,
|
||||
showDefaultBrowserPage = true,
|
||||
showNotificationPage = false,
|
||||
showAddWidgetPage = false,
|
||||
jexlConditions = jexlConditions,
|
||||
|
@ -251,11 +340,13 @@ class OnboardingMapperTest {
|
|||
@Test
|
||||
fun noPrerequisites_shouldDisplayCard_returnsConvertedPage() {
|
||||
val jexlConditions = mapOf("ALWAYS" to "true", "NEVER" to "false")
|
||||
val expected = listOf(syncPageUiData)
|
||||
val expected = listOf(syncPageUiDataWithPrivacyCaption)
|
||||
|
||||
assertEquals(
|
||||
expected,
|
||||
listOf(syncCardData).toPageUiData(
|
||||
privacyCaption = privacyCaption,
|
||||
showDefaultBrowserPage = true,
|
||||
showNotificationPage = false,
|
||||
showAddWidgetPage = false,
|
||||
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,
|
||||
imageRes = R.drawable.ic_onboarding_welcome,
|
||||
title = "default browser title",
|
||||
description = "default browser body with link text",
|
||||
linkText = "link text",
|
||||
description = "default browser body",
|
||||
primaryButtonLabel = "default browser primary button text",
|
||||
secondaryButtonLabel = "default browser secondary button text",
|
||||
privacyCaption = privacyCaption,
|
||||
)
|
||||
private val addSearchWidgetPageUiData = OnboardingPageUiData(
|
||||
type = OnboardingPageUiData.Type.ADD_SEARCH_WIDGET,
|
||||
imageRes = R.drawable.ic_onboarding_search_widget,
|
||||
title = "add search widget title",
|
||||
description = "add search widget body with link text",
|
||||
linkText = "link text",
|
||||
description = "add search widget body",
|
||||
primaryButtonLabel = "add search widget primary 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(
|
||||
type = OnboardingPageUiData.Type.SYNC_SIGN_IN,
|
||||
|
@ -290,6 +391,16 @@ private val syncPageUiData = OnboardingPageUiData(
|
|||
description = "sync body",
|
||||
primaryButtonLabel = "sync primary 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(
|
||||
type = OnboardingPageUiData.Type.NOTIFICATION_PERMISSION,
|
||||
|
@ -298,14 +409,14 @@ private val notificationPageUiData = OnboardingPageUiData(
|
|||
description = "notification body",
|
||||
primaryButtonLabel = "notification primary button text",
|
||||
secondaryButtonLabel = "notification secondary button text",
|
||||
privacyCaption = null,
|
||||
)
|
||||
|
||||
private val defaultBrowserCardData = OnboardingCardData(
|
||||
cardType = OnboardingCardType.DEFAULT_BROWSER,
|
||||
imageRes = R.drawable.ic_onboarding_welcome,
|
||||
title = StringHolder(null, "default browser title"),
|
||||
body = StringHolder(null, "default browser body with link text"),
|
||||
linkText = StringHolder(null, "link text"),
|
||||
body = StringHolder(null, "default browser body"),
|
||||
primaryButtonLabel = StringHolder(null, "default browser primary button text"),
|
||||
secondaryButtonLabel = StringHolder(null, "default browser secondary button text"),
|
||||
ordering = 10,
|
||||
|
@ -317,8 +428,7 @@ private val defaultBrowserCardDataNoDisqualifiers = OnboardingCardData(
|
|||
cardType = OnboardingCardType.DEFAULT_BROWSER,
|
||||
imageRes = R.drawable.ic_onboarding_welcome,
|
||||
title = StringHolder(null, "default browser title"),
|
||||
body = StringHolder(null, "default browser body with link text"),
|
||||
linkText = StringHolder(null, "link text"),
|
||||
body = StringHolder(null, "default browser body"),
|
||||
primaryButtonLabel = StringHolder(null, "default browser primary button text"),
|
||||
secondaryButtonLabel = StringHolder(null, "default browser secondary button text"),
|
||||
ordering = 10,
|
||||
|
@ -330,8 +440,7 @@ private val addSearchWidgetCardDataNoConditions = OnboardingCardData(
|
|||
cardType = OnboardingCardType.ADD_SEARCH_WIDGET,
|
||||
imageRes = R.drawable.ic_onboarding_search_widget,
|
||||
title = StringHolder(null, "add search widget title"),
|
||||
body = StringHolder(null, "add search widget body with link text"),
|
||||
linkText = StringHolder(null, "link text"),
|
||||
body = StringHolder(null, "add search widget body"),
|
||||
primaryButtonLabel = StringHolder(null, "add search widget primary button text"),
|
||||
secondaryButtonLabel = StringHolder(null, "add search widget secondary button text"),
|
||||
ordering = 15,
|
||||
|
@ -343,8 +452,7 @@ private val addSearchWidgetCardData = OnboardingCardData(
|
|||
cardType = OnboardingCardType.ADD_SEARCH_WIDGET,
|
||||
imageRes = R.drawable.ic_onboarding_search_widget,
|
||||
title = StringHolder(null, "add search widget title"),
|
||||
body = StringHolder(null, "add search widget body with link text"),
|
||||
linkText = StringHolder(null, "link text"),
|
||||
body = StringHolder(null, "add search widget body"),
|
||||
primaryButtonLabel = StringHolder(null, "add search widget primary button text"),
|
||||
secondaryButtonLabel = StringHolder(null, "add search widget secondary button text"),
|
||||
ordering = 15,
|
||||
|
|
|
@ -7,7 +7,6 @@ package org.mozilla.fenix.ui
|
|||
import okhttp3.mockwebserver.MockWebServer
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Ignore
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.mozilla.fenix.customannotations.SmokeTest
|
||||
|
@ -265,7 +264,6 @@ class AddressAutofillTest {
|
|||
}
|
||||
|
||||
// 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
|
||||
fun verifyMultipleAddressesSelectionTest() {
|
||||
val addressFormPage =
|
||||
|
|
|
@ -14,6 +14,7 @@ import org.junit.Rule
|
|||
import org.junit.Test
|
||||
import org.mozilla.fenix.customannotations.SmokeTest
|
||||
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.HomeActivityTestRule
|
||||
import org.mozilla.fenix.helpers.TestAssetHelper
|
||||
|
@ -54,6 +55,7 @@ class ComposeNavigationToolbarTest {
|
|||
@After
|
||||
fun tearDown() {
|
||||
mockWebServer.shutdown()
|
||||
resetSystemLocaleToEnUS()
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
||||
import org.junit.Ignore
|
||||
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
|
||||
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.TestHelper
|
||||
import org.mozilla.fenix.ui.robots.navigationToolbar
|
||||
|
@ -20,6 +22,7 @@ import org.mozilla.fenix.ui.robots.navigationToolbar
|
|||
*/
|
||||
|
||||
class FirefoxSuggestTest {
|
||||
|
||||
@get:Rule
|
||||
val activityTestRule = AndroidComposeTestRule(
|
||||
HomeActivityTestRule(
|
||||
|
@ -33,91 +36,156 @@ class FirefoxSuggestTest {
|
|||
),
|
||||
) { 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
|
||||
@Ignore("Failing, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1874831")
|
||||
@SmokeTest
|
||||
@Test
|
||||
fun verifyFirefoxSuggestSponsoredSearchResultsTest() {
|
||||
AppAndSystemHelper.runWithCondition(TestHelper.appContext.settings().enableFxSuggest) {
|
||||
runWithCondition(TestHelper.appContext.settings().enableFxSuggest) {
|
||||
navigationToolbar {
|
||||
}.clickUrlbar {
|
||||
typeSearch(searchTerm = "Amazon")
|
||||
typeSearch(searchTerm = sponsoredKeyWord)
|
||||
verifySearchEngineSuggestionResults(
|
||||
rule = activityTestRule,
|
||||
searchSuggestions = arrayOf(
|
||||
"Firefox Suggest",
|
||||
"Amazon.com - Official Site",
|
||||
sponsoredKeyWords.getValue(sponsoredKeyWord)[0],
|
||||
"Sponsored",
|
||||
),
|
||||
searchTerm = "Amazon",
|
||||
searchTerm = sponsoredKeyWord,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
fun verifyFirefoxSuggestSponsoredSearchResultsWithPartialKeywordTest() {
|
||||
AppAndSystemHelper.runWithCondition(TestHelper.appContext.settings().enableFxSuggest) {
|
||||
runWithCondition(TestHelper.appContext.settings().enableFxSuggest) {
|
||||
navigationToolbar {
|
||||
}.clickUrlbar {
|
||||
typeSearch(searchTerm = "Amaz")
|
||||
typeSearch(searchTerm = sponsoredKeyWord.dropLast(1))
|
||||
verifySearchEngineSuggestionResults(
|
||||
rule = activityTestRule,
|
||||
searchSuggestions = arrayOf(
|
||||
"Firefox Suggest",
|
||||
"Amazon.com - Official Site",
|
||||
sponsoredKeyWords.getValue(sponsoredKeyWord)[0],
|
||||
"Sponsored",
|
||||
),
|
||||
searchTerm = "Amaz",
|
||||
searchTerm = sponsoredKeyWord.dropLast(1),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
fun openFirefoxSuggestSponsoredSearchResultsTest() {
|
||||
AppAndSystemHelper.runWithCondition(TestHelper.appContext.settings().enableFxSuggest) {
|
||||
runWithCondition(TestHelper.appContext.settings().enableFxSuggest) {
|
||||
navigationToolbar {
|
||||
}.clickUrlbar {
|
||||
typeSearch(searchTerm = "Amazon")
|
||||
typeSearch(searchTerm = sponsoredKeyWord)
|
||||
verifySearchEngineSuggestionResults(
|
||||
rule = activityTestRule,
|
||||
searchSuggestions = arrayOf(
|
||||
"Firefox Suggest",
|
||||
"Amazon.com - Official Site",
|
||||
sponsoredKeyWords.getValue(sponsoredKeyWord)[0],
|
||||
"Sponsored",
|
||||
),
|
||||
searchTerm = "Amazon",
|
||||
)
|
||||
}.clickSearchSuggestion("Amazon.com - Official Site") {
|
||||
waitForPageToLoad()
|
||||
verifyUrl(
|
||||
"amazon.com/?tag=admarketus-20&ref=pd_sl_924ab4435c5a5c23aa2804307ee0669ab36f88caee841ce51d1f2ecb&mfadid=adm",
|
||||
searchTerm = sponsoredKeyWord,
|
||||
)
|
||||
}.clickSearchSuggestion(sponsoredKeyWords.getValue(sponsoredKeyWord)[0]) {
|
||||
verifyUrl(sponsoredKeyWords.getValue(sponsoredKeyWord)[1])
|
||||
verifyTabCounter("1")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
fun verifyFirefoxSuggestSponsoredSearchResultsWithEditedKeywordTest() {
|
||||
AppAndSystemHelper.runWithCondition(TestHelper.appContext.settings().enableFxSuggest) {
|
||||
runWithCondition(TestHelper.appContext.settings().enableFxSuggest) {
|
||||
navigationToolbar {
|
||||
}.clickUrlbar {
|
||||
typeSearch(searchTerm = "Amazon")
|
||||
deleteSearchKeywordCharacters(numberOfDeletionSteps = 3)
|
||||
typeSearch(searchTerm = sponsoredKeyWord)
|
||||
deleteSearchKeywordCharacters(numberOfDeletionSteps = 1)
|
||||
verifySearchEngineSuggestionResults(
|
||||
rule = activityTestRule,
|
||||
searchSuggestions = arrayOf(
|
||||
"Firefox Suggest",
|
||||
"Amazon.com - Official Site",
|
||||
sponsoredKeyWords.getValue(sponsoredKeyWord)[0],
|
||||
"Sponsored",
|
||||
),
|
||||
searchTerm = "Amazon",
|
||||
searchTerm = sponsoredKeyWord,
|
||||
shouldEditKeyword = true,
|
||||
numberOfDeletionSteps = 3,
|
||||
numberOfDeletionSteps = 1,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -127,17 +195,17 @@ class FirefoxSuggestTest {
|
|||
@SmokeTest
|
||||
@Test
|
||||
fun verifyFirefoxSuggestNonSponsoredSearchResultsTest() {
|
||||
AppAndSystemHelper.runWithCondition(TestHelper.appContext.settings().enableFxSuggest) {
|
||||
runWithCondition(TestHelper.appContext.settings().enableFxSuggest) {
|
||||
navigationToolbar {
|
||||
}.clickUrlbar {
|
||||
typeSearch(searchTerm = "Marvel")
|
||||
typeSearch(searchTerm = nonSponsoredKeyWord)
|
||||
verifySearchEngineSuggestionResults(
|
||||
rule = activityTestRule,
|
||||
searchSuggestions = arrayOf(
|
||||
"Firefox Suggest",
|
||||
"Wikipedia - Marvel Cinematic Universe",
|
||||
nonSponsoredKeyWords.getValue(nonSponsoredKeyWord)[0],
|
||||
),
|
||||
searchTerm = "Marvel",
|
||||
searchTerm = nonSponsoredKeyWord,
|
||||
)
|
||||
verifySuggestionsAreNotDisplayed(
|
||||
rule = activityTestRule,
|
||||
|
@ -152,17 +220,17 @@ class FirefoxSuggestTest {
|
|||
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/2348375
|
||||
@Test
|
||||
fun verifyFirefoxSuggestNonSponsoredSearchResultsWithPartialKeywordTest() {
|
||||
AppAndSystemHelper.runWithCondition(TestHelper.appContext.settings().enableFxSuggest) {
|
||||
runWithCondition(TestHelper.appContext.settings().enableFxSuggest) {
|
||||
navigationToolbar {
|
||||
}.clickUrlbar {
|
||||
typeSearch(searchTerm = "Marv")
|
||||
typeSearch(searchTerm = nonSponsoredKeyWord.dropLast(1))
|
||||
verifySearchEngineSuggestionResults(
|
||||
rule = activityTestRule,
|
||||
searchSuggestions = arrayOf(
|
||||
"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
|
||||
@Test
|
||||
fun openFirefoxSuggestNonSponsoredSearchResultsTest() {
|
||||
AppAndSystemHelper.runWithCondition(TestHelper.appContext.settings().enableFxSuggest) {
|
||||
runWithCondition(TestHelper.appContext.settings().enableFxSuggest) {
|
||||
navigationToolbar {
|
||||
}.clickUrlbar {
|
||||
typeSearch(searchTerm = "Marvel")
|
||||
typeSearch(searchTerm = nonSponsoredKeyWord)
|
||||
verifySearchEngineSuggestionResults(
|
||||
rule = activityTestRule,
|
||||
searchSuggestions = arrayOf(
|
||||
"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()
|
||||
verifyUrl(
|
||||
"wikipedia.org/wiki/Marvel_Cinematic_Universe",
|
||||
)
|
||||
verifyUrl(nonSponsoredKeyWords.getValue(nonSponsoredKeyWord)[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -607,14 +607,12 @@ class LoginsTest {
|
|||
revealPassword()
|
||||
verifyPasswordSaved("firefox")
|
||||
}.goBackToSavedLogins {
|
||||
clickSearchLoginButton()
|
||||
searchLogin("android")
|
||||
viewSavedLoginDetails(originWebsite)
|
||||
verifyLoginItemUsername("android")
|
||||
revealPassword()
|
||||
verifyPasswordSaved("firefox")
|
||||
}.goBackToSavedLogins {
|
||||
clickSearchLoginButton()
|
||||
searchLogin("AnDrOiD")
|
||||
viewSavedLoginDetails(originWebsite)
|
||||
verifyLoginItemUsername("android")
|
||||
|
@ -654,14 +652,12 @@ class LoginsTest {
|
|||
revealPassword()
|
||||
verifyPasswordSaved("firefox")
|
||||
}.goBackToSavedLogins {
|
||||
clickSearchLoginButton()
|
||||
searchLogin("mozilla")
|
||||
viewSavedLoginDetails(originWebsite)
|
||||
verifyLoginItemUsername("android")
|
||||
revealPassword()
|
||||
verifyPasswordSaved("firefox")
|
||||
}.goBackToSavedLogins {
|
||||
clickSearchLoginButton()
|
||||
searchLogin("MoZiLlA")
|
||||
viewSavedLoginDetails(originWebsite)
|
||||
verifyLoginItemUsername("android")
|
||||
|
|
|
@ -37,7 +37,8 @@ class MainMenuTest {
|
|||
private lateinit var mockWebServer: MockWebServer
|
||||
|
||||
@get:Rule
|
||||
val activityTestRule = HomeActivityIntentTestRule.withDefaultSettingsOverrides()
|
||||
val activityTestRule =
|
||||
HomeActivityIntentTestRule.withDefaultSettingsOverrides(translationsEnabled = true)
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
|
|
|
@ -14,6 +14,7 @@ import org.junit.Rule
|
|||
import org.junit.Test
|
||||
import org.mozilla.fenix.customannotations.SmokeTest
|
||||
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
|
||||
import org.mozilla.fenix.helpers.AppAndSystemHelper
|
||||
import org.mozilla.fenix.helpers.AppAndSystemHelper.runWithSystemLocaleChanged
|
||||
import org.mozilla.fenix.helpers.HomeActivityTestRule
|
||||
import org.mozilla.fenix.helpers.TestAssetHelper
|
||||
|
@ -50,6 +51,7 @@ class NavigationToolbarTest {
|
|||
@After
|
||||
fun tearDown() {
|
||||
mockWebServer.shutdown()
|
||||
AppAndSystemHelper.resetSystemLocaleToEnUS()
|
||||
}
|
||||
|
||||
// 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.ext.components
|
||||
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.denyPermission
|
||||
import org.mozilla.fenix.helpers.AppAndSystemHelper.grantSystemPermission
|
||||
|
@ -92,6 +93,7 @@ class SearchTest {
|
|||
@After
|
||||
fun tearDown() {
|
||||
searchMockServer.shutdown()
|
||||
AppAndSystemHelper.resetSystemLocaleToEnUS()
|
||||
}
|
||||
|
||||
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/2154189
|
||||
|
|
|
@ -327,7 +327,7 @@ class SettingsAdvancedTest {
|
|||
}
|
||||
|
||||
navigationToolbar {
|
||||
}.enterURLAndEnterToBrowser(youTubePage) {
|
||||
}.enterURLAndEnterToBrowser("https://m.youtube.com/".toUri()) {
|
||||
waitForPageToLoad()
|
||||
verifyOpenLinksInAppsCFRExists(true)
|
||||
}.clickOpenLinksInAppsGoToSettingsCFRButton {
|
||||
|
|
|
@ -14,6 +14,7 @@ import org.mozilla.fenix.FenixApplication
|
|||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.customannotations.SmokeTest
|
||||
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.runWithSystemLocaleChanged
|
||||
import org.mozilla.fenix.helpers.DataGenerationHelper.getStringResource
|
||||
|
@ -51,6 +52,7 @@ class SettingsGeneralTest {
|
|||
@After
|
||||
fun tearDown() {
|
||||
mockWebServer.shutdown()
|
||||
AppAndSystemHelper.resetSystemLocaleToEnUS()
|
||||
}
|
||||
|
||||
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/2092697
|
||||
|
|
|
@ -5,14 +5,12 @@
|
|||
package org.mozilla.fenix.ui
|
||||
|
||||
import androidx.core.net.toUri
|
||||
import androidx.test.espresso.Espresso.pressBack
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.mozilla.fenix.customannotations.SmokeTest
|
||||
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
|
||||
import org.mozilla.fenix.helpers.MatcherHelper.itemContainingText
|
||||
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.homeScreen
|
||||
import org.mozilla.fenix.ui.robots.navigationToolbar
|
||||
|
@ -182,13 +180,7 @@ class SettingsHTTPSOnlyModeTest {
|
|||
waitForPageToLoad()
|
||||
}.openNavigationToolbar {
|
||||
verifyUrl(httpsPageUrl)
|
||||
pressBack()
|
||||
}
|
||||
browserScreen {
|
||||
}.openTabDrawer {
|
||||
closeTab()
|
||||
}
|
||||
homeScreen {
|
||||
}.goBackToBrowserScreen {
|
||||
}.openThreeDotMenu {
|
||||
}.openSettings {
|
||||
}.openHttpsOnlyModeMenu {
|
||||
|
@ -203,7 +195,6 @@ class SettingsHTTPSOnlyModeTest {
|
|||
waitForPageToLoad()
|
||||
}.openNavigationToolbar {
|
||||
verifyUrl(httpPageUrl)
|
||||
pressBack()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import org.junit.Rule
|
|||
import org.junit.Test
|
||||
import org.mozilla.fenix.customannotations.SmokeTest
|
||||
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.setSystemLocale
|
||||
import org.mozilla.fenix.helpers.DataGenerationHelper.setTextToClipBoard
|
||||
|
@ -40,7 +41,6 @@ class SettingsSearchTest {
|
|||
listOf(
|
||||
"LeOSearch",
|
||||
"DuckDuckGo",
|
||||
"Google",
|
||||
)
|
||||
|
||||
@get:Rule
|
||||
|
@ -64,6 +64,7 @@ class SettingsSearchTest {
|
|||
@After
|
||||
fun tearDown() {
|
||||
mockWebServer.shutdown()
|
||||
resetSystemLocaleToEnUS()
|
||||
}
|
||||
|
||||
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/2203333
|
||||
|
@ -424,6 +425,10 @@ class SettingsSearchTest {
|
|||
fun verifyShowSearchSuggestionsToggleTest() {
|
||||
homeScreen {
|
||||
}.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 ")
|
||||
verifySearchEngineSuggestionResults(
|
||||
activityTestRule,
|
||||
|
@ -438,6 +443,10 @@ class SettingsSearchTest {
|
|||
}.goBack {
|
||||
}.goBack {
|
||||
}.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")
|
||||
verifySuggestionsAreNotDisplayed(activityTestRule, "mozilla firefox")
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@ import androidx.test.uiautomator.UiDevice
|
|||
import okhttp3.mockwebserver.MockWebServer
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Ignore
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.mozilla.fenix.customannotations.SmokeTest
|
||||
|
@ -90,7 +89,6 @@ class SponsoredShortcutsTest {
|
|||
}
|
||||
|
||||
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/1729335
|
||||
@Ignore("Failing, see: https://github.com/mozilla-mobile/fenix/issues/25926")
|
||||
@Test
|
||||
fun openSponsorsAndYourPrivacyOptionTest() {
|
||||
homeScreen {
|
||||
|
@ -102,7 +100,6 @@ class SponsoredShortcutsTest {
|
|||
}
|
||||
|
||||
// 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
|
||||
fun openSponsoredShortcutsSettingsOptionTest() {
|
||||
homeScreen {
|
||||
|
|
|
@ -17,6 +17,7 @@ import androidx.compose.ui.test.performClick
|
|||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.ViewInteraction
|
||||
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.contrib.PickerActions
|
||||
import androidx.test.espresso.matcher.RootMatchers.isDialog
|
||||
|
@ -40,7 +41,6 @@ import org.junit.Assert.assertTrue
|
|||
import org.junit.Assert.fail
|
||||
import org.mozilla.fenix.R
|
||||
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.TAG
|
||||
import org.mozilla.fenix.helpers.DataGenerationHelper.getStringResource
|
||||
|
@ -686,24 +686,29 @@ class BrowserRobot {
|
|||
|
||||
fun verifyCookieBannerExists(exists: Boolean) {
|
||||
for (i in 1..RETRY_COUNT) {
|
||||
Log.i(TAG, "verifyCookieBannerExists: For loop: $i")
|
||||
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
|
||||
} catch (e: AssertionError) {
|
||||
if (i == RETRY_COUNT) {
|
||||
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() {
|
||||
assertUIObjectExists(
|
||||
itemWithResId("$packageName:id/parentPanel"),
|
||||
|
@ -841,7 +846,7 @@ class BrowserRobot {
|
|||
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() =
|
||||
assertUIObjectExists(
|
||||
|
@ -1149,6 +1154,7 @@ class BrowserRobot {
|
|||
"$packageName:id/action",
|
||||
getStringResource(R.string.open_in_app_cfr_positive_button_text),
|
||||
).clickAndWaitForNewWindow(waitingTime)
|
||||
Log.i(TAG, "clickOpenLinksInAppsGoToSettingsCFRButton: Clicked \"Go to settings\" open links in apps CFR button")
|
||||
|
||||
SettingsRobot().interact()
|
||||
return SettingsRobot.Transition()
|
||||
|
@ -1302,8 +1308,6 @@ fun clearTextFieldItem(item: UiObject) {
|
|||
item.clearTextField()
|
||||
}
|
||||
|
||||
private fun cookieBanner() = itemWithResId("startsiden-gdpr-disclaimer")
|
||||
|
||||
// Context menu items
|
||||
// Link URL
|
||||
private fun contextMenuLinkUrl(linkUrl: String) =
|
||||
|
|
|
@ -26,6 +26,7 @@ import androidx.recyclerview.widget.RecyclerView
|
|||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.action.ViewActions
|
||||
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.isPartiallyBelow
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
|
@ -51,7 +52,6 @@ import org.junit.Assert
|
|||
import org.junit.Assert.assertTrue
|
||||
import org.mozilla.fenix.R
|
||||
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.DataGenerationHelper.getStringResource
|
||||
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" +
|
||||
" else who uses this device."
|
||||
|
||||
fun verifyNavigationToolbar() = assertUIObjectExists(navigationToolbar)
|
||||
fun verifyNavigationToolbar() = assertUIObjectExists(navigationToolbar())
|
||||
|
||||
fun verifyHomeScreen() = assertUIObjectExists(homeScreen)
|
||||
fun verifyHomeScreen() = assertUIObjectExists(homeScreen())
|
||||
|
||||
fun verifyPrivateBrowsingHomeScreenItems() {
|
||||
verifyHomeScreenAppBarItems()
|
||||
|
@ -97,19 +97,19 @@ class HomeScreenRobot {
|
|||
}
|
||||
|
||||
fun verifyHomeScreenAppBarItems() =
|
||||
assertUIObjectExists(homeScreen, privateBrowsingButton, homepageWordmark)
|
||||
assertUIObjectExists(homeScreen(), privateBrowsingButton(), homepageWordmark())
|
||||
|
||||
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 verifyTabButton() = assertTabButton()
|
||||
fun verifyCollectionsHeader() = assertCollectionsHeader()
|
||||
fun verifyNoCollectionsText() = assertNoCollectionsText()
|
||||
fun verifyHomeWordmark() {
|
||||
homeScreenList().scrollToBeginning(3)
|
||||
assertUIObjectExists(homepageWordmark)
|
||||
assertUIObjectExists(homepageWordmark())
|
||||
}
|
||||
fun verifyHomeComponent() = assertHomeComponent()
|
||||
|
||||
|
@ -140,7 +140,7 @@ class HomeScreenRobot {
|
|||
).assertExists()
|
||||
|
||||
it.onNodeWithText(
|
||||
getStringResource(R.string.juno_onboarding_default_browser_description_nimbus_2),
|
||||
getStringResource(R.string.juno_onboarding_default_browser_description_nimbus_3),
|
||||
).assertExists()
|
||||
|
||||
it.onNodeWithText(
|
||||
|
@ -292,7 +292,7 @@ class HomeScreenRobot {
|
|||
mDevice.waitNotNull(findObject(By.text(expectedText)), waitingTime)
|
||||
}
|
||||
|
||||
fun clickFirefoxLogo() = homepageWordmark.click()
|
||||
fun clickFirefoxLogo() = homepageWordmark().click()
|
||||
|
||||
fun verifyThoughtProvokingStories(enabled: Boolean) {
|
||||
if (enabled) {
|
||||
|
@ -481,8 +481,8 @@ class HomeScreenRobot {
|
|||
}
|
||||
|
||||
fun openSearch(interact: SearchRobot.() -> Unit): SearchRobot.Transition {
|
||||
navigationToolbar.waitForExists(waitingTime)
|
||||
navigationToolbar.click()
|
||||
navigationToolbar().waitForExists(waitingTime)
|
||||
navigationToolbar().click()
|
||||
mDevice.waitForIdle()
|
||||
|
||||
SearchRobot().interact()
|
||||
|
@ -502,14 +502,14 @@ class HomeScreenRobot {
|
|||
fun togglePrivateBrowsingMode(switchPBModeOn: Boolean = true) {
|
||||
// Switch to private browsing homescreen
|
||||
if (switchPBModeOn && !isPrivateModeEnabled()) {
|
||||
privateBrowsingButton.waitForExists(waitingTime)
|
||||
privateBrowsingButton.click()
|
||||
privateBrowsingButton().waitForExists(waitingTime)
|
||||
privateBrowsingButton().click()
|
||||
}
|
||||
|
||||
// Switch to normal browsing homescreen
|
||||
if (!switchPBModeOn && isPrivateModeEnabled()) {
|
||||
privateBrowsingButton.waitForExists(waitingTime)
|
||||
privateBrowsingButton.click()
|
||||
privateBrowsingButton().waitForExists(waitingTime)
|
||||
privateBrowsingButton().click()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -521,7 +521,7 @@ class HomeScreenRobot {
|
|||
waitingTime,
|
||||
)
|
||||
|
||||
privateBrowsingButton.click()
|
||||
privateBrowsingButton().click()
|
||||
}
|
||||
|
||||
AddToHomeScreenRobot().interact()
|
||||
|
@ -535,7 +535,7 @@ class HomeScreenRobot {
|
|||
fun openNavigationToolbar(interact: NavigationToolbarRobot.() -> Unit): NavigationToolbarRobot.Transition {
|
||||
mDevice.findObject(UiSelector().resourceId("$packageName:id/toolbar"))
|
||||
.waitForExists(waitingTime)
|
||||
navigationToolbar.click()
|
||||
navigationToolbar().click()
|
||||
|
||||
NavigationToolbarRobot().interact()
|
||||
return NavigationToolbarRobot.Transition()
|
||||
|
@ -557,7 +557,8 @@ class HomeScreenRobot {
|
|||
}
|
||||
|
||||
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()
|
||||
return Transition()
|
||||
|
@ -631,8 +632,10 @@ class HomeScreenRobot {
|
|||
}
|
||||
|
||||
fun clickSponsoredShortcutsSettingsButton(interact: SettingsSubMenuHomepageRobot.() -> Unit): SettingsSubMenuHomepageRobot.Transition {
|
||||
Log.i(TAG, "clickSponsoredShortcutsSettingsButton: Looking for: ${sponsoredShortcutsSettingsButton.selector}")
|
||||
sponsoredShortcutsSettingsButton.waitForExists(waitingTime)
|
||||
sponsoredShortcutsSettingsButton.clickAndWaitForNewWindow(waitingTime)
|
||||
Log.i(TAG, "clickSponsoredShortcutsSettingsButton: Clicked ${sponsoredShortcutsSettingsButton.selector} and waiting for $waitingTime for a new window")
|
||||
|
||||
SettingsSubMenuHomepageRobot().interact()
|
||||
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 sponsoredShortcut(sponsoredShortcutTitle: String) =
|
||||
mDevice.findObject(
|
||||
By
|
||||
.res("$packageName:id/top_site_title")
|
||||
.textContains(sponsoredShortcutTitle),
|
||||
onView(
|
||||
allOf(
|
||||
withId(R.id.top_site_title),
|
||||
withText(sponsoredShortcutTitle),
|
||||
),
|
||||
)
|
||||
|
||||
private fun storyByTopicItem(composeTestRule: ComposeTestRule, position: Int) =
|
||||
composeTestRule.onNodeWithTag("pocket.categories").onChildAt(position - 1)
|
||||
|
||||
private val homeScreen =
|
||||
private fun homeScreen() =
|
||||
itemWithResId("$packageName:id/homeLayout")
|
||||
private val privateBrowsingButton =
|
||||
private fun privateBrowsingButton() =
|
||||
itemWithResId("$packageName:id/privateBrowsingButton")
|
||||
|
||||
private fun isPrivateModeEnabled(): Boolean =
|
||||
|
@ -959,10 +963,10 @@ private fun isPrivateModeEnabled(): Boolean =
|
|||
"Disable private browsing",
|
||||
).exists()
|
||||
|
||||
private val homepageWordmark =
|
||||
private fun homepageWordmark() =
|
||||
itemWithResId("$packageName:id/wordmark")
|
||||
|
||||
private val navigationToolbar =
|
||||
private fun navigationToolbar() =
|
||||
itemWithResId("$packageName:id/toolbar")
|
||||
private val menuButton =
|
||||
itemWithResId("$packageName:id/menuButton")
|
||||
|
|
|
@ -15,6 +15,7 @@ import androidx.test.espresso.Espresso.onView
|
|||
import androidx.test.espresso.IdlingRegistry
|
||||
import androidx.test.espresso.IdlingResource
|
||||
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.matches
|
||||
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.itemWithResIdAndText
|
||||
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.TestAssetHelper.waitingTime
|
||||
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTimeShort
|
||||
|
@ -149,7 +149,7 @@ class NavigationToolbarRobot {
|
|||
assertTrue(
|
||||
itemWithResId("$packageName:id/browserLayout").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()
|
||||
}
|
||||
|
||||
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 {
|
||||
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.")
|
||||
|
||||
NavigationToolbarRobot().interact()
|
||||
|
@ -388,8 +398,7 @@ private fun awesomeBar() =
|
|||
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 tabTrayButton() = onView(withId(R.id.tab_button))
|
||||
private fun tabsCounter() =
|
||||
mDevice.findObject(By.res("$packageName:id/counter_root"))
|
||||
private fun tabsCounter() = onView(withId(R.id.mozac_browser_toolbar_browser_actions))
|
||||
private fun fillLinkButton() = onView(withId(R.id.fill_link_from_clipboard))
|
||||
private fun clearAddressBarButton() = itemWithResId("$packageName:id/mozac_browser_toolbar_clear_view")
|
||||
private fun readerViewToggle() =
|
||||
|
|
|
@ -107,6 +107,27 @@ class SettingsRobot {
|
|||
fun verifyPrivacyHeading() = assertPrivacyHeading()
|
||||
|
||||
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 verifyLoginsAndPasswordsButton() = assertLoginsAndPasswordsButton()
|
||||
fun verifyPrivateBrowsingButton() = assertPrivateBrowsingButton()
|
||||
|
@ -583,6 +604,7 @@ private fun assertOpenLinksInAppsButton() {
|
|||
scrollToElementByText("Open links in apps")
|
||||
openLinksInAppsButton()
|
||||
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
|
||||
Log.i(TAG, "clickOpenLinksInAppsGoToSettingsCFRButton: Verified \"Open links in apps\" setting option")
|
||||
}
|
||||
|
||||
// ADVANCED SECTION
|
||||
|
|
|
@ -124,7 +124,8 @@ private fun assertCurrentTimestamp() {
|
|||
private fun assertWhatIsNewInFirefoxPreview() {
|
||||
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)))
|
||||
.perform(click())
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
package org.mozilla.fenix.ui.robots
|
||||
|
||||
import android.util.Log
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.matcher.ViewMatchers
|
||||
|
@ -21,6 +22,7 @@ import org.hamcrest.CoreMatchers
|
|||
import org.hamcrest.CoreMatchers.allOf
|
||||
import org.hamcrest.Matchers
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.helpers.Constants
|
||||
import org.mozilla.fenix.helpers.MatcherHelper.assertUIObjectExists
|
||||
import org.mozilla.fenix.helpers.MatcherHelper.itemContainingText
|
||||
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTimeShort
|
||||
|
@ -61,6 +63,7 @@ class SettingsSubMenuHomepageRobot {
|
|||
assertHomepageButton()
|
||||
assertLastTabButton()
|
||||
assertHomepageAfterFourHoursButton()
|
||||
Log.i(Constants.TAG, "verifyHomePageView: Verified the home page elements")
|
||||
}
|
||||
|
||||
fun verifySelectedOpeningScreenOption(openingScreenOption: String) =
|
||||
|
|
|
@ -118,7 +118,7 @@ class SettingsSubMenuLoginsAndPasswordsSavedLoginsRobot {
|
|||
)
|
||||
|
||||
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) =
|
||||
mDevice.waitNotNull(Until.findObjects(By.text(username)))
|
||||
|
|
|
@ -54,9 +54,9 @@ import org.mozilla.fenix.nimbus.FxNimbus
|
|||
class ThreeDotMenuMainRobot {
|
||||
fun verifyShareAllTabsButton() = assertShareAllTabsButton()
|
||||
fun verifySettingsButton() = assertUIObjectExists(settingsButton())
|
||||
fun verifyHistoryButton() = assertUIObjectExists(historyButton)
|
||||
fun verifyHistoryButton() = assertUIObjectExists(historyButton())
|
||||
fun verifyThreeDotMenuExists() = threeDotMenuRecyclerViewExists()
|
||||
fun verifyAddBookmarkButton() = assertUIObjectExists(addBookmarkButton)
|
||||
fun verifyAddBookmarkButton() = assertUIObjectExists(addBookmarkButton())
|
||||
fun verifyEditBookmarkButton() = assertEditBookmarkButton()
|
||||
fun verifyCloseAllTabsButton() = assertCloseAllTabsButton()
|
||||
fun verifyReaderViewAppearance(visible: Boolean) = assertReaderViewAppearanceButton(visible)
|
||||
|
@ -76,9 +76,9 @@ class ThreeDotMenuMainRobot {
|
|||
fun verifyShareTabButton() = assertShareTabButton()
|
||||
fun verifySelectTabs() = assertSelectTabsButton()
|
||||
|
||||
fun verifyFindInPageButton() = assertUIObjectExists(findInPageButton)
|
||||
fun verifyFindInPageButton() = assertUIObjectExists(findInPageButton())
|
||||
fun verifyAddToShortcutsButton(shouldExist: Boolean) =
|
||||
assertUIObjectExists(addToShortcutsButton, exists = shouldExist)
|
||||
assertUIObjectExists(addToShortcutsButton(), exists = shouldExist)
|
||||
fun verifyRemoveFromShortcutsButton() = assertRemoveFromShortcutsButton()
|
||||
fun verifyShareTabsOverlay() = assertShareTabsOverlay()
|
||||
|
||||
|
@ -90,20 +90,21 @@ class ThreeDotMenuMainRobot {
|
|||
fun verifyPageThreeDotMainMenuItems(isRequestDesktopSiteEnabled: Boolean) {
|
||||
expandMenu()
|
||||
assertUIObjectExists(
|
||||
normalBrowsingNewTabButton,
|
||||
bookmarksButton,
|
||||
historyButton,
|
||||
downloadsButton,
|
||||
addOnsButton,
|
||||
syncAndSaveDataButton,
|
||||
findInPageButton,
|
||||
desktopSiteButton,
|
||||
reportSiteIssueButton,
|
||||
addToHomeScreenButton,
|
||||
addToShortcutsButton,
|
||||
saveToCollectionButton,
|
||||
addBookmarkButton,
|
||||
normalBrowsingNewTabButton(),
|
||||
bookmarksButton(),
|
||||
historyButton(),
|
||||
downloadsButton(),
|
||||
addOnsButton(),
|
||||
syncAndSaveDataButton(),
|
||||
findInPageButton(),
|
||||
desktopSiteButton(),
|
||||
reportSiteIssueButton(),
|
||||
addToHomeScreenButton(),
|
||||
addToShortcutsButton(),
|
||||
saveToCollectionButton(),
|
||||
addBookmarkButton(),
|
||||
desktopSiteToggle(isRequestDesktopSiteEnabled),
|
||||
translateButton(),
|
||||
)
|
||||
// Swipe to second part of menu
|
||||
expandMenu()
|
||||
|
@ -111,28 +112,28 @@ class ThreeDotMenuMainRobot {
|
|||
settingsButton(),
|
||||
)
|
||||
if (FxNimbus.features.print.value().browserPrintEnabled) {
|
||||
assertUIObjectExists(printContentButton)
|
||||
assertUIObjectExists(printContentButton())
|
||||
}
|
||||
assertUIObjectExists(
|
||||
backButton,
|
||||
forwardButton,
|
||||
shareButton,
|
||||
refreshButton,
|
||||
backButton(),
|
||||
forwardButton(),
|
||||
shareButton(),
|
||||
refreshButton(),
|
||||
)
|
||||
}
|
||||
|
||||
fun verifyHomeThreeDotMainMenuItems(isRequestDesktopSiteEnabled: Boolean) {
|
||||
assertUIObjectExists(
|
||||
bookmarksButton,
|
||||
historyButton,
|
||||
downloadsButton,
|
||||
addOnsButton,
|
||||
bookmarksButton(),
|
||||
historyButton(),
|
||||
downloadsButton(),
|
||||
addOnsButton(),
|
||||
// Disabled step due to https://github.com/mozilla-mobile/fenix/issues/26788
|
||||
// syncAndSaveDataButton,
|
||||
desktopSiteButton,
|
||||
whatsNewButton,
|
||||
helpButton,
|
||||
customizeHomeButton,
|
||||
desktopSiteButton(),
|
||||
whatsNewButton(),
|
||||
helpButton(),
|
||||
customizeHomeButton(),
|
||||
settingsButton(),
|
||||
desktopSiteToggle(isRequestDesktopSiteEnabled),
|
||||
)
|
||||
|
@ -202,7 +203,7 @@ class ThreeDotMenuMainRobot {
|
|||
fun openDownloadsManager(interact: DownloadRobot.() -> Unit): DownloadRobot.Transition {
|
||||
threeDotMenuRecyclerView().perform(swipeDown())
|
||||
Log.i(TAG, "openDownloadsManager: Swiped up main menu")
|
||||
downloadsButton.click()
|
||||
downloadsButton().click()
|
||||
Log.i(TAG, "openDownloadsManager: Clicked main menu \"DOWNLOADS\" button")
|
||||
|
||||
DownloadRobot().interact()
|
||||
|
@ -212,7 +213,7 @@ class ThreeDotMenuMainRobot {
|
|||
fun openSyncSignIn(interact: SyncSignInRobot.() -> Unit): SyncSignInRobot.Transition {
|
||||
threeDotMenuRecyclerView().perform(swipeDown())
|
||||
mDevice.waitNotNull(Until.findObject(By.text("Sync and save data")), waitingTime)
|
||||
syncAndSaveDataButton.click()
|
||||
syncAndSaveDataButton().click()
|
||||
|
||||
SyncSignInRobot().interact()
|
||||
return SyncSignInRobot.Transition()
|
||||
|
@ -222,7 +223,7 @@ class ThreeDotMenuMainRobot {
|
|||
threeDotMenuRecyclerView().perform(swipeDown())
|
||||
mDevice.waitNotNull(Until.findObject(By.text("Bookmarks")), waitingTime)
|
||||
|
||||
bookmarksButton.click()
|
||||
bookmarksButton().click()
|
||||
assertUIObjectExists(itemWithResId("$packageName:id/bookmark_list"))
|
||||
|
||||
BookmarksRobot().interact()
|
||||
|
@ -230,7 +231,7 @@ class ThreeDotMenuMainRobot {
|
|||
}
|
||||
|
||||
fun clickNewTabButton(interact: SearchRobot.() -> Unit): SearchRobot.Transition {
|
||||
normalBrowsingNewTabButton.click()
|
||||
normalBrowsingNewTabButton().click()
|
||||
|
||||
SearchRobot().interact()
|
||||
return SearchRobot.Transition()
|
||||
|
@ -239,7 +240,7 @@ class ThreeDotMenuMainRobot {
|
|||
fun openHistory(interact: HistoryRobot.() -> Unit): HistoryRobot.Transition {
|
||||
threeDotMenuRecyclerView().perform(swipeDown())
|
||||
mDevice.waitNotNull(Until.findObject(By.text("History")), waitingTime)
|
||||
historyButton.click()
|
||||
historyButton().click()
|
||||
|
||||
HistoryRobot().interact()
|
||||
return HistoryRobot.Transition()
|
||||
|
@ -247,7 +248,7 @@ class ThreeDotMenuMainRobot {
|
|||
|
||||
fun bookmarkPage(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
|
||||
mDevice.waitNotNull(Until.findObject(By.text("Bookmarks")), waitingTime)
|
||||
addBookmarkButton.click()
|
||||
addBookmarkButton().click()
|
||||
|
||||
BrowserRobot().interact()
|
||||
return BrowserRobot.Transition()
|
||||
|
@ -263,7 +264,7 @@ class ThreeDotMenuMainRobot {
|
|||
|
||||
fun openHelp(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
|
||||
mDevice.waitNotNull(Until.findObject(By.text("Help")), waitingTime)
|
||||
helpButton.click()
|
||||
helpButton().click()
|
||||
|
||||
BrowserRobot().interact()
|
||||
return BrowserRobot.Transition()
|
||||
|
@ -278,7 +279,7 @@ class ThreeDotMenuMainRobot {
|
|||
waitingTime,
|
||||
)
|
||||
|
||||
customizeHomeButton.click()
|
||||
customizeHomeButton().click()
|
||||
|
||||
mDevice.findObject(
|
||||
UiSelector().resourceId("$packageName:id/recycler_view"),
|
||||
|
@ -289,21 +290,21 @@ class ThreeDotMenuMainRobot {
|
|||
}
|
||||
|
||||
fun goForward(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
|
||||
forwardButton.click()
|
||||
forwardButton().click()
|
||||
|
||||
BrowserRobot().interact()
|
||||
return BrowserRobot.Transition()
|
||||
}
|
||||
|
||||
fun goToPreviousPage(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
|
||||
backButton.click()
|
||||
backButton().click()
|
||||
|
||||
BrowserRobot().interact()
|
||||
return BrowserRobot.Transition()
|
||||
}
|
||||
|
||||
fun clickShareButton(interact: ShareOverlayRobot.() -> Unit): ShareOverlayRobot.Transition {
|
||||
shareButton.click()
|
||||
shareButton().click()
|
||||
Log.i(TAG, "clickShareButton: Clicked main menu share button")
|
||||
mDevice.waitNotNull(Until.findObject(By.text("ALL ACTIONS")), waitingTime)
|
||||
|
||||
|
@ -320,7 +321,7 @@ class ThreeDotMenuMainRobot {
|
|||
}
|
||||
|
||||
fun refreshPage(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
|
||||
refreshButton.also {
|
||||
refreshButton().also {
|
||||
Log.i(TAG, "refreshPage: Looking for refresh button")
|
||||
it.waitForExists(waitingTime)
|
||||
it.click()
|
||||
|
@ -349,7 +350,7 @@ class ThreeDotMenuMainRobot {
|
|||
fun openReportSiteIssue(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
|
||||
threeDotMenuRecyclerView().perform(swipeUp())
|
||||
threeDotMenuRecyclerView().perform(swipeUp())
|
||||
reportSiteIssueButton.click()
|
||||
reportSiteIssueButton().click()
|
||||
|
||||
BrowserRobot().interact()
|
||||
return BrowserRobot.Transition()
|
||||
|
@ -359,7 +360,7 @@ class ThreeDotMenuMainRobot {
|
|||
threeDotMenuRecyclerView().perform(swipeUp())
|
||||
threeDotMenuRecyclerView().perform(swipeUp())
|
||||
mDevice.waitNotNull(Until.findObject(By.text("Find in page")), waitingTime)
|
||||
findInPageButton.click()
|
||||
findInPageButton().click()
|
||||
|
||||
FindInPageRobot().interact()
|
||||
return FindInPageRobot.Transition()
|
||||
|
@ -367,7 +368,7 @@ class ThreeDotMenuMainRobot {
|
|||
|
||||
fun openWhatsNew(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
|
||||
mDevice.waitNotNull(Until.findObject(By.text("What’s new")), waitingTime)
|
||||
whatsNewButton.click()
|
||||
whatsNewButton().click()
|
||||
|
||||
BrowserRobot().interact()
|
||||
return BrowserRobot.Transition()
|
||||
|
@ -385,7 +386,7 @@ class ThreeDotMenuMainRobot {
|
|||
fun addToFirefoxHome(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
|
||||
for (i in 1..RETRY_COUNT) {
|
||||
try {
|
||||
addToShortcutsButton.also {
|
||||
addToShortcutsButton().also {
|
||||
it.waitForExists(waitingTime)
|
||||
it.click()
|
||||
}
|
||||
|
@ -416,7 +417,7 @@ class ThreeDotMenuMainRobot {
|
|||
}
|
||||
|
||||
fun openAddToHomeScreen(interact: AddToHomeScreenRobot.() -> Unit): AddToHomeScreenRobot.Transition {
|
||||
addToHomeScreenButton.clickAndWaitForNewWindow(waitingTime)
|
||||
addToHomeScreenButton().clickAndWaitForNewWindow(waitingTime)
|
||||
|
||||
AddToHomeScreenRobot().interact()
|
||||
return AddToHomeScreenRobot.Transition()
|
||||
|
@ -437,7 +438,7 @@ class ThreeDotMenuMainRobot {
|
|||
threeDotMenuRecyclerView().perform(swipeUp())
|
||||
|
||||
mDevice.waitNotNull(Until.findObject(By.text("Save to collection")), waitingTime)
|
||||
saveToCollectionButton.click()
|
||||
saveToCollectionButton().click()
|
||||
CollectionRobot().interact()
|
||||
return CollectionRobot.Transition()
|
||||
}
|
||||
|
@ -465,7 +466,7 @@ class ThreeDotMenuMainRobot {
|
|||
fun switchDesktopSiteMode(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
|
||||
threeDotMenuRecyclerView().perform(swipeUp())
|
||||
threeDotMenuRecyclerView().perform(swipeUp())
|
||||
desktopSiteButton.click()
|
||||
desktopSiteButton().click()
|
||||
|
||||
BrowserRobot().interact()
|
||||
return BrowserRobot.Transition()
|
||||
|
@ -481,8 +482,8 @@ class ThreeDotMenuMainRobot {
|
|||
fun clickPrintButton(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
|
||||
threeDotMenuRecyclerView().perform(swipeUp())
|
||||
threeDotMenuRecyclerView().perform(swipeUp())
|
||||
printButton.waitForExists(waitingTime)
|
||||
printButton.click()
|
||||
printButton().waitForExists(waitingTime)
|
||||
printButton().click()
|
||||
|
||||
BrowserRobot().interact()
|
||||
return BrowserRobot.Transition()
|
||||
|
@ -558,7 +559,7 @@ private fun openInAppButton() =
|
|||
|
||||
private fun clickAddonsManagerButton() {
|
||||
onView(withId(R.id.mozac_browser_menu_menuView)).perform(swipeDown())
|
||||
addOnsButton.click()
|
||||
addOnsButton().click()
|
||||
}
|
||||
|
||||
private fun shareAllTabsButton() =
|
||||
|
@ -571,15 +572,15 @@ private fun assertShareAllTabsButton() {
|
|||
)
|
||||
}
|
||||
|
||||
private val bookmarksButton =
|
||||
private fun bookmarksButton() =
|
||||
itemContainingText(getStringResource(R.string.library_bookmarks))
|
||||
private val historyButton =
|
||||
private fun historyButton() =
|
||||
itemContainingText(getStringResource(R.string.library_history))
|
||||
private val downloadsButton =
|
||||
private fun downloadsButton() =
|
||||
itemContainingText(getStringResource(R.string.library_downloads))
|
||||
private val addOnsButton =
|
||||
private fun addOnsButton() =
|
||||
itemContainingText(getStringResource(R.string.browser_menu_add_ons))
|
||||
private val desktopSiteButton =
|
||||
private fun desktopSiteButton() =
|
||||
itemContainingText(getStringResource(R.string.browser_menu_desktop_site))
|
||||
private fun desktopSiteToggle(state: Boolean) =
|
||||
checkedItemWithResIdAndText(
|
||||
|
@ -587,31 +588,32 @@ private fun desktopSiteToggle(state: Boolean) =
|
|||
getStringResource(R.string.browser_menu_desktop_site),
|
||||
state,
|
||||
)
|
||||
private val whatsNewButton =
|
||||
private fun whatsNewButton() =
|
||||
itemContainingText(getStringResource(R.string.browser_menu_whats_new))
|
||||
private val helpButton =
|
||||
private fun helpButton() =
|
||||
itemContainingText(getStringResource(R.string.browser_menu_help))
|
||||
private val customizeHomeButton =
|
||||
private fun customizeHomeButton() =
|
||||
itemContainingText(getStringResource(R.string.browser_menu_customize_home_1))
|
||||
private fun settingsButton(localizedText: String = getStringResource(R.string.browser_menu_settings)) =
|
||||
itemContainingText(localizedText)
|
||||
private val syncAndSaveDataButton =
|
||||
private fun syncAndSaveDataButton() =
|
||||
itemContainingText(getStringResource(R.string.sync_menu_sync_and_save_data))
|
||||
private val normalBrowsingNewTabButton =
|
||||
private fun normalBrowsingNewTabButton() =
|
||||
itemContainingText(getStringResource(R.string.library_new_tab))
|
||||
private val addBookmarkButton =
|
||||
private fun addBookmarkButton() =
|
||||
itemWithResIdAndText(
|
||||
"$packageName:id/checkbox",
|
||||
getStringResource(R.string.browser_menu_add),
|
||||
)
|
||||
private val findInPageButton = itemContainingText(getStringResource(R.string.browser_menu_find_in_page))
|
||||
private val reportSiteIssueButton = itemContainingText("Report Site Issue")
|
||||
private val addToHomeScreenButton = itemContainingText(getStringResource(R.string.browser_menu_add_to_homescreen))
|
||||
private val addToShortcutsButton = itemContainingText(getStringResource(R.string.browser_menu_add_to_shortcuts))
|
||||
private val saveToCollectionButton = itemContainingText(getStringResource(R.string.browser_menu_save_to_collection_2))
|
||||
private val printContentButton = itemContainingText(getStringResource(R.string.menu_print))
|
||||
private val backButton = itemWithDescription(getStringResource(R.string.browser_menu_back))
|
||||
private val forwardButton = itemWithDescription(getStringResource(R.string.browser_menu_forward))
|
||||
private val shareButton = itemWithDescription(getStringResource(R.string.share_button_content_description))
|
||||
private val refreshButton = itemWithDescription(getStringResource(R.string.browser_menu_refresh))
|
||||
private val printButton = itemWithText("Print")
|
||||
private fun findInPageButton() = itemContainingText(getStringResource(R.string.browser_menu_find_in_page))
|
||||
private fun translateButton() = itemContainingText(getStringResource(R.string.browser_menu_translations))
|
||||
private fun reportSiteIssueButton() = itemContainingText("Report Site Issue")
|
||||
private fun addToHomeScreenButton() = itemContainingText(getStringResource(R.string.browser_menu_add_to_homescreen))
|
||||
private fun addToShortcutsButton() = itemContainingText(getStringResource(R.string.browser_menu_add_to_shortcuts))
|
||||
private fun saveToCollectionButton() = itemContainingText(getStringResource(R.string.browser_menu_save_to_collection_2))
|
||||
private fun printContentButton() = itemContainingText(getStringResource(R.string.menu_print))
|
||||
private fun backButton() = itemWithDescription(getStringResource(R.string.browser_menu_back))
|
||||
private fun forwardButton() = itemWithDescription(getStringResource(R.string.browser_menu_forward))
|
||||
private fun shareButton() = itemWithDescription(getStringResource(R.string.share_button_content_description))
|
||||
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/. -->
|
||||
<resources>
|
||||
<!-- Name of the application -->
|
||||
<string name="app_name" translatable="false">LeOsium</string>
|
||||
<string name="app_name" translatable="false">LeOSium</string>
|
||||
</resources>
|
||||
|
|
|
@ -54,7 +54,7 @@
|
|||
android:name=".FenixApplication"
|
||||
android:allowBackup="false"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="LeOSium"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/NormalTheme"
|
||||
|
@ -354,6 +354,7 @@
|
|||
|
||||
<service
|
||||
android:name=".downloads.DownloadService"
|
||||
android:foregroundServiceType="dataSync"
|
||||
android:exported="false" />
|
||||
|
||||
<receiver
|
||||
|
@ -374,8 +375,14 @@
|
|||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<service android:name=".session.PrivateNotificationService"
|
||||
android:exported="false" />
|
||||
<service
|
||||
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
|
||||
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),
|
||||
FromReviewQualityCheck(R.id.reviewQualityCheckFragment),
|
||||
FromAddonsManagementFragment(R.id.addonsManagementFragment),
|
||||
FromTranslationsDialogFragment(R.id.translationsDialogFragment),
|
||||
}
|
||||
|
|
|
@ -74,6 +74,11 @@ object FeatureFlags {
|
|||
*/
|
||||
const val fxSuggest = true
|
||||
|
||||
/**
|
||||
* Allows users to enable SuggestStrongPassword feature.
|
||||
*/
|
||||
const val suggestStrongPassword = true
|
||||
|
||||
/**
|
||||
* Enable Meta attribution.
|
||||
*/
|
||||
|
|
|
@ -23,7 +23,7 @@ class FenixLogSink(
|
|||
priority: Log.Priority,
|
||||
tag: String?,
|
||||
throwable: Throwable?,
|
||||
message: String?,
|
||||
message: String,
|
||||
) {
|
||||
if (priority == Log.Priority.DEBUG && !logsDebug) {
|
||||
return
|
||||
|
|
|
@ -29,14 +29,12 @@ import androidx.annotation.CallSuper
|
|||
import androidx.annotation.IdRes
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.annotation.VisibleForTesting.Companion.PROTECTED
|
||||
import androidx.appcompat.app.ActionBar
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.NavDestination
|
||||
import androidx.navigation.NavDirections
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.fragment.NavHostFragment
|
||||
import androidx.navigation.ui.AppBarConfiguration
|
||||
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.SplashScreen
|
||||
import org.mozilla.fenix.GleanMetrics.StartOnHome
|
||||
import org.mozilla.fenix.addons.AddonDetailsFragmentDirections
|
||||
import org.mozilla.fenix.addons.AddonPermissionsDetailsFragmentDirections
|
||||
import org.mozilla.fenix.addons.AddonsManagementFragmentDirections
|
||||
import org.mozilla.fenix.addons.ExtensionsProcessDisabledController
|
||||
import org.mozilla.fenix.addons.ExtensionsProcessDisabledBackgroundController
|
||||
import org.mozilla.fenix.addons.ExtensionsProcessDisabledForegroundController
|
||||
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
|
||||
import org.mozilla.fenix.browser.browsingmode.BrowsingModeManager
|
||||
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.databinding.ActivityHomeBinding
|
||||
import org.mozilla.fenix.debugsettings.data.DefaultDebugSettingsRepository
|
||||
import org.mozilla.fenix.debugsettings.ui.DebugOverlay
|
||||
import org.mozilla.fenix.exceptions.trackingprotection.TrackingProtectionExceptionsFragmentDirections
|
||||
import org.mozilla.fenix.debugsettings.ui.FenixOverlay
|
||||
import org.mozilla.fenix.experiments.ResearchSurfaceDialogFragment
|
||||
import org.mozilla.fenix.ext.alreadyOnDestination
|
||||
import org.mozilla.fenix.ext.breadcrumb
|
||||
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.nav
|
||||
import org.mozilla.fenix.ext.setNavigationIcon
|
||||
import org.mozilla.fenix.ext.settings
|
||||
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.CrashReporterIntentProcessor
|
||||
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.SpeechProcessingIntentProcessor
|
||||
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.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.FenixNimbusMessagingController
|
||||
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.StartupTimeline
|
||||
import org.mozilla.fenix.perf.StartupTypeTelemetry
|
||||
import org.mozilla.fenix.search.SearchDialogFragmentDirections
|
||||
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.tabhistory.TabHistoryDialogFragment
|
||||
import org.mozilla.fenix.tabstray.TabsTrayFragment
|
||||
import org.mozilla.fenix.tabstray.TabsTrayFragmentDirections
|
||||
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.trackingprotection.TrackingProtectionPanelDialogFragmentDirections
|
||||
import org.mozilla.fenix.utils.Settings
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.Locale
|
||||
|
@ -176,7 +153,7 @@ import java.util.Locale
|
|||
* - home screen
|
||||
* - browser screen
|
||||
*/
|
||||
@SuppressWarnings("TooManyFunctions", "LargeClass", "LongParameterList", "LongMethod")
|
||||
@SuppressWarnings("TooManyFunctions", "LargeClass", "LongMethod")
|
||||
open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
|
||||
// DO NOT MOVE ANYTHING ABOVE THIS, GETTING INIT TIME IS CRITICAL
|
||||
// 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 {
|
||||
ExtensionsProcessDisabledController(this@HomeActivity)
|
||||
private val extensionsProcessDisabledForegroundController by lazy {
|
||||
ExtensionsProcessDisabledForegroundController(this@HomeActivity)
|
||||
}
|
||||
|
||||
private val extensionsProcessDisabledBackgroundController by lazy {
|
||||
ExtensionsProcessDisabledBackgroundController(
|
||||
browserStore = components.core.store,
|
||||
appStore = components.appStore,
|
||||
)
|
||||
}
|
||||
|
||||
private val serviceWorkerSupport by lazy {
|
||||
|
@ -300,9 +284,10 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
|
|||
visibility = View.VISIBLE
|
||||
|
||||
setContent {
|
||||
FirefoxTheme(theme = Theme.getTheme(allowPrivateTheme = false)) {
|
||||
DebugOverlay()
|
||||
}
|
||||
FenixOverlay(
|
||||
browserStore = components.core.store,
|
||||
inactiveTabsEnabled = settings().inactiveTabsAreEnabled,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
setContent {}
|
||||
|
@ -348,7 +333,7 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
|
|||
// Unless the activity is recreated, navigate to home first (without rendering it)
|
||||
// to add it to the back stack.
|
||||
if (savedInstanceState == null) {
|
||||
navigateToHome()
|
||||
navigateToHome(navHost.navController)
|
||||
}
|
||||
|
||||
if (!shouldStartOnHome() && shouldNavigateToBrowserOnColdStart(savedInstanceState)) {
|
||||
|
@ -392,7 +377,8 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
|
|||
|
||||
lifecycle.addObservers(
|
||||
webExtensionPopupObserver,
|
||||
extensionsProcessDisabledPromptObserver,
|
||||
extensionsProcessDisabledForegroundController,
|
||||
extensionsProcessDisabledBackgroundController,
|
||||
serviceWorkerSupport,
|
||||
webExtensionPromptFeature,
|
||||
)
|
||||
|
@ -699,7 +685,12 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
|
|||
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:
|
||||
// https://github.com/mozilla-mobile/android-components/issues/7960
|
||||
breadcrumb(
|
||||
|
@ -886,20 +877,6 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
|
|||
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
|
||||
* 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
|
||||
* value of [searchTermOrURL]).
|
||||
|
@ -1003,7 +978,6 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
|
|||
* was opened from history.
|
||||
* @param additionalHeaders The extra headers to use when loading the URL.
|
||||
*/
|
||||
@Suppress("LongParameterList")
|
||||
fun openToBrowserAndLoad(
|
||||
searchTermOrURL: String,
|
||||
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]).
|
||||
*
|
||||
|
@ -1194,7 +1109,12 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
|
|||
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
|
||||
// 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) {
|
||||
|
@ -1203,8 +1123,13 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
|
|||
}
|
||||
}
|
||||
|
||||
open fun navigateToHome() {
|
||||
navHost.navController.navigate(NavGraphDirections.actionStartupHome())
|
||||
@VisibleForTesting
|
||||
internal fun navigateToHome(navController: NavController) {
|
||||
if (this is ExternalAppBrowserActivity) {
|
||||
return
|
||||
}
|
||||
|
||||
navController.navigate(NavGraphDirections.actionStartupHome())
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.Button
|
||||
import android.widget.TextView
|
||||
|
@ -20,35 +18,30 @@ import mozilla.components.support.webextensions.ExtensionsProcessDisabledPromptO
|
|||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.components.AppStore
|
||||
import org.mozilla.fenix.ext.components
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
/**
|
||||
* 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
|
||||
* process spawning or disable extensions). When in background, we kill the app to prevent
|
||||
* extensions from being disabled and network requests continuing.
|
||||
* process spawning or disable extensions).
|
||||
*
|
||||
* @param context to show the AlertDialog
|
||||
* @param browserStore The [BrowserStore] which holds the state for showing the dialog
|
||||
* @param appStore The [AppStore] containing the application state
|
||||
* @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 onKillApp called when the app is backgrounded and extensions process is disabled
|
||||
*/
|
||||
class ExtensionsProcessDisabledController(
|
||||
class ExtensionsProcessDisabledForegroundController(
|
||||
@UiContext context: Context,
|
||||
browserStore: BrowserStore = context.components.core.store,
|
||||
appStore: AppStore = context.components.appStore,
|
||||
builder: AlertDialog.Builder = AlertDialog.Builder(context),
|
||||
appName: String = context.appName,
|
||||
onKillApp: () -> Unit = { killApp() },
|
||||
) : ExtensionsProcessDisabledPromptObserver(
|
||||
browserStore,
|
||||
store = browserStore,
|
||||
shouldCancelOnStop = true,
|
||||
{
|
||||
if (appStore.state.isForeground) {
|
||||
presentDialog(context, browserStore, builder, appName)
|
||||
} else {
|
||||
onKillApp.invoke()
|
||||
}
|
||||
},
|
||||
) {
|
||||
|
@ -61,16 +54,6 @@ class ExtensionsProcessDisabledController(
|
|||
companion object {
|
||||
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
|
||||
* 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.Bundle
|
||||
import android.provider.Settings
|
||||
import android.util.Log
|
||||
import android.view.Gravity
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
|
@ -31,9 +32,11 @@ import androidx.navigation.NavController
|
|||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.Dispatchers.IO
|
||||
import kotlinx.coroutines.Dispatchers.Main
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.distinctUntilChangedBy
|
||||
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.engine.permission.SitePermissions
|
||||
import mozilla.components.concept.engine.prompt.ShareData
|
||||
import mozilla.components.concept.storage.LoginEntry
|
||||
import mozilla.components.feature.accounts.FxaCapability
|
||||
import mozilla.components.feature.accounts.FxaWebChannelFeature
|
||||
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.DialogColorsProvider
|
||||
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.readerview.ReaderViewFeature
|
||||
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.sync.autofill.DefaultCreditCardValidationDelegate
|
||||
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.PermissionsFeature
|
||||
import mozilla.components.support.base.feature.UserInteractionHandler
|
||||
|
@ -108,6 +115,7 @@ import mozilla.components.support.locale.ActivityContextWrapper
|
|||
import mozilla.components.ui.widgets.withCenterAlignedButtons
|
||||
import org.mozilla.fenix.BuildConfig
|
||||
import org.mozilla.fenix.FeatureFlags
|
||||
import org.mozilla.fenix.GleanMetrics.Events
|
||||
import org.mozilla.fenix.GleanMetrics.MediaState
|
||||
import org.mozilla.fenix.GleanMetrics.PullToRefreshInBrowser
|
||||
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.wifi.SitePermissionsWifiIntegration
|
||||
import java.lang.ref.WeakReference
|
||||
import kotlin.coroutines.cancellation.CancellationException
|
||||
import mozilla.components.feature.session.behavior.ToolbarPosition as MozacToolbarPosition
|
||||
|
||||
/**
|
||||
|
@ -460,6 +469,7 @@ abstract class BaseBrowserFragment :
|
|||
|
||||
browserToolbarView.view.display.setOnSiteSecurityClickedListener {
|
||||
showQuickSettingsDialog()
|
||||
Events.browserToolbarSecurityIndicatorTapped.record()
|
||||
}
|
||||
|
||||
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 {
|
||||
override val creditCardPickerView
|
||||
get() = binding.creditCardSelectBar
|
||||
|
@ -894,9 +916,12 @@ abstract class BaseBrowserFragment :
|
|||
binding.swipeRefresh.isEnabled = shouldPullToRefreshBeEnabled(false)
|
||||
|
||||
if (binding.swipeRefresh.isEnabled) {
|
||||
val primaryTextColor =
|
||||
ThemeManager.resolveAttribute(R.attr.textPrimary, context)
|
||||
binding.swipeRefresh.setColorSchemeColors(primaryTextColor)
|
||||
val primaryTextColor = ThemeManager.resolveAttribute(R.attr.textPrimary, context)
|
||||
val primaryBackgroundColor = ThemeManager.resolveAttribute(R.attr.layer2, context)
|
||||
binding.swipeRefresh.apply {
|
||||
setColorSchemeResources(primaryTextColor)
|
||||
setProgressBackgroundColorSchemeResource(primaryBackgroundColor)
|
||||
}
|
||||
swipeRefreshFeature.set(
|
||||
feature = SwipeRefreshFeature(
|
||||
requireComponents.core.store,
|
||||
|
@ -1651,4 +1676,38 @@ abstract class BaseBrowserFragment :
|
|||
|
||||
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 actionToPerform The action to be performed on action button press.
|
||||
*/
|
||||
@Suppress("LongParameterList")
|
||||
class DynamicInfoBanner(
|
||||
private val context: Context,
|
||||
container: ViewGroup,
|
||||
|
|
|
@ -26,7 +26,6 @@ import org.mozilla.fenix.ext.settings
|
|||
* @property dismissAction Optional callback invoked when the user dismisses the banner.
|
||||
* @param actionToPerform The action to be performed on action button press.
|
||||
*/
|
||||
@SuppressWarnings("LongParameterList")
|
||||
open class InfoBanner(
|
||||
private val context: Context,
|
||||
private val container: ViewGroup,
|
||||
|
|
|
@ -53,7 +53,7 @@ class IntentProcessors(
|
|||
* Provides intent processing functionality for ACTION_VIEW and ACTION_SEND intents in private tabs.
|
||||
*/
|
||||
val privateIntentProcessor by lazyMonitored {
|
||||
TabIntentProcessor(tabsUseCases, searchUseCases.newTabSearch, isPrivate = true)
|
||||
TabIntentProcessor(tabsUseCases, searchUseCases.newPrivateTabSearch, isPrivate = true)
|
||||
}
|
||||
|
||||
val customTabIntentProcessor by lazyMonitored {
|
||||
|
|
|
@ -223,7 +223,9 @@ class DefaultBrowserToolbarController(
|
|||
|
||||
override fun handleTranslationsButtonClick() {
|
||||
val directions =
|
||||
BrowserFragmentDirections.actionBrowserFragmentToTranslationsDialogFragment()
|
||||
BrowserFragmentDirections.actionBrowserFragmentToTranslationsDialogFragment(
|
||||
sessionId = currentSession?.id,
|
||||
)
|
||||
navController.navigateSafe(R.id.browserFragment, directions)
|
||||
}
|
||||
|
||||
|
|
|
@ -406,6 +406,14 @@ class DefaultBrowserToolbarMenuController(
|
|||
.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) {
|
||||
when (item) {
|
||||
is ToolbarMenu.Item.OpenInFenix ->
|
||||
|
@ -433,10 +441,19 @@ class DefaultBrowserToolbarMenuController(
|
|||
Events.browserMenuAction.record(Events.BrowserMenuActionExtra("open_in_app"))
|
||||
is ToolbarMenu.Item.CustomizeReaderView ->
|
||||
Events.browserMenuAction.record(Events.BrowserMenuActionExtra("reader_mode_appearance"))
|
||||
is ToolbarMenu.Item.Back ->
|
||||
Events.browserMenuAction.record(Events.BrowserMenuActionExtra("back"))
|
||||
is ToolbarMenu.Item.Back -> {
|
||||
if (item.viewHistory) {
|
||||
Events.browserMenuAction.record(Events.BrowserMenuActionExtra("back_long_press"))
|
||||
} else {
|
||||
Events.browserMenuAction.record(Events.BrowserMenuActionExtra("back"))
|
||||
}
|
||||
}
|
||||
is ToolbarMenu.Item.Forward ->
|
||||
Events.browserMenuAction.record(Events.BrowserMenuActionExtra("forward"))
|
||||
if (item.viewHistory) {
|
||||
Events.browserMenuAction.record(Events.BrowserMenuActionExtra("forward_long_press"))
|
||||
} else {
|
||||
Events.browserMenuAction.record(Events.BrowserMenuActionExtra("forward"))
|
||||
}
|
||||
is ToolbarMenu.Item.Reload ->
|
||||
Events.browserMenuAction.record(Events.BrowserMenuActionExtra("reload"))
|
||||
is ToolbarMenu.Item.Stop ->
|
||||
|
@ -483,6 +500,12 @@ class DefaultBrowserToolbarMenuController(
|
|||
Events.browserMenuAction.record(Events.BrowserMenuActionExtra("set_default_browser"))
|
||||
is ToolbarMenu.Item.RemoveFromTopSites ->
|
||||
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.nimbus.FxNimbus
|
||||
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.
|
||||
|
@ -55,7 +54,7 @@ import org.mozilla.fenix.utils.Settings
|
|||
* @param pinnedSiteStorage Used to check if the current url is a pinned site.
|
||||
* @property isPinningSupported true if the launcher supports adding shortcuts.
|
||||
*/
|
||||
@Suppress("LargeClass", "LongParameterList", "TooManyFunctions")
|
||||
@Suppress("LargeClass", "TooManyFunctions")
|
||||
open class DefaultToolbarMenu(
|
||||
private val context: Context,
|
||||
private val store: BrowserStore,
|
||||
|
@ -193,6 +192,14 @@ open class DefaultToolbarMenu(
|
|||
fun shouldShowReaderViewCustomization(): Boolean = selectedSession?.let {
|
||||
store.state.findTab(it.id)?.readerState?.active
|
||||
} ?: false
|
||||
|
||||
/**
|
||||
* Should Translations menu item be visible?
|
||||
*/
|
||||
@VisibleForTesting(otherwise = PRIVATE)
|
||||
fun shouldShowTranslations(): Boolean = selectedSession?.let {
|
||||
context.settings().enableTranslations
|
||||
} ?: false
|
||||
// End of predicates //
|
||||
|
||||
private val installToHomescreen = BrowserMenuHighlightableItem(
|
||||
|
@ -248,6 +255,14 @@ open class DefaultToolbarMenu(
|
|||
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(
|
||||
imageResource = R.drawable.ic_desktop,
|
||||
label = context.getString(R.string.browser_menu_desktop_site),
|
||||
|
@ -405,6 +420,7 @@ open class DefaultToolbarMenu(
|
|||
syncMenuItem(),
|
||||
BrowserMenuDivider(),
|
||||
findInPageItem,
|
||||
translationsItem.apply { visible = ::shouldShowTranslations },
|
||||
desktopSiteItem,
|
||||
openInRegularTabItem.apply { visible = ::shouldShowOpenInRegularTab },
|
||||
customizeReaderView.apply { visible = ::shouldShowReaderViewCustomization },
|
||||
|
|
|
@ -76,7 +76,6 @@ abstract class ToolbarIntegration(
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
class DefaultToolbarIntegration(
|
||||
context: Context,
|
||||
toolbar: BrowserToolbar,
|
||||
|
|
|
@ -18,6 +18,11 @@ interface ToolbarMenu {
|
|||
*/
|
||||
object OpenInRegularTab : Item()
|
||||
object FindInPage : Item()
|
||||
|
||||
/**
|
||||
* Opens the translations flow.
|
||||
*/
|
||||
object Translate : Item()
|
||||
object Share : Item()
|
||||
data class Back(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].
|
||||
*/
|
||||
@Composable
|
||||
@Suppress("LongParameterList")
|
||||
fun Image(
|
||||
url: String,
|
||||
modifier: Modifier = Modifier,
|
||||
|
|
|
@ -7,10 +7,22 @@ package org.mozilla.fenix.compose
|
|||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.compose.foundation.background
|
||||
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.material.Card
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextButton
|
||||
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.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.semantics
|
||||
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.TextDecoration
|
||||
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
|
||||
|
||||
/**
|
||||
|
@ -67,6 +82,12 @@ fun LinkText(
|
|||
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
|
||||
// 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
|
||||
|
@ -76,12 +97,17 @@ fun LinkText(
|
|||
style = style,
|
||||
modifier = Modifier.semantics(mergeDescendants = true) {
|
||||
onClick {
|
||||
linkTextStates.firstOrNull()?.let {
|
||||
it.onClick(it.url)
|
||||
if (linkTextStates.size > 1) {
|
||||
showDialog.value = true
|
||||
} else {
|
||||
linkTextStates.firstOrNull()?.let {
|
||||
it.onClick(it.url)
|
||||
}
|
||||
}
|
||||
|
||||
return@onClick true
|
||||
}
|
||||
contentDescription = "$annotatedString $linksAvailable"
|
||||
},
|
||||
onClick = { charOffset ->
|
||||
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
|
||||
internal fun onTextClick(
|
||||
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.
|
||||
*
|
||||
* @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 onCheckedChange Invoked when Switch is being clicked, therefore the change of checked
|
||||
* state is requested.
|
||||
|
@ -44,6 +45,7 @@ private const val DISABLED_ALPHA = 0.5f
|
|||
@Composable
|
||||
fun SwitchWithLabel(
|
||||
label: String,
|
||||
description: String? = null,
|
||||
checked: Boolean,
|
||||
onCheckedChange: ((Boolean) -> Unit),
|
||||
modifier: Modifier = Modifier,
|
||||
|
@ -60,16 +62,28 @@ fun SwitchWithLabel(
|
|||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = label,
|
||||
color = if (enabled) {
|
||||
FirefoxTheme.colors.textPrimary
|
||||
} else {
|
||||
FirefoxTheme.colors.textDisabled
|
||||
},
|
||||
style = FirefoxTheme.typography.subtitle1,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f),
|
||||
) {
|
||||
Text(
|
||||
text = label,
|
||||
color = if (enabled) {
|
||||
FirefoxTheme.colors.textPrimary
|
||||
} else {
|
||||
FirefoxTheme.colors.textDisabled
|
||||
},
|
||||
style = FirefoxTheme.typography.subtitle1,
|
||||
)
|
||||
|
||||
description?.let {
|
||||
Text(
|
||||
text = description,
|
||||
color = FirefoxTheme.colors.textSecondary,
|
||||
style = FirefoxTheme.typography.body2,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Switch(
|
||||
modifier = Modifier.clearAndSetSemantics {},
|
||||
|
@ -139,6 +153,7 @@ private fun SwitchWithLabelPreview() {
|
|||
var enabledSwitchState by remember { mutableStateOf(false) }
|
||||
SwitchWithLabel(
|
||||
label = if (enabledSwitchState) "On" else "Off",
|
||||
description = "Description text",
|
||||
checked = enabledSwitchState,
|
||||
onCheckedChange = { enabledSwitchState = it },
|
||||
)
|
||||
|
|
|
@ -42,7 +42,6 @@ private const val FALLBACK_ICON_SIZE = 36
|
|||
* @param alignment [Alignment] used to draw the image content.
|
||||
*/
|
||||
@Composable
|
||||
@Suppress("LongParameterList")
|
||||
fun TabThumbnail(
|
||||
tab: TabSessionState,
|
||||
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.
|
||||
*/
|
||||
@Composable
|
||||
@Suppress("LongParameterList")
|
||||
fun ThumbnailImage(
|
||||
request: ImageLoadRequest,
|
||||
storage: ThumbnailStorage,
|
||||
|
|
|
@ -38,7 +38,6 @@ import org.mozilla.fenix.theme.FirefoxTheme
|
|||
* @param onClick Optional lambda for handling header clicks.
|
||||
* @param actions Optional Composable for adding UI to the end of the header.
|
||||
*/
|
||||
@Suppress("LongParameterList")
|
||||
@Composable
|
||||
fun ExpandableListHeader(
|
||||
headerText: String,
|
||||
|
|
|
@ -91,7 +91,7 @@ import org.mozilla.fenix.theme.FirefoxTheme
|
|||
*/
|
||||
@OptIn(ExperimentalMaterialApi::class, ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
@Suppress("MagicNumber", "LongParameterList", "LongMethod")
|
||||
@Suppress("MagicNumber", "LongMethod")
|
||||
fun TabGridItem(
|
||||
tab: TabSessionState,
|
||||
storage: ThumbnailStorage,
|
||||
|
|
|
@ -75,7 +75,7 @@ import org.mozilla.fenix.theme.FirefoxTheme
|
|||
*/
|
||||
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
@Suppress("MagicNumber", "LongMethod", "LongParameterList")
|
||||
@Suppress("MagicNumber", "LongMethod")
|
||||
fun TabListItem(
|
||||
tab: TabSessionState,
|
||||
storage: ThumbnailStorage,
|
||||
|
@ -209,7 +209,6 @@ private fun clickableColor() = when (isSystemInDarkTheme()) {
|
|||
}
|
||||
|
||||
@Composable
|
||||
@Suppress("LongParameterList")
|
||||
private fun Thumbnail(
|
||||
tab: TabSessionState,
|
||||
size: Int,
|
||||
|
|
|
@ -5,24 +5,16 @@
|
|||
package org.mozilla.fenix.customtabs
|
||||
|
||||
import android.app.assist.AssistContent
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.navigation.NavDestination
|
||||
import androidx.navigation.NavDirections
|
||||
import mozilla.components.browser.state.selector.findCustomTab
|
||||
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 org.mozilla.fenix.BrowserDirection
|
||||
import org.mozilla.fenix.HomeActivity
|
||||
import org.mozilla.fenix.NavGraphDirections
|
||||
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"
|
||||
|
||||
|
@ -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() {
|
||||
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.WebAppHideToolbarFeature
|
||||
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.ktx.android.arch.lifecycle.addObservers
|
||||
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.
|
||||
*/
|
||||
class ExternalAppBrowserFragment : BaseBrowserFragment(), UserInteractionHandler {
|
||||
class ExternalAppBrowserFragment : BaseBrowserFragment() {
|
||||
|
||||
private val args by navArgs<ExternalAppBrowserFragmentArgs>()
|
||||
|
||||
|
@ -212,9 +211,4 @@ class ExternalAppBrowserFragment : BaseBrowserFragment(), UserInteractionHandler
|
|||
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
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
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.SnackbarHost
|
||||
import androidx.compose.material.SnackbarHostState
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.rememberDrawerState
|
||||
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.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.snapshotFlow
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
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.compose.annotation.LightDarkPreview
|
||||
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
|
||||
|
||||
/**
|
||||
* 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
|
||||
fun DebugOverlay() {
|
||||
fun DebugOverlay(
|
||||
navController: NavHostController,
|
||||
drawerStatus: DrawerStatus,
|
||||
debugDrawerDestinations: List<DebugDrawerDestination>,
|
||||
onDrawerOpen: () -> Unit,
|
||||
onDrawerClose: () -> Unit,
|
||||
onDrawerBackButtonClick: () -> Unit,
|
||||
) {
|
||||
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(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
|
@ -41,12 +86,41 @@ fun DebugOverlay() {
|
|||
.align(Alignment.CenterStart)
|
||||
.padding(start = 16.dp),
|
||||
onClick = {
|
||||
scope.launch {
|
||||
snackbarState.showSnackbar("Show debug drawer")
|
||||
}
|
||||
onDrawerOpen()
|
||||
},
|
||||
)
|
||||
|
||||
// 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
|
||||
SnackbarHost(
|
||||
hostState = snackbarState,
|
||||
|
@ -62,13 +136,41 @@ fun DebugOverlay() {
|
|||
@Composable
|
||||
@LightDarkPreview
|
||||
private fun DebugOverlayPreview() {
|
||||
FirefoxTheme {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(color = FirefoxTheme.colors.layer1),
|
||||
) {
|
||||
DebugOverlay()
|
||||
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 {
|
||||
DebugOverlay(
|
||||
navController = navController,
|
||||
drawerStatus = drawerStatus,
|
||||
debugDrawerDestinations = destinations,
|
||||
onDrawerOpen = {
|
||||
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 = {
|
||||
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) {
|
||||
|
|
|
@ -15,12 +15,47 @@ import androidx.annotation.DrawableRes
|
|||
import androidx.annotation.RequiresApi
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.navigation.NavDestination
|
||||
import androidx.navigation.NavDirections
|
||||
import mozilla.components.concept.base.crash.Breadcrumb
|
||||
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.HomeActivity
|
||||
import org.mozilla.fenix.NavGraphDirections
|
||||
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.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.
|
||||
|
@ -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 SETTINGS_SELECT_OPTION_KEY = ":settings:fragment_args_key"
|
||||
const val SETTINGS_SHOW_FRAGMENT_ARGS = ":settings:show_fragment_args"
|
||||
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.ext.components
|
||||
import org.mozilla.fenix.ext.settings
|
||||
import org.mozilla.fenix.nimbus.FxNimbus
|
||||
import org.mozilla.geckoview.ContentBlocking
|
||||
import org.mozilla.geckoview.ContentBlocking.SafeBrowsingProvider
|
||||
import org.mozilla.geckoview.GeckoRuntime
|
||||
|
@ -134,7 +133,7 @@ object GeckoProvider {
|
|||
.consoleOutput(context.components.settings.enableGeckoLogs)
|
||||
.debugLogging(Config.channel.isDebug || context.components.settings.enableGeckoLogs)
|
||||
.aboutConfigEnabled(true)
|
||||
.extensionsProcessEnabled(FxNimbus.features.extensionsProcess.value().enabled)
|
||||
.extensionsProcessEnabled(true)
|
||||
.extensionsWebAPIEnabled(true)
|
||||
.build()
|
||||
}
|
||||
|
|
|
@ -49,7 +49,9 @@ import kotlinx.coroutines.Dispatchers.Main
|
|||
import kotlinx.coroutines.MainScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import mozilla.components.browser.state.selector.findTab
|
||||
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.nimbus.FxNimbus
|
||||
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.SearchSelectorMenu
|
||||
import org.mozilla.fenix.tabstray.TabsTrayAccessPoint
|
||||
|
@ -1007,17 +1008,18 @@ class HomeFragment : Fragment() {
|
|||
lastAppliedWallpaperName = wallpaperName
|
||||
}
|
||||
else -> {
|
||||
runBlockingIncrement {
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
// loadBitmap does file lookups based on name, so we don't need a fully
|
||||
// qualified type to load the image
|
||||
val wallpaper = Wallpaper.Default.copy(name = wallpaperName)
|
||||
val wallpaperImage =
|
||||
requireComponents.useCases.wallpaperUseCases.loadBitmap(wallpaper)
|
||||
context?.let { requireComponents.useCases.wallpaperUseCases.loadBitmap(it, wallpaper) }
|
||||
wallpaperImage?.let {
|
||||
it.scaleToBottomOfView(binding.wallpaperImageView)
|
||||
binding.wallpaperImageView.isVisible = true
|
||||
lastAppliedWallpaperName = wallpaperName
|
||||
} ?: run {
|
||||
if (!isActive) return@run
|
||||
with(binding.wallpaperImageView) {
|
||||
isVisible = false
|
||||
showSnackBar(
|
||||
|
@ -1051,11 +1053,15 @@ class HomeFragment : Fragment() {
|
|||
}
|
||||
|
||||
private fun observeWallpaperUpdates() {
|
||||
consumeFrom(requireComponents.appStore) {
|
||||
val currentWallpaper = it.wallpaperState.currentWallpaper
|
||||
if (currentWallpaper.name != lastAppliedWallpaperName) {
|
||||
applyWallpaper(wallpaperName = currentWallpaper.name, orientationChange = false)
|
||||
}
|
||||
consumeFlow(requireComponents.appStore, viewLifecycleOwner) { flow ->
|
||||
flow.filter { it.mode == BrowsingMode.Normal }
|
||||
.map { it.wallpaperState.currentWallpaper }
|
||||
.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.
|
||||
* @param fxaEntrypoint The source entry point to FxA.
|
||||
*/
|
||||
@Suppress("LongParameterList")
|
||||
class HomeMenuView(
|
||||
private val view: View,
|
||||
private val context: Context,
|
||||
|
|
|
@ -239,7 +239,7 @@ fun PocketSponsoredStory(
|
|||
* @param onDiscoverMoreClicked Callback for when the user taps an element which contains an
|
||||
*/
|
||||
@OptIn(ExperimentalComposeUiApi::class)
|
||||
@Suppress("LongParameterList", "LongMethod")
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
fun PocketStories(
|
||||
@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.
|
||||
*/
|
||||
@OptIn(ExperimentalComposeUiApi::class)
|
||||
@Suppress("LongParameterList")
|
||||
@Composable
|
||||
fun PocketStoriesCategories(
|
||||
categories: List<PocketRecommendedStoriesCategory>,
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
package org.mozilla.fenix.home.recentbookmarks.view
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
|
@ -42,10 +41,10 @@ import androidx.compose.ui.text.style.TextOverflow
|
|||
import androidx.compose.ui.unit.dp
|
||||
import mozilla.components.browser.icons.compose.Loader
|
||||
import mozilla.components.browser.icons.compose.Placeholder
|
||||
import mozilla.components.browser.icons.compose.WithIcon
|
||||
import mozilla.components.ui.colors.PhotonColors
|
||||
import org.mozilla.fenix.components.components
|
||||
import org.mozilla.fenix.compose.ContextualMenu
|
||||
import org.mozilla.fenix.compose.Favicon
|
||||
import org.mozilla.fenix.compose.Image
|
||||
import org.mozilla.fenix.compose.MenuItem
|
||||
import org.mozilla.fenix.compose.annotation.LightDarkPreview
|
||||
|
@ -172,6 +171,11 @@ private fun RecentBookmarkImage(bookmark: RecentBookmark) {
|
|||
modifier = imageModifier,
|
||||
targetSize = imageWidth,
|
||||
contentScale = ContentScale.Crop,
|
||||
fallback = {
|
||||
if (!bookmark.url.isNullOrEmpty()) {
|
||||
FallbackBookmarkFaviconImage(url = bookmark.url)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
!bookmark.url.isNullOrEmpty() && !inComposePreview -> {
|
||||
|
@ -180,23 +184,7 @@ private fun RecentBookmarkImage(bookmark: RecentBookmark) {
|
|||
PlaceholderBookmarkImage()
|
||||
}
|
||||
|
||||
WithIcon { icon ->
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
FallbackBookmarkFaviconImage(bookmark.url)
|
||||
}
|
||||
}
|
||||
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
|
||||
@LightDarkPreview
|
||||
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.
|
||||
*/
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Suppress("LongMethod", "LongParameterList")
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
fun RecentSyncedTab(
|
||||
tab: RecentSyncedTab?,
|
||||
|
|
|
@ -240,6 +240,15 @@ fun RecentTabImage(
|
|||
modifier = modifier,
|
||||
targetSize = THUMBNAIL_SIZE.dp,
|
||||
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(
|
||||
|
|
|
@ -190,7 +190,6 @@ class AdapterItemDiffCallback : DiffUtil.ItemCallback<AdapterItem>() {
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
class SessionControlAdapter(
|
||||
private val interactor: SessionControlInteractor,
|
||||
private val viewLifecycleOwner: LifecycleOwner,
|
||||
|
|
|
@ -239,7 +239,7 @@ data class TopSiteColors(
|
|||
* @param onTopSiteLongClick Invoked when the user long clicks on a top site.
|
||||
* @param onTopSitesItemBound Invoked during the composition of a top site item.
|
||||
*/
|
||||
@Suppress("LongParameterList", "LongMethod")
|
||||
@Suppress("LongMethod")
|
||||
@OptIn(ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class)
|
||||
@Composable
|
||||
private fun TopSiteItem(
|
||||
|
@ -401,7 +401,6 @@ private fun TopSiteFavicon(url: String, imageUrl: String? = null) {
|
|||
}
|
||||
|
||||
@Composable
|
||||
@Suppress("LongParameterList")
|
||||
private fun getMenuItems(
|
||||
topSite: TopSite,
|
||||
onOpenInPrivateTabClicked: (topSite: TopSite) -> Unit,
|
||||
|
|