diff --git a/.github/workflows/sync-strings.yml b/.github/workflows/sync-strings.yml
new file mode 100644
index 000000000..35e1c5544
--- /dev/null
+++ b/.github/workflows/sync-strings.yml
@@ -0,0 +1,43 @@
+# 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/
+
+name: "Sync Strings"
+
+on:
+ schedule:
+ - cron: '0 */4 * * *'
+
+jobs:
+ main:
+ name: "Sync Strings"
+ runs-on: ubuntu-20.04
+ steps:
+ - name: "Discover Fenix Beta Version"
+ id: fenix-beta-version
+ uses: mozilla-mobile/fenix-beta-version@1.0.0
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ - name: "Checkout Master Branch"
+ uses: actions/checkout@v2
+ with:
+ path: main
+ ref: master
+ - name: "Checkout Beta Branch"
+ uses: actions/checkout@v2
+ with:
+ path: beta
+ ref: "releases_v${{ steps.fenix-beta-version.outputs.fenix-beta-version }}.0.0"
+ - name: "Sync Strings"
+ uses: mozilla-mobile/sync-strings-action@1.0.1
+ with:
+ src: main
+ dst: beta
+ - name: Create Pull Request
+ uses: peter-evans/create-pull-request@v3
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+ path: beta
+ branch: automation/sync-strings-${{ steps.fenix-beta-version.outputs.major-beta-version }}
+ title: "Sync Strings from master to releases_${{steps.fenix-beta-version.outputs.fenix-beta-version}}.0"
+ body: "This (automated) PR syncs strings from `master` to `releases_${{steps.fenix-beta-version.outputs.fenix-beta-version}}.0.0`"
diff --git a/.gitignore b/.gitignore
index 67abe8cc2..cd0e1c137 100644
--- a/.gitignore
+++ b/.gitignore
@@ -80,7 +80,6 @@ gen-external-apklibs
.DS_Store
# Secrets files, e.g. tokens
-.leanplum_token
.adjust_token
.sentry_token
.mls_token
diff --git a/app/build.gradle b/app/build.gradle
index 635073f9a..5f5711313 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -255,7 +255,7 @@ android {
// instead. :)
maxParallelForks = 2
forkEvery = 80
- maxHeapSize = "2048m"
+ maxHeapSize = "3072m"
minHeapSize = "1024m"
}
}
@@ -362,25 +362,6 @@ android.applicationVariants.all { variant ->
println("--")
}
-// -------------------------------------------------------------------------------------------------
-// Leanplum: Read token from local file if it exists
-// -------------------------------------------------------------------------------------------------
-
- print("Leanplum token: ")
-
- try {
- def parts = new File("${rootDir}/.leanplum_token").text.trim().split(":")
- def id = parts[0]
- def key = parts[1]
- buildConfigField 'String', 'LEANPLUM_ID', '"' + id + '"'
- buildConfigField 'String', 'LEANPLUM_TOKEN', '"' + key + '"'
- println "(Added from .leanplum_token file)"
- } catch (FileNotFoundException ignored) {
- buildConfigField 'String', 'LEANPLUM_ID', 'null'
- buildConfigField 'String', 'LEANPLUM_TOKEN', 'null'
- println("X_X")
- }
-
// -------------------------------------------------------------------------------------------------
// MLS: Read token from local file if it exists
// -------------------------------------------------------------------------------------------------
@@ -483,7 +464,6 @@ dependencies {
implementation Deps.sentry
-
implementation Deps.mozilla_concept_base
implementation Deps.mozilla_concept_engine
implementation Deps.mozilla_concept_menu
diff --git a/app/metrics.yaml b/app/metrics.yaml
index 2e311c746..59f89331d 100644
--- a/app/metrics.yaml
+++ b/app/metrics.yaml
@@ -2,7 +2,7 @@
# 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/.
---
-$schema: moz://mozilla.org/schemas/glean/metrics/1-0-0
+$schema: moz://mozilla.org/schemas/glean/metrics/2-0-0
no_lint:
- CATEGORY_GENERIC
@@ -11,6 +11,12 @@ events:
app_opened_all_startup:
type: event
description: |
+ **This probe has a known flaw:** for COLD start up, it doesn't take into
+ account if the process is already running when the app starts, possibly
+ inflating results (e.g. a Service started the process 20min ago and only
+ now is HomeActivity launching). See the `cold_*_app_to_first_frame` probes
+ for a replacement.
+
A user opened the app to the HomeActivity. The HomeActivity
encompasses the home screen, browser screen, settings screen,
collections and other screens in the nav_graph.
@@ -217,6 +223,32 @@ events:
notification_emails:
- fenix-core@mozilla.com
expires: "2021-07-01"
+ default_browser_changed:
+ type: event
+ description: |
+ Indicates the default browser was changed
+ bugs:
+ - https://github.com/mozilla-mobile/fenix/issues/18857
+ data_reviews:
+ - https://github.com/mozilla-mobile/fenix/pull/18895
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - fenix-core@mozilla.com
+ expires: "2021-10-01"
+ toolbar_menu_visible:
+ type: event
+ description: |
+ The browser menu was displayed from toolbar menu
+ bugs:
+ - https://github.com/mozilla-mobile/fenix/issues/18855
+ data_reviews:
+ - https://github.com/mozilla-mobile/fenix/pull/18895
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - fenix-core@mozilla.com
+ expires: "2021-10-01"
total_uri_count:
type: counter
description: |
@@ -555,6 +587,20 @@ search_shortcuts:
- fenix-core@mozilla.com
expires: "2021-08-01"
+experiments_default_browser:
+ toolbar_menu_clicked:
+ type: event
+ description: |
+ Set default browser was clicked from toolbar menu
+ bugs:
+ - https://github.com/mozilla-mobile/fenix/issues/18851
+ data_reviews:
+ - https://github.com/mozilla-mobile/fenix/pull/18895
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - fenix-core@mozilla.com
+ expires: "2021-10-01"
toolbar_settings:
changed_position:
type: event
@@ -1187,6 +1233,40 @@ metrics:
notification_emails:
- fenix-core@mozilla.com
expires: "2021-08-01"
+ start_reason_process_error:
+ type: boolean
+ description: |
+ The `AppStartReasonProvider.ProcessLifecycleObserver.onCreate` was
+ unexpectedly called twice. We can use this metric to validate our
+ assumptions about how these APIs are called. This probe can be removed
+ once we validate these assumptions.
+ bugs:
+ - https://github.com/mozilla-mobile/fenix/issues/18426
+ data_reviews:
+ - https://github.com/mozilla-mobile/fenix/pull/18632#issue-600193452
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - perf-android-fe@mozilla.com
+ - mcomella@mozilla.com
+ expires: "2021-08-11"
+ start_reason_activity_error:
+ type: boolean
+ description: |
+ The `AppStartReasonProvider.ActivityLifecycleCallbacks.onActivityCreated`
+ was unexpectedly called twice. We can use this metric to validate our
+ assumptions about how these APIs are called. This probe can be removed
+ once we validate these assumptions.
+ bugs:
+ - https://github.com/mozilla-mobile/fenix/issues/18426
+ data_reviews:
+ - https://github.com/mozilla-mobile/fenix/pull/18632#issue-600193452
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - perf-android-fe@mozilla.com
+ - mcomella@mozilla.com
+ expires: "2021-08-11"
preferences:
show_search_suggestions:
@@ -2433,6 +2513,19 @@ tabs_tray:
notification_emails:
- fenix-core@mozilla.com
expires: "2021-08-01"
+ synced_mode_tapped:
+ type: event
+ description: |
+ A user switched to synced mode
+ bugs:
+ - https://github.com/mozilla-mobile/fenix/issues/18948
+ data_reviews:
+ - https://github.com/mozilla-mobile/fenix/pull/19004
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - fenix-core@mozilla.com
+ expires: "2022-08-01"
new_tab_tapped:
type: event
description: |
@@ -3938,6 +4031,23 @@ addons:
notification_emails:
- fenix-core@mozilla.com
expires: "2021-07-01"
+ open_addon_setting:
+ type: event
+ description: |
+ A user opened an add-on's setting
+ extra_keys:
+ addon_id:
+ description: |
+ The id of the add-on that was interacted with
+ bugs:
+ - https://github.com/mozilla-mobile/fenix/issues/17644
+ data_reviews:
+ - https://github.com/mozilla-mobile/fenix/pull/18504
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - fenix-core@mozilla.com
+ expires: "2022-08-01"
has_installed_addons:
type: boolean
description: |
@@ -4126,6 +4236,95 @@ startup.timeline:
expires: "2021-08-01"
perf.startup:
+ cold_main_app_to_first_frame:
+ type: timing_distribution
+ time_unit: millisecond
+ description: |
+ The duration from `*Application`'s initializer to the first Android frame
+ being drawn in a [COLD MAIN start
+ up](https://wiki.mozilla.org/index.php?title=Performance/Fenix/Glossary).
+ Notably, this duration omits the time from process start to the
+ initializer (which includes a lengthy dex operation) and the time from
+ the first frame to visual completeness. This probe doesn't measure Custom
+ Tabs or other uses of `ExternalAppBrowserActivity` to simplify result
+ analysis. The methodology for determining this measurement is imperfect
+ to simplify implementation. Issues may include:
+ - Not measuring Beta and Release channels (due to
+ `MigrationDecisionActivity` interrupting the logic).
+ - Not distinguishing between MAIN to homescreen, onboarding, session
+ restore, others?
+ - Not choosing to record a MAIN based on what the user would see and
+ thus the core code path (i.e. the thing we want to measure) but rather on
+ the initial `Intent` state.
+
+ The hope is that these cases will not have a significant impact on the end
+ results but, if they appear to, we can replace it with a more complex
+ implementation.
+
+ Around April 8, 2021 the implementation was refactored. Functionally, it
+ should be the same but it's noted just in case there are bugs.
+ bugs:
+ - https://github.com/mozilla-mobile/fenix/issues/18426
+ data_reviews:
+ - https://github.com/mozilla-mobile/fenix/pull/18632#issue-600193452
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - perf-android-fe@mozilla.com
+ - mcomella@mozilla.com
+ expires: "2021-08-11"
+ cold_view_app_to_first_frame:
+ type: timing_distribution
+ time_unit: millisecond
+ description: |
+ The duration from `*Application`'s initializer to the first Android frame
+ being drawn in a [COLD VIEW start
+ up](https://wiki.mozilla.org/index.php?title=Performance/Fenix/Glossary).
+ The methodology for determining this measurement is imperfect to simplify
+ implementation. Issues may include:
+ -Including VIEW intents that aren't valid so take code paths similar
+ to MAIN (this is speculative)
+
+ See the `cold_main_app_to_first_frame` probe docs for other possible
+ known issues and more details.
+
+ Around April 8, 2021 the implementation was refactored. Functionally, it
+ should be the same but it's noted just in case there are bugs.
+ bugs:
+ - https://github.com/mozilla-mobile/fenix/issues/18426
+ data_reviews:
+ - https://github.com/mozilla-mobile/fenix/pull/18632#issue-600193452
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - perf-android-fe@mozilla.com
+ - mcomella@mozilla.com
+ expires: "2021-08-11"
+ cold_unknwn_app_to_first_frame:
+ type: timing_distribution
+ time_unit: millisecond
+ description: |
+ The duration from `*Application`'s initializer to the first Android frame
+ being drawn in a [COLD start
+ up](https://wiki.mozilla.org/index.php?title=Performance/Fenix/Glossary)
+ where we can't say it was a MAIN or VIEW start up. The methodology for
+ determining this measurement is imperfect to simplify implementation.
+
+ See the `cold_main_app_to_first_frame` probe docs for known issues and
+ more details.
+
+ Around April 8, 2021 the implementation was refactored. Functionally, it
+ should be the same but it's noted just in case there are bugs.
+ bugs:
+ - https://github.com/mozilla-mobile/fenix/issues/18426
+ data_reviews:
+ - https://github.com/mozilla-mobile/fenix/pull/18632#issue-600193452
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - perf-android-fe@mozilla.com
+ - mcomella@mozilla.com
+ expires: "2021-08-11"
application_on_create:
type: timing_distribution
time_unit: millisecond
@@ -4293,6 +4492,81 @@ perf.startup:
- perf-android-fe@mozilla.com
- mcomella@mozilla.com
expires: "2021-08-11"
+ startup_type:
+ type: labeled_counter
+ description: |
+ Indicates how the browser was started. The label is divided into two
+ variables. `state` is how cached the browser is when started. `path` is
+ what code path we are expected to take. Together, they create a combined
+ label: `state_path`. For brevity, the specific states are documented in
+ the [Fenix perf
+ glossary](https://wiki.mozilla.org/index.php?title=Performance/Fenix/Glossary).
+
+ This implementation is intended to be simple, not comprehensive. We list
+ the implications below.
+
+
+ These ways of opening the app undesirably adds events to our primary
+ buckets (non-`unknown` cases):
+ - App switcher cold/warm: `cold/warm_` + duplicates path from
+ previous launch
+ - Home screen shortcuts: `*_view`
+ - An Intent is sent internally that's uses `ACTION_MAIN` or
+ `ACTION_VIEW` could be: `*_main/view` (unknown if this ever happens)
+ - A command-line launch uses `ACTION_MAIN` or `ACTION_VIEW` could be:
+ `*_main/view`
+
+
+ These ways of opening the app undesirably do not add their events to our
+ primary buckets:
+ - Close and reopen the app very quickly: no event is recorded.
+
+
+ These ways of opening the app don't affect our primary buckets:
+ - App switcher hot: `hot_unknown`
+ - PWA (all states): `unknown_unknown`
+ - Custom tab: `unknown_view`
+ - Cold start where a service or other non-activity starts the process
+ (not manually tested) - this seems to happen if you have the homescreen
+ widget: `unknown_*`
+ - Another activity is drawn before HomeActivity (e.g. widget voice
+ search): `unknown_*`
+ - Widget text search: `*_unknown`
+
+
+ In addition to the events above, the `unknown` state may be chosen when we
+ were unable to determine a cause due to implementation details or the API
+ was used incorrectly. We may be able to record the events listed above
+ into different buckets but we kept the implementation simple for now.
+
+ N.B.: for implementation simplicity, we duplicate the logic in app that
+ determines `path` so it's not perfectly accurate. In one way, we record we
+ is intended to happen rather than what actually happened (e.g. the user
+ may click a link so we record VIEW but the app does a MAIN by going to the
+ homescreen because the link was invalid).
+ labels:
+ - cold_main
+ - cold_view
+ - cold_unknown
+ - warm_main
+ - warm_view
+ - warm_unknown
+ - hot_main
+ - hot_view
+ - hot_unknown
+ - unknown_main
+ - unknown_view
+ - unknown_unknown
+ bugs:
+ - https://github.com/mozilla-mobile/fenix/issues/18836
+ data_reviews:
+ - https://github.com/mozilla-mobile/fenix/pull/19028
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - perf-android-fe@mozilla.com
+ - mcomella@mozilla.com
+ expires: "2021-10-09"
perf.awesomebar:
history_suggestions:
@@ -4312,7 +4586,7 @@ perf.awesomebar:
notification_emails:
- fenix-core@mozilla.com
- gkruglov@mozilla.com
- expires: "2020-11-15"
+ expires: "2021-11-15"
bookmark_suggestions:
send_in_pings:
- metrics
@@ -4330,7 +4604,7 @@ perf.awesomebar:
notification_emails:
- fenix-core@mozilla.com
- gkruglov@mozilla.com
- expires: "2020-11-15"
+ expires: "2021-11-15"
search_engine_suggestions:
send_in_pings:
- metrics
@@ -4348,7 +4622,7 @@ perf.awesomebar:
notification_emails:
- fenix-core@mozilla.com
- gkruglov@mozilla.com
- expires: "2020-11-15"
+ expires: "2021-11-15"
session_suggestions:
send_in_pings:
- metrics
@@ -4366,7 +4640,7 @@ perf.awesomebar:
notification_emails:
- fenix-core@mozilla.com
- gkruglov@mozilla.com
- expires: "2020-11-15"
+ expires: "2021-11-15"
synced_tabs_suggestions:
send_in_pings:
- metrics
@@ -4384,7 +4658,7 @@ perf.awesomebar:
notification_emails:
- fenix-core@mozilla.com
- gkruglov@mozilla.com
- expires: "2020-11-15"
+ expires: "2021-11-15"
clipboard_suggestions:
send_in_pings:
- metrics
@@ -4402,7 +4676,7 @@ perf.awesomebar:
notification_emails:
- fenix-core@mozilla.com
- gkruglov@mozilla.com
- expires: "2020-11-15"
+ expires: "2021-11-15"
shortcuts_suggestions:
send_in_pings:
- metrics
@@ -4420,7 +4694,7 @@ perf.awesomebar:
notification_emails:
- fenix-core@mozilla.com
- gkruglov@mozilla.com
- expires: "2020-11-15"
+ expires: "2021-11-15"
autoplay:
visited_setting:
@@ -4811,6 +5085,46 @@ engine_tab:
- fenix-core@mozilla.com
- skaspari@mozilla.com
expires: "2021-12-31"
+ foreground_metrics:
+ type: event
+ description: |
+ Event collecting data about the state of tabs when the app comes back to
+ the foreground.
+ bugs:
+ - https://github.com/mozilla-mobile/android-components/issues/9997
+ data_reviews:
+ - https://github.com/mozilla-mobile/fenix/pull/18747#issuecomment-815731764
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - fenix-core@mozilla.com
+ - skaspari@mozilla.com
+ expires: "2021-12-31"
+ extra_keys:
+ background_active_tabs:
+ description: |
+ Number of active tabs (with an engine session assigned) when the app
+ went to the background.
+ background_crashed_tabs:
+ description: |
+ Number of tabs marked as crashed when the app went to the background.
+ background_total_tabs:
+ description: |
+ Number of total tabs when the app went to the background.
+ foreground_active_tabs:
+ description: |
+ Number of active tabs (with an engine session assigned) when the
+ app came back to the foreground.
+ foreground_crashed_tabs:
+ description: |
+ Number of tabs marked as crashed when the app came back to the
+ foreground.
+ foreground_total_tabs:
+ description: |
+ Number of total tabs when the app came back to the foreground.
+ time_in_background:
+ description: |
+ Time (in milliseconds) the app was in the background.
synced_tabs:
synced_tabs_suggestion_clicked:
@@ -4907,6 +5221,34 @@ awesomebar:
- fenix-core@mozilla.com
expires: "2021-08-01"
+home_menu:
+ settings_item_clicked:
+ type: event
+ description: The user clicked the settings option in home menu.
+ bugs:
+ - https://github.com/mozilla-mobile/fenix/issues/18856
+ data_reviews:
+ - https://github.com/mozilla-mobile/fenix/pull/18987
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - fenix-core@mozilla.com
+ expires: "2021-10-01"
+
+home_screen:
+ home_screen_displayed:
+ type: event
+ description: The user clicked the settings option in home menu.
+ bugs:
+ - https://github.com/mozilla-mobile/fenix/issues/18856
+ data_reviews:
+ - https://github.com/mozilla-mobile/fenix/pull/19025
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - fenix-core@mozilla.com
+ expires: "2021-10-01"
+
android_keystore_experiment:
experiment_failure:
type: event
@@ -5007,3 +5349,46 @@ android_keystore_experiment:
notification_emails:
- fenix-core@mozilla.com
expires: "2021-09-01"
+
+set_default_newtab_experiment:
+ set_default_browser_clicked:
+ type: event
+ description: |
+ Set default browser was clicked from new tab screen.
+ bugs:
+ - https://github.com/mozilla-mobile/fenix/issues/18853
+ data_reviews:
+ - https://github.com/mozilla-mobile/fenix/pull/18895
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - fenix-core@mozilla.com
+ expires: "2021-10-01"
+ close_experiment_card_clicked:
+ type: event
+ description: |
+ Close experiment card was clicked from new tab screen.
+ bugs:
+ - https://github.com/mozilla-mobile/fenix/issues/18853
+ data_reviews:
+ - https://github.com/mozilla-mobile/fenix/pull/18895
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - fenix-core@mozilla.com
+ expires: "2021-10-01"
+
+set_default_setting_experiment:
+ set_default_browser_clicked:
+ type: event
+ description: |
+ Set default browser was clicked from settings screen.
+ bugs:
+ - https://github.com/mozilla-mobile/fenix/issues/18852
+ data_reviews:
+ - https://github.com/mozilla-mobile/fenix/pull/19047
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - fenix-core@mozilla.com
+ expires: "2021-10-01"
diff --git a/app/pings.yaml b/app/pings.yaml
index bd726ff12..d244af9f0 100644
--- a/app/pings.yaml
+++ b/app/pings.yaml
@@ -2,7 +2,7 @@
# 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/.
---
-$schema: moz://mozilla.org/schemas/glean/pings/1-0-0
+$schema: moz://mozilla.org/schemas/glean/pings/2-0-0
activation:
description: |
diff --git a/app/src/androidTest/java/org/mozilla/fenix/perf/StartupExcessiveResourceUseTest.kt b/app/src/androidTest/java/org/mozilla/fenix/perf/StartupExcessiveResourceUseTest.kt
index 3575a7101..b40ad62b1 100644
--- a/app/src/androidTest/java/org/mozilla/fenix/perf/StartupExcessiveResourceUseTest.kt
+++ b/app/src/androidTest/java/org/mozilla/fenix/perf/StartupExcessiveResourceUseTest.kt
@@ -21,7 +21,7 @@ import org.mozilla.fenix.helpers.HomeActivityTestRule
// BEFORE INCREASING THESE VALUES, PLEASE CONSULT WITH THE PERF TEAM.
private const val EXPECTED_SUPPRESSION_COUNT = 11
-private const val EXPECTED_RUNBLOCKING_COUNT = 2
+private const val EXPECTED_RUNBLOCKING_COUNT = 3
private const val EXPECTED_COMPONENT_INIT_COUNT = 42
private const val EXPECTED_VIEW_HIERARCHY_DEPTH = 12
private const val EXPECTED_RECYCLER_VIEW_CONSTRAINT_LAYOUT_CHILDREN = 4
diff --git a/app/src/androidTest/java/org/mozilla/fenix/screenshots/DefaultHomeScreenTest.kt b/app/src/androidTest/java/org/mozilla/fenix/screenshots/DefaultHomeScreenTest.kt
index d0f32270d..abedf0eaf 100644
--- a/app/src/androidTest/java/org/mozilla/fenix/screenshots/DefaultHomeScreenTest.kt
+++ b/app/src/androidTest/java/org/mozilla/fenix/screenshots/DefaultHomeScreenTest.kt
@@ -31,8 +31,8 @@ class DefaultHomeScreenTest : ScreenshotTest() {
@Test
fun showDefaultHomeScreen() {
homeScreen {
- verifyAccountsSignInButton()
swipeToBottom()
+ verifyAccountsSignInButton()
Screengrab.screenshot("HomeScreenRobot_home-screen-scroll")
TestAssetHelper.waitingTime
}
diff --git a/app/src/androidTest/java/org/mozilla/fenix/screenshots/MenuScreenShotTest.kt b/app/src/androidTest/java/org/mozilla/fenix/screenshots/MenuScreenShotTest.kt
index 3e3f2d674..d058bc8c7 100644
--- a/app/src/androidTest/java/org/mozilla/fenix/screenshots/MenuScreenShotTest.kt
+++ b/app/src/androidTest/java/org/mozilla/fenix/screenshots/MenuScreenShotTest.kt
@@ -142,8 +142,8 @@ class MenuScreenShotTest : ScreenshotTest() {
editBookmarkFolder()
Screengrab.screenshot("ThreeDotMenuBookmarksRobot_edit-bookmark-folder-menu")
// It may be needed to wait here to have the screenshot
- mDevice.pressBack()
bookmarksMenu {
+ navigateUp()
}.openThreeDotMenu("test") {
deleteBookmarkFolder()
Screengrab.screenshot("ThreeDotMenuBookmarksRobot_delete-bookmark-folder-menu")
diff --git a/app/src/androidTest/java/org/mozilla/fenix/syncintegration/SyncIntegrationTest.kt b/app/src/androidTest/java/org/mozilla/fenix/syncintegration/SyncIntegrationTest.kt
index 7b6c02d16..2ab2cecca 100644
--- a/app/src/androidTest/java/org/mozilla/fenix/syncintegration/SyncIntegrationTest.kt
+++ b/app/src/androidTest/java/org/mozilla/fenix/syncintegration/SyncIntegrationTest.kt
@@ -28,7 +28,6 @@ import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.ext.toUri
import org.mozilla.fenix.helpers.ext.waitNotNull
import org.mozilla.fenix.ui.robots.accountSettings
-import org.mozilla.fenix.ui.robots.browserScreen
import org.mozilla.fenix.ui.robots.homeScreen
import org.mozilla.fenix.ui.robots.navigationToolbar
import org.mozilla.fenix.ui.robots.settingsSubMenuLoginsAndPassword
@@ -141,10 +140,7 @@ class SyncIntegrationTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage) {
}.openThreeDotMenu {
- verifyAddBookmarkButton()
- clickAddBookmarkButton()
- }
- browserScreen {
+ }.bookmarkPage {
}.openThreeDotMenu {
}.openSettings {
}.openTurnOnSyncMenu {
diff --git a/app/src/androidTest/java/org/mozilla/fenix/syncintegration/gradlewbuild.py b/app/src/androidTest/java/org/mozilla/fenix/syncintegration/gradlewbuild.py
index d4b325c00..37e03bee0 100644
--- a/app/src/androidTest/java/org/mozilla/fenix/syncintegration/gradlewbuild.py
+++ b/app/src/androidTest/java/org/mozilla/fenix/syncintegration/gradlewbuild.py
@@ -21,7 +21,7 @@ class GradlewBuild(object):
# Change path accordingly to go to root folder to run gradlew
os.chdir('../../../../../../../..')
- cmd = './gradlew ' + 'app:connectedGeckoNightlyDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=org.mozilla.fenix.syncintegration.SyncIntegrationTest#{}'.format(identifier)
+ cmd = './gradlew ' + 'app:connectedDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=org.mozilla.fenix.syncintegration.SyncIntegrationTest#{}'.format(identifier)
self.logger.info('Running cmd: {}'.format(cmd))
diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/BookmarksTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/BookmarksTest.kt
index e4893305b..d09de56b3 100644
--- a/app/src/androidTest/java/org/mozilla/fenix/ui/BookmarksTest.kt
+++ b/app/src/androidTest/java/org/mozilla/fenix/ui/BookmarksTest.kt
@@ -12,7 +12,6 @@ import mozilla.appservices.places.BookmarkRoot
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.R
@@ -32,7 +31,6 @@ import org.mozilla.fenix.ui.robots.navigationToolbar
/**
* Tests for verifying basic functionality of bookmarks
*/
-@Ignore("To be re-implemented in https://github.com/mozilla-mobile/fenix/issues/17979")
class BookmarksTest {
/* ktlint-disable no-blank-line-before-rbrace */ // This imposes unreadable grouping.
@@ -96,10 +94,7 @@ class BookmarksTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.openThreeDotMenu {
- verifyAddBookmarkButton()
- clickAddBookmarkButton()
- }
- browserScreen {
+ }.bookmarkPage {
}.openThreeDotMenu {
verifyEditBookmarkButton()
}
diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/ContextMenusTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/ContextMenusTest.kt
index 258a2a803..046055cfb 100644
--- a/app/src/androidTest/java/org/mozilla/fenix/ui/ContextMenusTest.kt
+++ b/app/src/androidTest/java/org/mozilla/fenix/ui/ContextMenusTest.kt
@@ -32,6 +32,7 @@ import org.mozilla.fenix.ui.robots.navigationToolbar
*
*/
+@Ignore("Test failures: https://github.com/mozilla-mobile/fenix/issues/18421")
class ContextMenusTest {
private val mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
private lateinit var mockWebServer: MockWebServer
diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/NavigationToolbarTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/NavigationToolbarTest.kt
index ee42a73e6..4afff92dd 100644
--- a/app/src/androidTest/java/org/mozilla/fenix/ui/NavigationToolbarTest.kt
+++ b/app/src/androidTest/java/org/mozilla/fenix/ui/NavigationToolbarTest.kt
@@ -88,31 +88,11 @@ class NavigationToolbarTest {
navigationToolbar {
}.openThreeDotMenu {
verifyThreeDotMenuExists()
- verifyForwardButton()
}.goForward {
verifyUrl(nextWebPage.url.toString())
}
}
- @Test
- fun refreshPageTest() {
- val refreshWebPage = TestAssetHelper.getRefreshAsset(mockWebServer)
-
- navigationToolbar {
- }.enterURLAndEnterToBrowser(refreshWebPage.url) {
- mDevice.waitForIdle()
- }
-
- // Use refresh from the three-dot menu
- navigationToolbar {
- }.openThreeDotMenu {
- verifyThreeDotMenuExists()
- verifyRefreshButton()
- }.refreshPage {
- verifyPageContent("REFRESHED")
- }
- }
-
@Test
fun visitURLTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/NoNetworkAccessStartupTests.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/NoNetworkAccessStartupTests.kt
index c1c2d582e..9373a195d 100644
--- a/app/src/androidTest/java/org/mozilla/fenix/ui/NoNetworkAccessStartupTests.kt
+++ b/app/src/androidTest/java/org/mozilla/fenix/ui/NoNetworkAccessStartupTests.kt
@@ -78,7 +78,7 @@ class NoNetworkAccessStartupTests {
browserScreen {
}.openThreeDotMenu {
- }.refreshPage {}
+ }.refreshPage { }
}
@Test
diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/ReaderViewTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/ReaderViewTest.kt
index e4b9ca94d..2d92884d6 100644
--- a/app/src/androidTest/java/org/mozilla/fenix/ui/ReaderViewTest.kt
+++ b/app/src/androidTest/java/org/mozilla/fenix/ui/ReaderViewTest.kt
@@ -9,10 +9,8 @@ import androidx.test.espresso.IdlingRegistry
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.FeatureFlags
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
@@ -104,7 +102,6 @@ class ReaderViewTest {
@Test
fun verifyReaderViewToggle() {
- // New three-dot menu design does not have readerview appearance menu item
val readerViewPage =
TestAssetHelper.getLoremIpsumAsset(mockWebServer)
@@ -127,34 +124,21 @@ class ReaderViewTest {
}
browserScreen {
verifyPageContent(estimatedReadingTime)
- }
+ }.openThreeDotMenu {
+ verifyReaderViewAppearance(true)
+ }.closeBrowserMenuToBrowser { }
+
navigationToolbar {
verifyCloseReaderViewDetected(true)
toggleReaderView()
mDevice.waitForIdle()
verifyReaderViewDetected(true)
- }
-
- if (!FeatureFlags.toolbarMenuFeature) {
- browserScreen {
- verifyPageContent(estimatedReadingTime)
- }.openThreeDotMenu {
- verifyReaderViewAppearance(true)
- }.closeBrowserMenuToBrowser { }
- }
-
- if (!FeatureFlags.toolbarMenuFeature) {
- navigationToolbar {
- toggleReaderView()
- mDevice.waitForIdle()
- }.openThreeDotMenu {
- verifyReaderViewAppearance(false)
- }.close { }
- }
+ }.openThreeDotMenu {
+ verifyReaderViewAppearance(false)
+ }.close { }
}
@Test
- @Ignore("To be re-implemented in https://github.com/mozilla-mobile/fenix/issues/17971")
fun verifyReaderViewAppearanceFontToggle() {
val readerViewPage =
TestAssetHelper.getLoremIpsumAsset(mockWebServer)
@@ -195,7 +179,6 @@ class ReaderViewTest {
}
@Test
- @Ignore("To be re-implemented in https://github.com/mozilla-mobile/fenix/issues/17971")
fun verifyReaderViewAppearanceFontSizeToggle() {
val readerViewPage =
TestAssetHelper.getLoremIpsumAsset(mockWebServer)
@@ -242,7 +225,6 @@ class ReaderViewTest {
}
@Test
- @Ignore("To be re-implemented in https://github.com/mozilla-mobile/fenix/issues/17971")
fun verifyReaderViewAppearanceColorSchemeChange() {
val readerViewPage =
TestAssetHelper.getLoremIpsumAsset(mockWebServer)
diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsBasicsTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsBasicsTest.kt
index a20877bd3..39191b4ec 100644
--- a/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsBasicsTest.kt
+++ b/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsBasicsTest.kt
@@ -106,7 +106,6 @@ class SettingsBasicsTest {
}
@Test
- @Ignore("To be re-implemented in https://github.com/mozilla-mobile/fenix/issues/17979")
fun toggleShowVisitedSitesAndBookmarks() {
// Bookmarks a few websites, toggles the history and bookmarks setting to off, then verifies if the visited and bookmarked websites do not show in the suggestions.
val page1 = getGenericAsset(mockWebServer, 1)
@@ -117,15 +116,13 @@ class SettingsBasicsTest {
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(page1.url) {
}.openThreeDotMenu {
- clickAddBookmarkButton()
- }
+ }.bookmarkPage { }
navigationToolbar {
}.enterURLAndEnterToBrowser(page2.url) {
verifyUrl(page2.url.toString())
}.openThreeDotMenu {
- clickAddBookmarkButton()
- }
+ }.bookmarkPage { }
navigationToolbar {
}.enterURLAndEnterToBrowser(page3.url) {
@@ -137,6 +134,7 @@ class SettingsBasicsTest {
}
}
+ @Ignore("Failing, see: https://github.com/mozilla-mobile/fenix/issues/19016")
@Test
fun changeThemeSetting() {
// Goes through the settings and changes the default search engine, then verifies it changes.
@@ -163,6 +161,7 @@ class SettingsBasicsTest {
}
}
+ @Ignore("Failing, see: https://github.com/mozilla-mobile/fenix/issues/18986")
@Test
fun changeAccessibiltySettings() {
// Goes through the settings and changes the default text on a webpage, then verifies if the text has changed.
diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsPrivacyTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsPrivacyTest.kt
index 36c603a0d..81a1a4459 100644
--- a/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsPrivacyTest.kt
+++ b/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsPrivacyTest.kt
@@ -319,6 +319,7 @@ class SettingsPrivacyTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.openThreeDotMenu {
+ expandMenu()
}.openAddToHomeScreen {
addShortcutName(pageShortcutName)
clickAddShortcutButton()
diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/ShareButtonTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/ShareButtonTest.kt
index bc9767cdb..2329bde05 100644
--- a/app/src/androidTest/java/org/mozilla/fenix/ui/ShareButtonTest.kt
+++ b/app/src/androidTest/java/org/mozilla/fenix/ui/ShareButtonTest.kt
@@ -56,7 +56,6 @@ class ShareButtonTest {
// From the 3-dot menu next to the Select share menu
navigationToolbar {
}.openThreeDotMenu {
- verifyShareButton()
clickShareButton()
verifyShareScrim()
verifySendToDeviceTitle()
diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/SmokeTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/SmokeTest.kt
index 3a5cd31ca..be94e034d 100644
--- a/app/src/androidTest/java/org/mozilla/fenix/ui/SmokeTest.kt
+++ b/app/src/androidTest/java/org/mozilla/fenix/ui/SmokeTest.kt
@@ -17,6 +17,7 @@ import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
+import org.mozilla.fenix.FeatureFlags
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
@@ -35,12 +36,13 @@ import org.mozilla.fenix.ui.robots.enhancedTrackingProtection
import org.mozilla.fenix.ui.robots.homeScreen
import org.mozilla.fenix.ui.robots.navigationToolbar
import org.mozilla.fenix.ui.robots.tabDrawer
+import org.mozilla.fenix.ui.util.STRING_ONBOARDING_TRACKING_PROTECTION_HEADER
/**
* Test Suite that contains tests defined as part of the Smoke and Sanity check defined in Test rail.
* These tests will verify different functionalities of the app as a way to quickly detect regressions in main areas
*/
-
+@Suppress("ForbiddenComment")
class SmokeTest {
private val mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
private lateinit var mockWebServer: MockWebServer
@@ -125,13 +127,9 @@ class SmokeTest {
verifyStartSyncHeader()
verifyAccountsSignInButton()
- // Intro to other sections
- verifyGetToKnowHeader()
-
- // Automatic privacy
- scrollToElementByText("Automatic privacy")
+ // Always-on privacy
+ scrollToElementByText(STRING_ONBOARDING_TRACKING_PROTECTION_HEADER)
verifyAutomaticPrivacyHeader()
- verifyTrackingProtectionToggle()
verifyAutomaticPrivacyText()
// Choose your theme
@@ -142,11 +140,7 @@ class SmokeTest {
verifyLightThemeDescription()
verifyLightThemeToggle()
- // Browse privately
- verifyBrowsePrivatelyHeader()
- verifyBrowsePrivatelyText()
-
- // Take a position
+ // Pick your toolbar placement
verifyTakePositionHeader()
verifyTakePositionElements()
@@ -161,6 +155,7 @@ class SmokeTest {
}
@Test
+ @Ignore("https://github.com/mozilla-mobile/fenix/issues/18603")
// Verifies the functionality of the onboarding Start Browsing button
fun startBrowsingButtonTest() {
homeScreen {
@@ -199,14 +194,13 @@ class SmokeTest {
@Test
// Verifies the list of items in a tab's 3 dot menu
- @Ignore("To be re-implemented with the three dot menu changes https://github.com/mozilla-mobile/fenix/issues/17870")
fun verifyPageMainMenuItemsTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.openThreeDotMenu {
- verifyThreeDotMainMenuItems()
+ verifyPageThreeDotMainMenuItems()
}
}
@@ -233,12 +227,21 @@ class SmokeTest {
}
@Test
- // Verifies the Synced tabs menu opens from a tab's 3 dot menu
- fun openMainMenuSyncedTabsItemTest() {
- homeScreen {
- }.openThreeDotMenu {
- }.openSyncedTabs {
- verifySyncedTabsMenuHeader()
+ // Verifies the Synced tabs menu or Sync Sign In menu opens from a tab's 3 dot menu.
+ // The test is assuming we are NOT signed in.
+ fun openMainMenuSyncItemTest() {
+ if (FeatureFlags.tabsTrayRewrite) {
+ homeScreen {
+ }.openThreeDotMenu {
+ }.openSyncSignIn {
+ verifySyncSignInMenuHeader()
+ }
+ } else {
+ homeScreen {
+ }.openThreeDotMenu {
+ }.openSyncedTabs {
+ verifySyncedTabsMenuHeader()
+ }
}
}
@@ -274,6 +277,7 @@ class SmokeTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.openThreeDotMenu {
+ expandMenu()
}.addToFirefoxHome {
verifySnackBarText("Added to top sites!")
}.openTabDrawer {
@@ -292,12 +296,14 @@ class SmokeTest {
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(website.url) {
}.openThreeDotMenu {
+ expandMenu()
}.openAddToHomeScreen {
clickCancelShortcutButton()
}
browserScreen {
}.openThreeDotMenu {
+ expandMenu()
}.openAddToHomeScreen {
verifyShortcutNameField("Test_Page_1")
addShortcutName("Test Page")
@@ -322,7 +328,6 @@ class SmokeTest {
@Test
// Verifies the Bookmark button in a tab's 3 dot menu
- @Ignore("To be re-implemented in https://github.com/mozilla-mobile/fenix/issues/17979")
fun mainMenuBookmarkButtonTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
@@ -357,7 +362,6 @@ class SmokeTest {
mDevice.waitForIdle()
}.openThreeDotMenu {
verifyThreeDotMenuExists()
- verifyRefreshButton()
}.refreshPage {
verifyPageContent("REFRESHED")
}
@@ -365,7 +369,6 @@ class SmokeTest {
@Test
// Turns ETP toggle off from Settings and verifies the ETP shield is not displayed in the nav bar
- @Ignore("To be re-implemented with the three dot menu changes https://github.com/mozilla-mobile/fenix/issues/17870")
fun verifyETPShieldNotDisplayedIfOFFGlobally() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
@@ -392,6 +395,7 @@ class SmokeTest {
}
}
+ @Ignore("Failing, see https://github.com/mozilla-mobile/fenix/issues/18647")
@Test
fun customTrackingProtectionSettingsTest() {
val trackingPage = TestAssetHelper.getEnhancedTrackingProtectionAsset(mockWebServer)
@@ -542,7 +546,6 @@ class SmokeTest {
@Test
// Saves a login, then changes it and verifies the update
- @Ignore("To be re-implemented with the three dot menu changes https://github.com/mozilla-mobile/fenix/issues/17870")
fun updateSavedLoginTest() {
val saveLoginTest =
TestAssetHelper.getSaveLoginAsset(mockWebServer)
@@ -606,7 +609,6 @@ class SmokeTest {
}
@Test
- @Ignore("To be re-implemented in https://github.com/mozilla-mobile/fenix/issues/17799")
// Installs uBlock add-on and checks that the app doesn't crash while loading pages with trackers
fun noCrashWithAddonInstalledTest() {
// setting ETP to Strict mode to test it works with add-ons
@@ -894,6 +896,7 @@ class SmokeTest {
}
}
+ @Ignore("Disabling until re-implemented by #19090")
@Test
fun verifyExpandedCollectionItemsTest() {
val webPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
@@ -943,6 +946,7 @@ class SmokeTest {
}
}
+ @Ignore("Disabling until re-implemented by #19090")
@Test
fun shareCollectionTest() {
val webPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
@@ -960,6 +964,7 @@ class SmokeTest {
}
}
+ @Ignore("Disabling until re-implemented by #19090")
@Test
fun deleteCollectionTest() {
val webPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
@@ -981,7 +986,6 @@ class SmokeTest {
@Test
// Verifies that deleting a Bookmarks folder also removes the item from inside it.
- @Ignore("To be re-implemented in https://github.com/mozilla-mobile/fenix/issues/17799")
fun deleteNonEmptyBookmarkFolderTest() {
val website = TestAssetHelper.getGenericAsset(mockWebServer, 1)
@@ -1058,6 +1062,7 @@ class SmokeTest {
}
}
+ @Ignore("Feature is temporarily removed; disabling test. See https://github.com/mozilla-mobile/fenix/issues/18656")
@Test
fun selectTabsButtonVisibilityTest() {
homeScreen {
@@ -1142,7 +1147,6 @@ class SmokeTest {
}
@Test
- @Ignore("To be re-implemented in https://github.com/mozilla-mobile/fenix/issues/17799")
fun mainMenuInstallPWATest() {
val pwaPage = "https://rpappalax.github.io/testapp/"
@@ -1159,7 +1163,6 @@ class SmokeTest {
}
@Test
- @Ignore("To be re-implemented in https://github.com/mozilla-mobile/fenix/issues/17971")
// Verifies that reader mode is detected and the custom appearance controls are displayed
fun verifyReaderViewAppearanceUI() {
val readerViewPage =
diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/StrictEnhancedTrackingProtectionTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/StrictEnhancedTrackingProtectionTest.kt
index 9e7494152..09ef8b83c 100644
--- a/app/src/androidTest/java/org/mozilla/fenix/ui/StrictEnhancedTrackingProtectionTest.kt
+++ b/app/src/androidTest/java/org/mozilla/fenix/ui/StrictEnhancedTrackingProtectionTest.kt
@@ -8,7 +8,6 @@ import androidx.test.platform.app.InstrumentationRegistry
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.ext.settings
@@ -127,7 +126,6 @@ class StrictEnhancedTrackingProtectionTest {
}
@Test
- @Ignore("To be re-implemented with the three dot menu changes https://github.com/mozilla-mobile/fenix/issues/17870")
fun testStrictVisitDisable() {
val trackingProtectionTest =
TestAssetHelper.getEnhancedTrackingProtectionAsset(mockWebServer)
@@ -151,7 +149,6 @@ class StrictEnhancedTrackingProtectionTest {
navigationToolbar {
}.openThreeDotMenu {
verifyThreeDotMenuExists()
- verifySettingsButton()
}.openSettings {
verifyEnhancedTrackingProtectionButton()
verifyEnhancedTrackingProtectionValue("On")
diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/ThreeDotMenuMainTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/ThreeDotMenuMainTest.kt
index 86645af12..69b2f294a 100644
--- a/app/src/androidTest/java/org/mozilla/fenix/ui/ThreeDotMenuMainTest.kt
+++ b/app/src/androidTest/java/org/mozilla/fenix/ui/ThreeDotMenuMainTest.kt
@@ -4,12 +4,9 @@
package org.mozilla.fenix.ui
-import androidx.test.platform.app.InstrumentationRegistry
-import androidx.test.uiautomator.UiDevice
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import org.junit.Before
-import org.junit.BeforeClass
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.FeatureFlags
@@ -38,85 +35,58 @@ class ThreeDotMenuMainTest {
}
}
- // changing the device preference for Touch and Hold delay, to avoid long-clicks instead of a single-click
- companion object {
- @BeforeClass
- @JvmStatic
- fun setDevicePreference() {
- val mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
- mDevice.executeShellCommand("settings put secure long_press_timeout 3000")
- }
- }
-
@After
fun tearDown() {
mockWebServer.shutdown()
}
+ // Verifies the list of items in the homescreen's 3 dot main menu
@Test
- fun threeDotMenuItemsTest() {
- if (FeatureFlags.toolbarMenuFeature) {
- homeScreen {
- }.openThreeDotMenu {
- }.openHistory {
- verifyHistoryMenuView()
- }.goBackToBrowser {}
-
- homeScreen {
- }.openThreeDotMenu {
- }.openBookmarks {
- verifyBookmarksMenuView()
- }.closeMenu {}
-
- homeScreen {
- }.openThreeDotMenu {
- verifySettingsButton()
- verifyBookmarksButton()
- verifyHistoryButton()
- }.openSettings {
- verifySettingsView()
- }.goBack {
- }.openThreeDotMenu {
- }.goBack {}
- } else {
- homeScreen {
- }.openThreeDotMenu {
- verifySettingsButton()
- verifyBookmarksButton()
- verifyHistoryButton()
- verifyHelpButton()
- verifyWhatsNewButton()
- }.openSettings {
- verifySettingsView()
- }.goBack {
- }.openThreeDotMenu {
- }.openHelp {
- verifyHelpUrl()
- }.openTabDrawer {
- closeTab()
- }
-
- homeScreen {
- }.openThreeDotMenu {
- }.openWhatsNew {
- verifyWhatsNewURL()
- }.openTabDrawer {
- closeTab()
+ fun homeThreeDotMenuItemsTest() {
+ homeScreen {
+ }.openThreeDotMenu {
+ verifyBookmarksButton()
+ verifyHistoryButton()
+ verifyDownloadsButton()
+ verifyAddOnsButton()
+ if (FeatureFlags.tabsTrayRewrite) {
+ verifySyncSignInButton()
+ } else {
+ verifySyncedTabsButton()
}
+ verifyDesktopSite()
+ verifyWhatsNewButton()
+ verifyHelpButton()
+ verifySettingsButton()
+ }.openSettings {
+ verifySettingsView()
+ }.goBack {
+ }.openThreeDotMenu {
+ }.openHelp {
+ verifyHelpUrl()
+ }.openTabDrawer {
+ closeTab()
+ }
- homeScreen {
- }.openThreeDotMenu {
- }.openBookmarks {
- verifyBookmarksMenuView()
- }.closeMenu {
- }
+ homeScreen {
+ }.openThreeDotMenu {
+ }.openWhatsNew {
+ verifyWhatsNewURL()
+ }.openTabDrawer {
+ closeTab()
+ }
- homeScreen {
- }.openThreeDotMenu {
- }.openHistory {
- verifyHistoryMenuView()
- }
+ homeScreen {
+ }.openThreeDotMenu {
+ }.openBookmarks {
+ verifyBookmarksMenuView()
+ }.closeMenu {
}
+ homeScreen {
+ }.openThreeDotMenu {
+ }.openHistory {
+ verifyHistoryMenuView()
+ }
}
}
diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/TopSitesTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/TopSitesTest.kt
index 4f5a4bbc1..96eac6ace 100644
--- a/app/src/androidTest/java/org/mozilla/fenix/ui/TopSitesTest.kt
+++ b/app/src/androidTest/java/org/mozilla/fenix/ui/TopSitesTest.kt
@@ -54,7 +54,8 @@ class TopSitesTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.openThreeDotMenu {
- verifyAddFirefoxHome()
+ expandMenu()
+ verifyAddToTopSitesButton()
}.addToFirefoxHome {
verifySnackBarText("Added to top sites!")
}.openTabDrawer {
@@ -73,7 +74,8 @@ class TopSitesTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.openThreeDotMenu {
- verifyAddFirefoxHome()
+ expandMenu()
+ verifyAddToTopSitesButton()
}.addToFirefoxHome {
verifySnackBarText("Added to top sites!")
}.openTabDrawer {
@@ -104,7 +106,8 @@ class TopSitesTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.openThreeDotMenu {
- verifyAddFirefoxHome()
+ expandMenu()
+ verifyAddToTopSitesButton()
}.addToFirefoxHome {
verifySnackBarText("Added to top sites!")
}.openTabDrawer {
@@ -128,7 +131,8 @@ class TopSitesTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.openThreeDotMenu {
- verifyAddFirefoxHome()
+ expandMenu()
+ verifyAddToTopSitesButton()
}.addToFirefoxHome {
verifySnackBarText("Added to top sites!")
}.openTabDrawer {
@@ -152,7 +156,8 @@ class TopSitesTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.openThreeDotMenu {
- verifyAddFirefoxHome()
+ expandMenu()
+ verifyAddToTopSitesButton()
}.addToFirefoxHome {
verifySnackBarText("Added to top sites!")
}.openTabDrawer {
diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/BrowserRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/BrowserRobot.kt
index 2e7343f36..d10a7df65 100644
--- a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/BrowserRobot.kt
+++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/BrowserRobot.kt
@@ -310,8 +310,7 @@ class BrowserRobot {
// needs to wait for the right url to load before saving a bookmark
verifyUrl(url.toString())
}.openThreeDotMenu {
- clickAddBookmarkButton()
- }
+ }.bookmarkPage { }
}
fun clickLinkMatchingText(expectedText: String) {
diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/HomeScreenRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/HomeScreenRobot.kt
index 2db81496c..95825a1c5 100644
--- a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/HomeScreenRobot.kt
+++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/HomeScreenRobot.kt
@@ -54,6 +54,9 @@ import org.mozilla.fenix.helpers.click
import org.mozilla.fenix.helpers.ext.waitNotNull
import org.mozilla.fenix.helpers.matchers.hasItem
import org.mozilla.fenix.helpers.withBitmapDrawable
+import org.mozilla.fenix.ui.util.STRING_ONBOARDING_ACCOUNT_SIGN_IN_HEADER
+import org.mozilla.fenix.ui.util.STRING_ONBOARDING_TOOLBAR_PLACEMENT_HEADER
+import org.mozilla.fenix.ui.util.STRING_ONBOARDING_TRACKING_PROTECTION_HEADER
/**
* Implementation of Robot Pattern for the home screen menu.
@@ -85,7 +88,6 @@ class HomeScreenRobot {
fun verifyStartSyncHeader() = assertStartSyncHeader()
fun verifyAccountsSignInButton() = assertAccountsSignInButton()
- fun verifyGetToKnowHeader() = assertGetToKnowHeader()
fun verifyChooseThemeHeader() = assertChooseThemeHeader()
fun verifyChooseThemeText() = assertChooseThemeText()
fun verifyLightThemeToggle() = assertLightThemeToggle()
@@ -95,18 +97,13 @@ class HomeScreenRobot {
fun verifyAutomaticThemeToggle() = assertAutomaticThemeToggle()
fun verifyAutomaticThemeDescription() = assertAutomaticThemeDescription()
fun verifyAutomaticPrivacyHeader() = assertAutomaticPrivacyHeader()
- fun verifyTrackingProtectionToggle() = assertTrackingProtectionToggle()
- fun verifyAutomaticPrivacyText() = assertAutomaticPrivacyText()
+ fun verifyAutomaticPrivacyText() = assertAlwaysPrivacyText()
- // Browse privately
- fun verifyBrowsePrivatelyHeader() = assertBrowsePrivatelyHeader()
- fun verifyBrowsePrivatelyText() = assertBrowsePrivatelyText()
-
- // Take a position
- fun verifyTakePositionHeader() = assertTakePositionheader()
+ // Pick your toolbar placement
+ fun verifyTakePositionHeader() = assertTakePlacementHeader()
fun verifyTakePositionElements() {
- assertTakePositionBottomRadioButton()
- assertTakePositionTopRadioButton()
+ assertTakePlacementBottomRadioButton()
+ assertTakePacementTopRadioButton()
}
// Your privacy
@@ -549,18 +546,15 @@ private fun assertWelcomeHeader() =
onView(allOf(withText("Welcome to ${appContext.appName}!")))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
-private fun assertStartSyncHeader() =
- onView(allOf(withText("Start syncing bookmarks, passwords, and more with your Firefox account.")))
+private fun assertStartSyncHeader() {
+ scrollToElementByText(STRING_ONBOARDING_ACCOUNT_SIGN_IN_HEADER)
+ onView(allOf(withText(R.string.onboarding_account_sign_in_header_1)))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
-
+}
private fun assertAccountsSignInButton() =
onView(ViewMatchers.withResourceName("fxa_sign_in_button"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
-private fun assertGetToKnowHeader() =
- onView(allOf(withText("Get to know ${appContext.appName}")))
- .check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
-
private fun assertChooseThemeHeader() {
scrollToElementByText("Choose your theme")
onView(withText("Choose your theme"))
@@ -568,7 +562,7 @@ private fun assertChooseThemeHeader() {
}
private fun assertChooseThemeText() {
scrollToElementByText("Choose your theme")
- onView(allOf(withText("Save some battery and your eyesight by enabling dark mode.")))
+ onView(allOf(withText("Save some battery and your eyesight with dark mode.")))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
}
@@ -608,40 +602,23 @@ private fun assertAutomaticThemeDescription() {
}
private fun assertAutomaticPrivacyHeader() {
- scrollToElementByText("Automatic privacy")
- onView(allOf(withText("Automatic privacy")))
+ scrollToElementByText(STRING_ONBOARDING_TRACKING_PROTECTION_HEADER)
+ onView(allOf(withText(STRING_ONBOARDING_TRACKING_PROTECTION_HEADER)))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
}
-private fun assertTrackingProtectionToggle() {
- scrollToElementByText("Automatic privacy")
- onView(withId(R.id.tracking_protection_toggle))
- .check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
-}
-
-private fun assertAutomaticPrivacyText() {
- scrollToElementByText("Automatic privacy")
+private fun assertAlwaysPrivacyText() {
+ scrollToElementByText(STRING_ONBOARDING_TRACKING_PROTECTION_HEADER)
onView(
allOf(
withText(
- "Privacy and security settings block trackers, malware, and companies that follow you."
+ R.string.onboarding_tracking_protection_description_3
)
)
)
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
}
-private fun assertBrowsePrivatelyHeader() {
- scrollToElementByText("Browse privately")
- onView(allOf(withText("Browse privately")))
- .check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
-}
-
-private fun assertBrowsePrivatelyText() {
- scrollToElementByText("Browse privately")
- onView(allOf(withText(containsString("Update your private browsing settings."))))
- .check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
-}
private fun assertYourPrivacyHeader() {
scrollToElementByText("Your privacy")
onView(allOf(withText("Your privacy")))
@@ -668,25 +645,25 @@ private fun assertPrivacyNoticeButton() {
private fun assertStartBrowsingButton() {
scrollToElementByText("Start browsing")
- onView(allOf(withText("Start browsing")))
+ onView(withId(R.id.finish_button))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
}
-// Take a position
-private fun assertTakePositionheader() {
- scrollToElementByText("Take a position")
- onView(allOf(withText("Take a position")))
+// Pick your toolbar placement
+private fun assertTakePlacementHeader() {
+ scrollToElementByText(STRING_ONBOARDING_TOOLBAR_PLACEMENT_HEADER)
+ onView(allOf(withText(STRING_ONBOARDING_TOOLBAR_PLACEMENT_HEADER)))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
}
-private fun assertTakePositionTopRadioButton() {
- scrollToElementByText("Take a position")
+private fun assertTakePacementTopRadioButton() {
+ scrollToElementByText(STRING_ONBOARDING_TOOLBAR_PLACEMENT_HEADER)
onView(ViewMatchers.withResourceName("toolbar_top_radio_button"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
}
-private fun assertTakePositionBottomRadioButton() {
- scrollToElementByText("Take a position")
+private fun assertTakePlacementBottomRadioButton() {
+ scrollToElementByText(STRING_ONBOARDING_TOOLBAR_PLACEMENT_HEADER)
onView(ViewMatchers.withResourceName("toolbar_bottom_radio_button"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
}
diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/NavigationToolbarRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/NavigationToolbarRobot.kt
index e6c3d13d2..c6177bc46 100644
--- a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/NavigationToolbarRobot.kt
+++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/NavigationToolbarRobot.kt
@@ -109,8 +109,7 @@ class NavigationToolbarRobot {
withResourceName("onboarding_message"), // Req ETP dialog
withResourceName("download_button")
)
- )
- .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
+ ).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
}
BrowserRobot().interact()
@@ -186,10 +185,10 @@ class NavigationToolbarRobot {
}
fun openTabTray(interact: TabDrawerRobot.() -> Unit): TabDrawerRobot.Transition {
- onView(withId(R.id.tab_button))
- .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
-
+ mDevice.waitForIdle(waitingTime)
tabTrayButton().click()
+ mDevice.waitNotNull(Until.findObject(By.res("$packageName:id/tab_layout")),
+ waitingTime)
TabDrawerRobot().interact()
return TabDrawerRobot.Transition()
diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsRobot.kt
index e83a759f5..61acebf2a 100644
--- a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsRobot.kt
+++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsRobot.kt
@@ -269,16 +269,20 @@ private fun assertThemeSelected() = onView(withText("Light"))
private fun assertAccessibilityButton() = onView(withText("Accessibility"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
-private fun assertSetAsDefaultBrowserButton() =
+private fun assertSetAsDefaultBrowserButton() {
+ scrollToElementByText("Set as default browser")
onView(withText("Set as default browser"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
+}
private fun assertDefaultBrowserIsDisabled() {
+ scrollToElementByText("Set as default browser")
onView(withId(R.id.switch_widget))
.check(matches(ViewMatchers.isNotChecked()))
}
private fun toggleDefaultBrowserSwitch() {
+ scrollToElementByText("Set as default browser")
onView(
CoreMatchers.allOf(
ViewMatchers.withParent(CoreMatchers.not(withId(R.id.navigationToolbar))),
diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuDataCollectionRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuDataCollectionRobot.kt
index 07f36c0ff..c4dbca480 100644
--- a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuDataCollectionRobot.kt
+++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuDataCollectionRobot.kt
@@ -77,7 +77,7 @@ private fun assertDataCollectionOptions() {
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
val marketingDataText =
- "Shares data about what features you use in Firefox Preview with Leanplum, our mobile marketing vendor."
+ "Shares basic usage data with Adjust, our mobile marketing vendor"
onView(withText(marketingDataText))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuEnhancedTrackingProtectionRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuEnhancedTrackingProtectionRobot.kt
index 14e7b0a8e..e3fe7d6fa 100644
--- a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuEnhancedTrackingProtectionRobot.kt
+++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuEnhancedTrackingProtectionRobot.kt
@@ -143,16 +143,13 @@ private fun assertEnhancedTrackingProtectionOptions() {
onView(withText("Standard (default)"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
- val stdText = "Blocks fewer trackers. Pages will load normally."
- onView(withText(stdText))
+ onView(withText(org.mozilla.fenix.R.string.preference_enhanced_tracking_protection_standard_description_4))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
onView(withText("Strict"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
- val strictText =
- "Blocks more trackers, ads, and popups. Pages load faster, but some functionality might not work."
- onView(withText(strictText))
+ onView(withText(org.mozilla.fenix.R.string.preference_enhanced_tracking_protection_strict_description_3))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
onView(withText("Custom"))
@@ -168,16 +165,13 @@ private fun assertEnhancedTrackingProtectionOptionsGrayedOut() {
onView(withText("Standard (default)"))
.check(matches(not(isEnabled(true))))
- val stdText = "Blocks fewer trackers. Pages will load normally."
- onView(withText(stdText))
+ onView(withText(org.mozilla.fenix.R.string.preference_enhanced_tracking_protection_standard_description_4))
.check(matches(not(isEnabled(true))))
onView(withText("Strict"))
.check(matches(not(isEnabled(true))))
- val strictText =
- "Blocks more trackers, ads, and popups. Pages load faster, but some functionality might not work."
- onView(withText(strictText))
+ onView(withText(org.mozilla.fenix.R.string.preference_enhanced_tracking_protection_strict_description_3))
.check(matches(not(isEnabled(true))))
onView(withText("Custom"))
diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuSitePermissionsCommonRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuSitePermissionsCommonRobot.kt
index dd129d479..566508837 100644
--- a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuSitePermissionsCommonRobot.kt
+++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuSitePermissionsCommonRobot.kt
@@ -129,12 +129,19 @@ private fun assertVideoAndAudioBlockedRecommended() = onView(withId(R.id.fourth_
private fun assertCheckAutoPayRadioButtonDefault() {
+ // Allow audio and video
onView(withId(R.id.block_radio))
.assertIsChecked(isChecked = false)
+ // Block audio and video on cellular data only
+ onView(withId(R.id.block_radio))
+ .assertIsChecked(isChecked = false)
+
+ // Block audio only
onView(withId(R.id.third_radio))
.assertIsChecked(isChecked = false)
+ // Block audio and video
onView(withId(R.id.fourth_radio))
.assertIsChecked(isChecked = true)
}
diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SyncSignInRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SyncSignInRobot.kt
new file mode 100644
index 000000000..ac7132ea7
--- /dev/null
+++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SyncSignInRobot.kt
@@ -0,0 +1,52 @@
+/* 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.robots
+
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.assertion.ViewAssertions.matches
+import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
+import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
+import androidx.test.espresso.matcher.ViewMatchers.withText
+import androidx.test.espresso.matcher.ViewMatchers.Visibility
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import org.hamcrest.CoreMatchers.allOf
+import org.mozilla.fenix.R
+import org.mozilla.fenix.helpers.click
+
+/**
+ * Implementation of Robot Pattern for Sync Sign In sub menu.
+ */
+class SyncSignInRobot {
+
+ fun verifyAccountSettingsMenuHeader() = assertAccountSettingsMenuHeader()
+ fun verifySyncSignInMenuHeader() = assertSyncSignInMenuHeader()
+
+ class Transition {
+ val mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())!!
+
+ fun goBack(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
+ goBackButton().click()
+
+ BrowserRobot().interact()
+ return BrowserRobot.Transition()
+ }
+ }
+}
+
+private fun goBackButton() =
+ onView(allOf(withContentDescription("Navigate up")))
+
+private fun assertAccountSettingsMenuHeader() {
+ // Replaced with the new string here, the test is assuming we are NOT signed in
+ // Sync tests in SettingsSyncTest are still TO-DO, so I'm not sure that we have a test for signing into Sync
+ onView(withText(R.string.preferences_account_settings))
+ .check((matches(withEffectiveVisibility(Visibility.VISIBLE))))
+}
+
+private fun assertSyncSignInMenuHeader() {
+ onView(withText(R.string.sign_in_with_camera))
+ .check((matches(withEffectiveVisibility(Visibility.VISIBLE))))
+}
diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SyncedTabsRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SyncedTabsRobot.kt
index 9add0cd59..43aad7ccf 100644
--- a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SyncedTabsRobot.kt
+++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SyncedTabsRobot.kt
@@ -39,6 +39,8 @@ private fun goBackButton() =
onView(allOf(withContentDescription("Navigate up")))
private fun assertSyncedTabsMenuHeader() {
- onView(withText(R.string.synced_tabs))
+ // Replaced with the new string here, the test is assuming we are NOT signed in
+ // Sync tests in SettingsSyncTest are still TO-DO, so I'm not sure that we have a test for signing into Sync
+ onView(withText(R.string.sync_menu_sign_in))
.check((matches(withEffectiveVisibility(Visibility.VISIBLE))))
}
diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/TabDrawerRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/TabDrawerRobot.kt
index b1f2f647a..f0007760c 100644
--- a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/TabDrawerRobot.kt
+++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/TabDrawerRobot.kt
@@ -160,11 +160,29 @@ class TabDrawerRobot {
fun clickTabMediaControlButton() = tabMediaControlButton().click()
- fun clickSelectTabs() = onView(withText("Select tabs")).click()
+ fun clickSelectTabs() {
+ threeDotMenu().click()
+
+ mDevice.waitNotNull(
+ Until.findObject(text("Select tabs")),
+ waitingTime
+ )
+
+ val selectTabsButton = mDevice.findObject(text("Select tabs"))
+ selectTabsButton.click()
+ }
fun clickAddNewCollection() = addNewCollectionButton().click()
- fun selectTab(title: String) = tab(title).click()
+ fun selectTab(title: String) {
+ mDevice.waitNotNull(
+ findObject(text(title)),
+ waitingTime
+ )
+
+ val tab = mDevice.findObject(text(title))
+ tab.click()
+ }
fun clickSaveCollection() = saveTabsToCollectionButton().click()
@@ -198,15 +216,10 @@ class TabDrawerRobot {
}
fun openTabDrawer(interact: TabDrawerRobot.() -> Unit): TabDrawerRobot.Transition {
- mDevice.findObject(UiSelector().resourceId("org.mozilla.fenix.debug:id/tab_button"))
- .waitForExists(waitingTime)
-
+ mDevice.waitForIdle(waitingTime)
tabsCounter().click()
-
- org.mozilla.fenix.ui.robots.mDevice.waitNotNull(
- Until.findObject(By.res("org.mozilla.fenix.debug:id/tab_layout")),
- waitingTime
- )
+ mDevice.waitNotNull(Until.findObject(By.res("$packageName:id/tab_layout")),
+ waitingTime)
TabDrawerRobot().interact()
return TabDrawerRobot.Transition()
diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/ThreeDotMenuMainRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/ThreeDotMenuMainRobot.kt
index 71d03cbc5..9db6eef94 100644
--- a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/ThreeDotMenuMainRobot.kt
+++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/ThreeDotMenuMainRobot.kt
@@ -6,24 +6,17 @@
package org.mozilla.fenix.ui.robots
-import android.view.View
import androidx.recyclerview.widget.RecyclerView
import androidx.test.espresso.Espresso.onView
-import androidx.test.espresso.Espresso.pressBack
-import androidx.test.espresso.UiController
-import androidx.test.espresso.ViewAction
import androidx.test.espresso.action.ViewActions
-import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.action.ViewActions.swipeDown
import androidx.test.espresso.action.ViewActions.swipeUp
-import androidx.test.espresso.assertion.ViewAssertions
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.contrib.RecyclerViewActions
import androidx.test.espresso.matcher.RootMatchers
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.Visibility
import androidx.test.espresso.matcher.ViewMatchers.hasDescendant
-import androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom
import androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
@@ -35,10 +28,9 @@ import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.UiSelector
import androidx.test.uiautomator.Until
-import org.hamcrest.Matcher
import org.hamcrest.Matchers.allOf
+import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
-import org.mozilla.fenix.FeatureFlags
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime
import org.mozilla.fenix.helpers.click
@@ -48,6 +40,7 @@ import org.mozilla.fenix.share.ShareFragment
/**
* Implementation of Robot Pattern for the three dot (main) menu.
*/
+@Suppress("ForbiddenComment")
class ThreeDotMenuMainRobot {
fun verifyTabSettingsButton() = assertTabSettingsButton()
fun verifyRecentlyClosedTabsButton() = assertRecentlyClosedTabsButton()
@@ -58,6 +51,7 @@ class ThreeDotMenuMainRobot {
fun verifyHistoryButton() = assertHistoryButton()
fun verifyBookmarksButton() = assertBookmarksButton()
fun verifySyncedTabsButton() = assertSyncedTabsButton()
+ fun verifySyncSignInButton() = assertSignInToSyncButton()
fun verifyHelpButton() = assertHelpButton()
fun verifyThreeDotMenuExists() = threeDotMenuRecyclerViewExists()
fun verifyForwardButton() = assertForwardButton()
@@ -68,7 +62,16 @@ class ThreeDotMenuMainRobot {
fun verifyShareButton() = assertShareButton()
fun verifyReaderViewAppearance(visible: Boolean) = assertReaderViewAppearanceButton(visible)
+ fun expandMenu() {
+ onView(withId(R.id.mozac_browser_menu_menuView)).perform(swipeUp())
+ }
+
fun clickShareButton() {
+ var maxSwipes = 3
+ while (!shareButton().exists() && maxSwipes != 0) {
+ threeDotMenuRecyclerView().perform(swipeUp())
+ maxSwipes--
+ }
shareButton().click()
mDevice.waitNotNull(Until.findObject(By.text("ALL ACTIONS")), waitingTime)
}
@@ -85,73 +88,41 @@ class ThreeDotMenuMainRobot {
addNewCollectionButton().click()
}
- fun clickAddBookmarkButton() {
- mDevice.waitNotNull(
- Until.findObject(By.desc("Bookmark")),
- waitingTime
- )
- addBookmarkButton().perform(
- click(
- /* no-op rollback action for when clicks randomly perform a long click, Espresso should attempt to click again
- https://issuetracker.google.com/issues/37078920#comment9
- */
- object : ViewAction {
- override fun getDescription(): String {
- return "Handle tap->longclick."
- }
-
- override fun getConstraints(): Matcher {
- return isAssignableFrom(View::class.java)
- }
-
- override fun perform(uiController: UiController?, view: View?) {
- // do nothing
- }
- }
- )
- )
- }
-
fun verifyCollectionNameTextField() = assertCollectionNameTextField()
fun verifyFindInPageButton() = assertFindInPageButton()
fun verifyShareScrim() = assertShareScrim()
fun verifySendToDeviceTitle() = assertSendToDeviceTitle()
fun verifyShareALinkTitle() = assertShareALinkTitle()
fun verifyWhatsNewButton() = assertWhatsNewButton()
- fun verifyAddFirefoxHome() = assertAddToFirefoxHome()
+ fun verifyAddToTopSitesButton() = assertAddToTopSitesButton()
fun verifyAddToMobileHome() = assertAddToMobileHome()
fun verifyDesktopSite() = assertDesktopSite()
fun verifyDownloadsButton() = assertDownloadsButton()
fun verifyShareTabsOverlay() = assertShareTabsOverlay()
-
- fun verifyThreeDotMainMenuItems() {
- if (FeatureFlags.toolbarMenuFeature) {
- verifyDownloadsButton()
- verifyHistoryButton()
- verifyBookmarksButton()
- verifySettingsButton()
- verifyDesktopSite()
- verifySaveCollection()
- verifyShareButton()
- verifyForwardButton()
- verifyRefreshButton()
- } else {
- verifyAddOnsButton()
- verifyDownloadsButton()
- verifyHistoryButton()
- verifyBookmarksButton()
- verifySyncedTabsButton()
- verifySettingsButton()
- verifyFindInPageButton()
- verifyAddFirefoxHome()
- verifyAddToMobileHome()
- verifyDesktopSite()
- verifySaveCollection()
- verifyAddBookmarkButton()
- verifyShareButton()
- verifyForwardButton()
- verifyRefreshButton()
- }
+ fun verifySignInToSyncButton() = assertSignInToSyncButton()
+ fun verifyNewTabButton() = assertNewTabButton()
+ fun verifyReportSiteIssueButton() = assertReportSiteIssueButton()
+
+ fun verifyPageThreeDotMainMenuItems() {
+ verifyNewTabButton()
+ verifyBookmarksButton()
+ verifyAddBookmarkButton()
+ verifyHistoryButton()
+ verifyDownloadsButton()
+ verifyAddOnsButton()
+ verifySignInToSyncButton()
+ threeDotMenuRecyclerView().perform(swipeUp())
+ verifyFindInPageButton()
+ verifyDesktopSite()
+ threeDotMenuRecyclerView().perform(swipeUp())
+ verifyReportSiteIssueButton()
+ verifyAddToTopSitesButton()
+ verifyAddToMobileHome()
+ verifySaveCollection()
+ verifySettingsButton()
+ verifyShareButton()
+ verifyForwardButton()
+ verifyRefreshButton()
}
private fun assertShareTabsOverlay() {
@@ -166,11 +137,12 @@ class ThreeDotMenuMainRobot {
private val mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
fun openSettings(interact: SettingsRobot.() -> Unit): SettingsRobot.Transition {
- onView(withId(R.id.mozac_browser_menu_recyclerView)).perform(ViewActions.swipeDown())
- onView(allOf(withResourceName("text"), withText(R.string.browser_menu_settings)))
- .check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
- .check(matches(isCompletelyDisplayed()))
- .perform(ViewActions.click())
+ var maxSwipes = 3
+ while (!settingsButton().exists() && maxSwipes != 0) {
+ threeDotMenuRecyclerView().perform(swipeUp())
+ maxSwipes--
+ }
+ settingsButton().click()
SettingsRobot().interact()
return SettingsRobot.Transition()
@@ -185,7 +157,7 @@ class ThreeDotMenuMainRobot {
}
fun openSyncedTabs(interact: SyncedTabsRobot.() -> Unit): SyncedTabsRobot.Transition {
- onView(withId(R.id.mozac_browser_menu_recyclerView)).perform(ViewActions.swipeDown())
+ onView(withId(R.id.mozac_browser_menu_recyclerView)).perform(swipeDown())
mDevice.waitNotNull(Until.findObject(By.text("Synced tabs")), waitingTime)
syncedTabsButton().click()
@@ -193,6 +165,15 @@ class ThreeDotMenuMainRobot {
return SyncedTabsRobot.Transition()
}
+ fun openSyncSignIn(interact: SyncSignInRobot.() -> Unit): SyncSignInRobot.Transition {
+ onView(withId(R.id.mozac_browser_menu_recyclerView)).perform(swipeDown())
+ mDevice.waitNotNull(Until.findObject(By.text("Sign in to sync")), waitingTime)
+ signInToSyncButton().click()
+
+ SyncSignInRobot().interact()
+ return SyncSignInRobot.Transition()
+ }
+
fun openBookmarks(interact: BookmarksRobot.() -> Unit): BookmarksRobot.Transition {
onView(withId(R.id.mozac_browser_menu_recyclerView)).perform(swipeDown())
mDevice.waitNotNull(Until.findObject(By.text("Bookmarks")), waitingTime)
@@ -205,7 +186,7 @@ class ThreeDotMenuMainRobot {
}
fun openHistory(interact: HistoryRobot.() -> Unit): HistoryRobot.Transition {
- onView(withId(R.id.mozac_browser_menu_recyclerView)).perform(ViewActions.swipeDown())
+ onView(withId(R.id.mozac_browser_menu_recyclerView)).perform(swipeDown())
mDevice.waitNotNull(Until.findObject(By.text("History")), waitingTime)
historyButton().click()
@@ -214,7 +195,7 @@ class ThreeDotMenuMainRobot {
}
fun bookmarkPage(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
- mDevice.waitNotNull(Until.findObject(By.desc("Bookmark")), waitingTime)
+ mDevice.waitNotNull(Until.findObject(By.text("Bookmarks")), waitingTime)
addBookmarkButton().click()
BrowserRobot().interact()
@@ -222,10 +203,7 @@ class ThreeDotMenuMainRobot {
}
fun sharePage(interact: LibrarySubMenusMultipleSelectionToolbarRobot.() -> Unit): LibrarySubMenusMultipleSelectionToolbarRobot.Transition {
- mDevice.waitNotNull(Until.findObject(By.desc("Share")), waitingTime)
shareButton().click()
- pressBack()
-
LibrarySubMenusMultipleSelectionToolbarRobot().interact()
return LibrarySubMenusMultipleSelectionToolbarRobot.Transition()
}
@@ -239,7 +217,6 @@ class ThreeDotMenuMainRobot {
}
fun goForward(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
- mDevice.waitNotNull(Until.findObject(By.desc("Forward")), waitingTime)
forwardButton().click()
BrowserRobot().interact()
@@ -273,7 +250,7 @@ class ThreeDotMenuMainRobot {
}
fun refreshPage(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
- mDevice.waitNotNull(Until.findObject(By.desc("Refresh")), waitingTime)
+ assertRefreshButton()
refreshButton().click()
BrowserRobot().interact()
@@ -303,7 +280,7 @@ class ThreeDotMenuMainRobot {
}
fun openFindInPage(interact: FindInPageRobot.() -> Unit): FindInPageRobot.Transition {
- onView(withId(R.id.mozac_browser_menu_recyclerView)).perform(ViewActions.swipeDown())
+ onView(withId(R.id.mozac_browser_menu_recyclerView)).perform(swipeDown())
mDevice.waitNotNull(Until.findObject(By.text("Find in page")), waitingTime)
findInPageButton().click()
@@ -340,6 +317,11 @@ class ThreeDotMenuMainRobot {
}
fun openReaderViewAppearance(interact: ReaderViewRobot.() -> Unit): ReaderViewRobot.Transition {
+ var maxSwipes = 3
+ while (!readerViewAppearanceToggle().exists() && maxSwipes != 0) {
+ threeDotMenuRecyclerView().perform(swipeUp())
+ maxSwipes--
+ }
readerViewAppearanceToggle().click()
ReaderViewRobot().interact()
@@ -347,7 +329,7 @@ class ThreeDotMenuMainRobot {
}
fun addToFirefoxHome(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
- addToFirefoxHomeButton().click()
+ addToTopSitesButton().click()
BrowserRobot().interact()
return BrowserRobot.Transition()
@@ -362,6 +344,11 @@ class ThreeDotMenuMainRobot {
}
fun clickInstall(interact: AddToHomeScreenRobot.() -> Unit): AddToHomeScreenRobot.Transition {
+ var maxSwipes = 3
+ while (!installPWAButton().exists() && maxSwipes != 0) {
+ threeDotMenuRecyclerView().perform(swipeUp())
+ maxSwipes--
+ }
installPWAButton().click()
AddToHomeScreenRobot().interact()
@@ -377,6 +364,11 @@ class ThreeDotMenuMainRobot {
}
fun openSaveToCollection(interact: ThreeDotMenuMainRobot.() -> Unit): ThreeDotMenuMainRobot.Transition {
+ // Ensure the menu is expanded and fully scrolled to the bottom.
+ for (i in 0..3) {
+ threeDotMenuRecyclerView().perform(swipeUp())
+ }
+
mDevice.waitNotNull(Until.findObject(By.text("Save to collection")), waitingTime)
saveCollectionButton().click()
ThreeDotMenuMainRobot().interact()
@@ -401,18 +393,17 @@ class ThreeDotMenuMainRobot {
}
}
}
+private fun threeDotMenuRecyclerView() =
+ onView(withId(R.id.mozac_browser_menu_recyclerView))
private fun threeDotMenuRecyclerViewExists() {
- onView(withId(R.id.mozac_browser_menu_recyclerView)).check(matches(isDisplayed()))
+ threeDotMenuRecyclerView().check(matches(isDisplayed()))
}
-private fun settingsButton() = onView(allOf(withResourceName("text"), withText(R.string.browser_menu_settings)))
-private fun assertSettingsButton() = settingsButton()
- .check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
- .check(matches(isCompletelyDisplayed()))
+private fun settingsButton() = mDevice.findObject(UiSelector().text("Settings"))
+private fun assertSettingsButton() = assertTrue(settingsButton().waitForExists(waitingTime))
-private val addOnsText = if (FeatureFlags.toolbarMenuFeature) "Extensions" else "Add-ons"
-private fun addOnsButton() = onView(allOf(withText(addOnsText)))
+private fun addOnsButton() = onView(allOf(withText("Add-ons")))
private fun assertAddOnsButton() {
onView(withId(R.id.mozac_browser_menu_menuView)).perform(swipeDown())
addOnsButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
@@ -430,27 +421,28 @@ private fun syncedTabsButton() = onView(allOf(withText(R.string.library_synced_t
private fun assertSyncedTabsButton() = syncedTabsButton()
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
+private fun signInToSyncButton() = onView(withText("Sign in to sync"))
+private fun assertSignInToSyncButton() = signInToSyncButton().check(matches(isDisplayed()))
+
private fun helpButton() = onView(allOf(withText(R.string.browser_menu_help)))
private fun assertHelpButton() = helpButton()
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
-private fun forwardButton() = onView(ViewMatchers.withContentDescription("Forward"))
-private fun assertForwardButton() = forwardButton()
- .check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
+private fun forwardButton() = mDevice.findObject(UiSelector().description("Forward"))
+private fun assertForwardButton() = assertTrue(forwardButton().waitForExists(waitingTime))
-private fun addBookmarkButton() = onView(ViewMatchers.withContentDescription("Bookmark"))
+private fun addBookmarkButton() = onView(allOf(withId(R.id.checkbox), withText("Add")))
private fun assertAddBookmarkButton() {
onView(withId(R.id.mozac_browser_menu_menuView)).perform(swipeUp())
addBookmarkButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
}
-private fun editBookmarkButton() = onView(ViewMatchers.withContentDescription("Edit bookmark"))
+private fun editBookmarkButton() = onView(withText("Edit"))
private fun assertEditBookmarkButton() = editBookmarkButton()
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
-private fun refreshButton() = onView(ViewMatchers.withContentDescription("Refresh"))
-private fun assertRefreshButton() = refreshButton()
- .check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
+private fun refreshButton() = mDevice.findObject(UiSelector().description("Refresh"))
+private fun assertRefreshButton() = assertTrue(refreshButton().waitForExists(waitingTime))
private fun stopLoadingButton() = onView(ViewMatchers.withContentDescription("Stop"))
@@ -462,14 +454,13 @@ private fun shareTabButton() = onView(allOf(withText("Share all tabs"))).inRoot(
private fun assertShareTabButton() = shareTabButton()
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
-private fun shareButton() = onView(ViewMatchers.withContentDescription("Share"))
-private fun assertShareButton() = shareButton()
- .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
+private fun shareButton() = mDevice.findObject(UiSelector().description("Share"))
+private fun assertShareButton() = assertTrue(shareButton().waitForExists(waitingTime))
private fun browserViewSaveCollectionButton() = onView(
allOf(
withText("Save to collection"),
- withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)
+ withEffectiveVisibility(Visibility.VISIBLE)
)
)
@@ -492,11 +483,14 @@ private fun assertCollectionNameTextField() = collectionNameTextField()
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
private fun reportSiteIssueButton() = onView(withText("Report Site Issue…"))
+private fun assertReportSiteIssueButton() = reportSiteIssueButton().check(matches(isDisplayed()))
private fun findInPageButton() = onView(allOf(withText("Find in page")))
+
private fun assertFindInPageButton() = findInPageButton()
private fun shareScrim() = onView(withResourceName("closeSharingScrim"))
+
private fun assertShareScrim() =
shareScrim().check(matches(ViewMatchers.withAlpha(ShareFragment.SHOW_PAGE_ALPHA)))
@@ -524,17 +518,29 @@ private fun assertWhatsNewButton() = whatsNewButton()
private fun addToHomeScreenButton() = onView(withText("Add to Home screen"))
private fun readerViewAppearanceToggle() =
- onView(allOf(withText(R.string.browser_menu_read_appearance)))
-
-private fun assertReaderViewAppearanceButton(visible: Boolean) = readerViewAppearanceToggle()
- .check(
- if (visible) matches(withEffectiveVisibility(Visibility.VISIBLE)) else ViewAssertions.doesNotExist()
- )
+ mDevice.findObject(UiSelector().text("Customize reader view"))
+
+private fun assertReaderViewAppearanceButton(visible: Boolean) {
+ var maxSwipes = 3
+ if (visible) {
+ while (!readerViewAppearanceToggle().exists() && maxSwipes != 0) {
+ threeDotMenuRecyclerView().perform(swipeUp())
+ maxSwipes--
+ }
+ assertTrue(readerViewAppearanceToggle().exists())
+ } else {
+ while (!readerViewAppearanceToggle().exists() && maxSwipes != 0) {
+ threeDotMenuRecyclerView().perform(swipeUp())
+ maxSwipes--
+ }
+ assertFalse(readerViewAppearanceToggle().exists())
+ }
+}
-private fun addToFirefoxHomeButton() =
+private fun addToTopSitesButton() =
onView(allOf(withText(R.string.browser_menu_add_to_top_sites)))
-private fun assertAddToFirefoxHome() {
+private fun assertAddToTopSitesButton() {
onView(withId(R.id.mozac_browser_menu_recyclerView))
.perform(
RecyclerViewActions.scrollTo(
@@ -545,6 +551,7 @@ private fun assertAddToFirefoxHome() {
private fun addToMobileHomeButton() =
onView(allOf(withText(R.string.browser_menu_add_to_homescreen)))
+
private fun assertAddToMobileHome() {
onView(withId(R.id.mozac_browser_menu_recyclerView))
.perform(
@@ -554,7 +561,7 @@ private fun assertAddToMobileHome() {
).check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
}
-private fun installPWAButton() = onView(allOf(withId(R.id.highlight_text), withText("Install")))
+private fun installPWAButton() = mDevice.findObject(UiSelector().text("Install"))
private fun desktopSiteButton() =
onView(allOf(withText(R.string.browser_menu_desktop_site)))
@@ -602,3 +609,5 @@ private fun assertShareAllTabsButton() {
.check(
matches(isDisplayed()))
}
+
+private fun assertNewTabButton() = onView(withText("New tab")).check(matches(isDisplayed()))
diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/util/Strings.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/util/Strings.kt
new file mode 100644
index 000000000..3550cf1bc
--- /dev/null
+++ b/app/src/androidTest/java/org/mozilla/fenix/ui/util/Strings.kt
@@ -0,0 +1,9 @@
+/* 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.util
+
+const val STRING_ONBOARDING_ACCOUNT_SIGN_IN_HEADER = "Sync Firefox between devices"
+const val STRING_ONBOARDING_TRACKING_PROTECTION_HEADER = "Always-on privacy"
+const val STRING_ONBOARDING_TOOLBAR_PLACEMENT_HEADER = "Pick your toolbar placement"
diff --git a/app/src/geckoBeta/java/org/mozilla/fenix/engine/GeckoProvider.kt b/app/src/geckoBeta/java/org/mozilla/fenix/engine/GeckoProvider.kt
index 2c58b165a..4fbd61291 100644
--- a/app/src/geckoBeta/java/org/mozilla/fenix/engine/GeckoProvider.kt
+++ b/app/src/geckoBeta/java/org/mozilla/fenix/engine/GeckoProvider.kt
@@ -83,6 +83,7 @@ object GeckoProvider {
val geckoRuntime = GeckoRuntime.create(context, runtimeSettings)
val loginStorageDelegate = GeckoLoginStorageDelegate(storage)
+ @Suppress("Deprecation")
geckoRuntime.loginStorageDelegate = GeckoLoginDelegateWrapper(loginStorageDelegate)
return geckoRuntime
diff --git a/app/src/geckoNightly/java/org/mozilla/fenix/engine/GeckoProvider.kt b/app/src/geckoNightly/java/org/mozilla/fenix/engine/GeckoProvider.kt
index 38c751f30..9a1d2f8b1 100644
--- a/app/src/geckoNightly/java/org/mozilla/fenix/engine/GeckoProvider.kt
+++ b/app/src/geckoNightly/java/org/mozilla/fenix/engine/GeckoProvider.kt
@@ -83,6 +83,7 @@ object GeckoProvider {
val geckoRuntime = GeckoRuntime.create(context, runtimeSettings)
val loginStorageDelegate = GeckoLoginStorageDelegate(storage)
+ @Suppress("Deprecation")
geckoRuntime.loginStorageDelegate = GeckoLoginDelegateWrapper(loginStorageDelegate)
return geckoRuntime
diff --git a/app/src/geckoRelease/java/org/mozilla/fenix/engine/GeckoProvider.kt b/app/src/geckoRelease/java/org/mozilla/fenix/engine/GeckoProvider.kt
index 77303e932..15e3d01e3 100644
--- a/app/src/geckoRelease/java/org/mozilla/fenix/engine/GeckoProvider.kt
+++ b/app/src/geckoRelease/java/org/mozilla/fenix/engine/GeckoProvider.kt
@@ -93,6 +93,7 @@ object GeckoProvider {
val geckoRuntime = GeckoRuntime.create(context, runtimeSettings)
val loginStorageDelegate = GeckoLoginStorageDelegate(storage)
+ @Suppress("Deprecation")
geckoRuntime.loginStorageDelegate = GeckoLoginDelegateWrapper(loginStorageDelegate)
return geckoRuntime
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index b13db0148..a91e307b2 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -25,6 +25,7 @@
- navController?.get()?.navigate(
+ navController?.get()?.navigateBlockingForAsyncNavGraph(
NavGraphDirections.actionGlobalAddonsManagementFragment(addonId)
)
diff --git a/app/src/main/java/org/mozilla/fenix/BrowserDirection.kt b/app/src/main/java/org/mozilla/fenix/BrowserDirection.kt
index e14c6c048..6b3734e59 100644
--- a/app/src/main/java/org/mozilla/fenix/BrowserDirection.kt
+++ b/app/src/main/java/org/mozilla/fenix/BrowserDirection.kt
@@ -31,6 +31,7 @@ enum class BrowserDirection(@IdRes val fragmentId: Int) {
FromAddonDetailsFragment(R.id.addonDetailsFragment),
FromAddonPermissionsDetailsFragment(R.id.addonPermissionsDetailFragment),
FromLoginDetailFragment(R.id.loginDetailFragment),
- FromTabTray(R.id.tabTrayDialogFragment),
+ FromTabTrayDialog(R.id.tabTrayDialogFragment),
+ FromTabTray(R.id.tabsTrayFragment),
FromRecentlyClosed(R.id.recentlyClosedFragment)
}
diff --git a/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt b/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt
index dfeffd860..ec81c9482 100644
--- a/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt
+++ b/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt
@@ -37,15 +37,10 @@ object FeatureFlags {
/**
* Shows new three-dot toolbar menu design.
*/
- val toolbarMenuFeature = Config.channel.isDebug
+ const val toolbarMenuFeature = true
/**
* Enables the tabs tray re-write with Synced Tabs.
*/
- val tabsTrayRewrite = Config.channel.isDebug
-
- /**
- * Enables the updated icon set look and feel.
- */
- val newIconSet = Config.channel.isNightlyOrDebug
+ const val tabsTrayRewrite = true
}
diff --git a/app/src/main/java/org/mozilla/fenix/FenixApplication.kt b/app/src/main/java/org/mozilla/fenix/FenixApplication.kt
index 837ccbf95..a14b00e73 100644
--- a/app/src/main/java/org/mozilla/fenix/FenixApplication.kt
+++ b/app/src/main/java/org/mozilla/fenix/FenixApplication.kt
@@ -13,6 +13,7 @@ import androidx.annotation.CallSuper
import androidx.annotation.VisibleForTesting
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.content.getSystemService
+import androidx.lifecycle.ProcessLifecycleOwner
import androidx.work.Configuration.Builder
import androidx.work.Configuration.Provider
import kotlinx.coroutines.Deferred
@@ -59,6 +60,7 @@ import org.mozilla.fenix.push.PushFxaIntegration
import org.mozilla.fenix.push.WebPushEngineIntegration
import org.mozilla.fenix.session.PerformanceActivityLifecycleCallbacks
import org.mozilla.fenix.session.VisibilityLifecycleCallback
+import org.mozilla.fenix.telemetry.TelemetryLifecycleObserver
import org.mozilla.fenix.utils.BrowsersCache
import java.util.concurrent.TimeUnit
@@ -190,8 +192,12 @@ open class FenixApplication : LocaleAwareApplication(), Provider {
// runStorageMaintenance()
// }
+ components.appStartReasonProvider.registerInAppOnCreate(this)
+ components.startupActivityLog.registerInAppOnCreate(this)
initVisualCompletenessQueueAndQueueTasks()
+ ProcessLifecycleOwner.get().lifecycle.addObserver(TelemetryLifecycleObserver(components.core.store))
+
components.appStartupTelemetry.onFenixApplicationOnCreate()
}
}
diff --git a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt
index c85a39d76..1da6a183f 100644
--- a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt
+++ b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt
@@ -16,6 +16,7 @@ import android.util.AttributeSet
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.View
+import android.view.ActionMode
import android.view.ViewConfiguration
import android.view.WindowManager.LayoutParams.FLAG_SECURE
import androidx.annotation.CallSuper
@@ -32,13 +33,14 @@ import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.NavigationUI
import kotlinx.android.synthetic.main.activity_home.*
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.delay
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
-import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
+import kotlinx.coroutines.Dispatchers.IO
import mozilla.appservices.places.BookmarkRoot
+import mozilla.components.browser.state.action.ContentAction
import mozilla.components.browser.state.search.SearchEngine
import mozilla.components.browser.state.selector.getNormalOrPrivateTabs
import mozilla.components.browser.state.state.SessionState
@@ -77,13 +79,13 @@ import org.mozilla.fenix.exceptions.trackingprotection.TrackingProtectionExcepti
import org.mozilla.fenix.ext.alreadyOnDestination
import org.mozilla.fenix.ext.breadcrumb
import org.mozilla.fenix.ext.components
+import org.mozilla.fenix.ext.navigateBlockingForAsyncNavGraph
import org.mozilla.fenix.ext.measureNoInline
import org.mozilla.fenix.ext.metrics
import org.mozilla.fenix.ext.nav
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.home.HomeFragmentDirections
import org.mozilla.fenix.home.intent.CrashReporterIntentProcessor
-import org.mozilla.fenix.home.intent.DeepLinkIntentProcessor
import org.mozilla.fenix.home.intent.OpenBrowserIntentProcessor
import org.mozilla.fenix.home.intent.OpenSpecificTabIntentProcessor
import org.mozilla.fenix.home.intent.SpeechProcessingIntentProcessor
@@ -92,10 +94,13 @@ 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.recentlyclosed.RecentlyClosedFragmentDirections
+import org.mozilla.fenix.perf.NavGraphProvider
import org.mozilla.fenix.perf.Performance
import org.mozilla.fenix.perf.PerformanceInflater
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.SettingsFragmentDirections
@@ -107,6 +112,7 @@ import org.mozilla.fenix.settings.search.AddSearchEngineFragmentDirections
import org.mozilla.fenix.settings.search.EditCustomSearchEngineFragmentDirections
import org.mozilla.fenix.share.AddNewDeviceFragmentDirections
import org.mozilla.fenix.sync.SyncedTabsFragmentDirections
+import org.mozilla.fenix.tabstray.TabsTrayFragmentDirections
import org.mozilla.fenix.tabtray.TabTrayDialogFragment
import org.mozilla.fenix.tabtray.TabTrayDialogFragmentDirections
import org.mozilla.fenix.theme.DefaultThemeManager
@@ -121,7 +127,7 @@ import java.lang.ref.WeakReference
* - browser screen
*/
@OptIn(ExperimentalCoroutinesApi::class)
-@SuppressWarnings("TooManyFunctions", "LargeClass")
+@SuppressWarnings("TooManyFunctions", "LargeClass", "LongParameterList")
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
@@ -129,7 +135,6 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
// components requires context to access.
protected val homeActivityInitTimeStampNanoSeconds = SystemClock.elapsedRealtimeNanos()
- private var webExtScope: CoroutineScope? = null
lateinit var themeManager: ThemeManager
lateinit var browsingModeManager: BrowsingModeManager
@@ -154,7 +159,6 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
listOf(
SpeechProcessingIntentProcessor(this, components.core.store, components.analytics.metrics),
StartSearchIntentProcessor(components.analytics.metrics),
- DeepLinkIntentProcessor(this, components.analytics.leanplumMetricsService),
OpenBrowserIntentProcessor(this, ::getIntentSessionId),
OpenSpecificTabIntentProcessor(this)
)
@@ -165,6 +169,12 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
private lateinit var navigationToolbar: Toolbar
+ // Tracker for contextual menu (Copy|Search|Select all|etc...)
+ private var actionMode: ActionMode? = null
+
+ private val startupPathProvider = StartupPathProvider()
+ private lateinit var startupTypeTelemetry: StartupTypeTelemetry
+
final override fun onCreate(savedInstanceState: Bundle?): Unit = PerfStartup.homeActivityOnCreate.measureNoInline {
// DO NOT MOVE ANYTHING ABOVE THIS addMarker CALL.
components.core.engine.profiler?.addMarker("Activity.onCreate", "HomeActivity")
@@ -172,6 +182,8 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
components.strictMode.attachListenerToDisablePenaltyDeath(supportFragmentManager)
// There is disk read violations on some devices such as samsung and pixel for android 9/10
components.strictMode.resetAfter(StrictMode.allowThreadDiskReads()) {
+ // Theme setup should always be called before super.onCreate
+ setupThemeAndBrowsingMode(getModeFromIntentOrLastKnown(intent))
super.onCreate(savedInstanceState)
}
@@ -187,8 +199,11 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
components.publicSuffixList.prefetch()
- setupThemeAndBrowsingMode(getModeFromIntentOrLastKnown(intent))
- setContentView(R.layout.activity_home)
+ setContentView(R.layout.activity_home).run {
+ // Do not call anything between setContentView and inflateNavGraphAsync.
+ // It needs to start its job as early as possible.
+ NavGraphProvider.inflateNavGraphAsync(navHost.navController, lifecycleScope)
+ }
// Must be after we set the content view
if (isVisuallyComplete) {
@@ -204,17 +219,8 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
it.start()
}
- if (isActivityColdStarted(
- intent,
- savedInstanceState
- ) && !externalSourceIntentProcessors.any {
- it.process(
- intent,
- navHost.navController,
- this.intent
- )
- }
- ) {
+ if (isActivityColdStarted(intent, savedInstanceState) &&
+ !externalSourceIntentProcessors.any { it.process(intent, navHost.navController, this.intent) }) {
navigateToBrowserOnColdStart()
}
@@ -252,6 +258,10 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
captureSnapshotTelemetryMetrics()
startupTelemetryOnCreateCalled(intent.toSafeIntent(), savedInstanceState != null)
+ startupPathProvider.attachOnActivityOnCreate(lifecycle, intent)
+ startupTypeTelemetry = StartupTypeTelemetry(components.startupStateProvider, startupPathProvider).apply {
+ attachOnHomeActivityOnCreate(lifecycle)
+ }
components.core.requestInterceptor.setNavigationController(navHost.navController)
@@ -262,11 +272,19 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
safeIntent: SafeIntent,
hasSavedInstanceState: Boolean
) {
+ // This function gets overridden by subclasses.
components.appStartupTelemetry.onHomeActivityOnCreate(
safeIntent,
hasSavedInstanceState,
homeActivityInitTimeStampNanoSeconds, rootContainer
)
+
+ components.performance.coldStartupDurationTelemetry.onHomeActivityOnCreate(
+ components.performance.visualCompletenessQueue,
+ components.startupStateProvider,
+ safeIntent,
+ rootContainer
+ )
}
override fun onRestart() {
@@ -455,6 +473,7 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
intent?.let {
handleNewIntent(it)
}
+ startupPathProvider.onIntentReceived(intent)
}
open fun handleNewIntent(intent: Intent) {
@@ -519,6 +538,20 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
else -> super.onCreateView(parent, name, context, attrs)
}
+ override fun onActionModeStarted(mode: ActionMode?) {
+ actionMode = mode
+ super.onActionModeStarted(mode)
+ }
+
+ override fun onActionModeFinished(mode: ActionMode?) {
+ actionMode = null
+ super.onActionModeFinished(mode)
+ }
+
+ fun finishActionMode() {
+ actionMode?.finish().also { actionMode = null }
+ }
+
@Suppress("MagicNumber")
// Defining the positions as constants doesn't seem super useful here.
private fun actionSorter(actions: Array): Array {
@@ -721,10 +754,11 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
customTabSessionId: String? = null,
engine: SearchEngine? = null,
forceSearch: Boolean = false,
- flags: EngineSession.LoadUrlFlags = EngineSession.LoadUrlFlags.none()
+ flags: EngineSession.LoadUrlFlags = EngineSession.LoadUrlFlags.none(),
+ requestDesktopMode: Boolean = false
) {
openToBrowser(from, customTabSessionId)
- load(searchTermOrURL, newTab, engine, forceSearch, flags)
+ load(searchTermOrURL, newTab, engine, forceSearch, flags, requestDesktopMode)
}
fun openToBrowser(from: BrowserDirection, customTabSessionId: String? = null) {
@@ -774,8 +808,10 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
AddonPermissionsDetailsFragmentDirections.actionGlobalBrowser(customTabSessionId)
BrowserDirection.FromLoginDetailFragment ->
LoginDetailFragmentDirections.actionGlobalBrowser(customTabSessionId)
- BrowserDirection.FromTabTray ->
+ BrowserDirection.FromTabTrayDialog ->
TabTrayDialogFragmentDirections.actionGlobalBrowser(customTabSessionId)
+ BrowserDirection.FromTabTray ->
+ TabsTrayFragmentDirections.actionGlobalBrowser(customTabSessionId)
BrowserDirection.FromRecentlyClosed ->
RecentlyClosedFragmentDirections.actionGlobalBrowser(customTabSessionId)
}
@@ -790,7 +826,8 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
newTab: Boolean,
engine: SearchEngine?,
forceSearch: Boolean,
- flags: EngineSession.LoadUrlFlags = EngineSession.LoadUrlFlags.none()
+ flags: EngineSession.LoadUrlFlags = EngineSession.LoadUrlFlags.none(),
+ requestDesktopMode: Boolean = false
) {
val startTime = components.core.engine.profiler?.getProfilerTime()
val mode = browsingModeManager.mode
@@ -807,6 +844,10 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
// and let it try to load whatever was entered.
if ((!forceSearch && searchTermOrURL.isUrl()) || engine == null) {
loadUrlUseCase.invoke(searchTermOrURL.toNormalizedUrl(), flags)
+
+ if (requestDesktopMode) {
+ handleRequestDesktopMode()
+ }
} else {
if (newTab) {
components.useCases.searchUseCases.newTabSearch
@@ -833,6 +874,19 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
}
}
+ internal fun handleRequestDesktopMode() {
+ val requestDesktopSiteUseCase =
+ components.useCases.sessionUseCases.requestDesktopSite
+ requestDesktopSiteUseCase.invoke(true)
+ components.core.store.dispatch(
+ ContentAction.UpdateDesktopModeAction(
+ components.core.store.state.selectedTabId.toString(), true
+ )
+ )
+ // Reset preference value after opening the tab in desktop mode
+ settings().openNextTabInDesktopMode = false
+ }
+
open fun navigateToBrowserOnColdStart() {
// 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
@@ -850,7 +904,11 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
}
override fun getSystemService(name: String): Any? {
- if (LAYOUT_INFLATER_SERVICE == name) {
+ // Issue #17759 had a crash with the PerformanceInflater.kt on Android 5.0 and 5.1
+ // when using the TimePicker. Since the inflater was created for performance monitoring
+ // purposes and that we test on new android versions, this means that any difference in
+ // inflation will be caught on those devices.
+ if (LAYOUT_INFLATER_SERVICE == name && Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP_MR1) {
if (inflater == null) {
inflater = PerformanceInflater(LayoutInflater.from(baseContext), this)
}
@@ -885,7 +943,7 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
webExtensionId = webExtensionState.id,
webExtensionTitle = webExtensionState.name
)
- navHost.navController.navigate(action)
+ navHost.navController.navigateBlockingForAsyncNavGraph(action)
}
/**
diff --git a/app/src/main/java/org/mozilla/fenix/addons/AddonsManagementFragment.kt b/app/src/main/java/org/mozilla/fenix/addons/AddonsManagementFragment.kt
index 64c77733d..ed7780177 100644
--- a/app/src/main/java/org/mozilla/fenix/addons/AddonsManagementFragment.kt
+++ b/app/src/main/java/org/mozilla/fenix/addons/AddonsManagementFragment.kt
@@ -358,7 +358,6 @@ class AddonsManagementFragment : Fragment(R.layout.fragment_add_ons_management)
adapter?.updateAddon(it)
addonProgressOverlay?.visibility = View.GONE
showInstallationDialog(it)
- Addons.hasInstalledAddons.set(true)
}
},
onError = { _, e ->
diff --git a/app/src/main/java/org/mozilla/fenix/addons/AddonsManagementView.kt b/app/src/main/java/org/mozilla/fenix/addons/AddonsManagementView.kt
index 4e9e24cab..d1994c78a 100644
--- a/app/src/main/java/org/mozilla/fenix/addons/AddonsManagementView.kt
+++ b/app/src/main/java/org/mozilla/fenix/addons/AddonsManagementView.kt
@@ -8,6 +8,7 @@ import androidx.navigation.NavController
import mozilla.components.feature.addons.Addon
import mozilla.components.feature.addons.ui.AddonsManagerAdapterDelegate
import org.mozilla.fenix.R
+import org.mozilla.fenix.ext.navigateBlockingForAsyncNavGraph
import org.mozilla.fenix.ext.navigateSafe
/**
@@ -55,6 +56,6 @@ class AddonsManagementView(
AddonsManagementFragmentDirections.actionAddonsManagementFragmentToNotYetSupportedAddonFragment(
unsupportedAddons.toTypedArray()
)
- navController.navigate(directions)
+ navController.navigateBlockingForAsyncNavGraph(directions)
}
}
diff --git a/app/src/main/java/org/mozilla/fenix/addons/InstalledAddonDetailsFragment.kt b/app/src/main/java/org/mozilla/fenix/addons/InstalledAddonDetailsFragment.kt
index c154aa4f9..cb550e2b5 100644
--- a/app/src/main/java/org/mozilla/fenix/addons/InstalledAddonDetailsFragment.kt
+++ b/app/src/main/java/org/mozilla/fenix/addons/InstalledAddonDetailsFragment.kt
@@ -21,10 +21,11 @@ import kotlinx.coroutines.launch
import mozilla.components.feature.addons.Addon
import mozilla.components.feature.addons.AddonManagerException
import mozilla.components.feature.addons.ui.translateName
-import org.mozilla.fenix.GleanMetrics.Addons
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
+import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.ext.components
+import org.mozilla.fenix.ext.navigateBlockingForAsyncNavGraph
import org.mozilla.fenix.ext.showToolbar
import org.mozilla.fenix.ext.runIfFragmentIsAttached
@@ -128,7 +129,6 @@ class InstalledAddonDetailsFragment : Fragment() {
)
)
}
- Addons.hasEnabledAddons.set(true)
}
},
onError = {
@@ -196,6 +196,9 @@ class InstalledAddonDetailsFragment : Fragment() {
view.settings.apply {
isVisible = shouldSettingsBeVisible()
setOnClickListener {
+ requireContext().components.analytics.metrics.track(
+ Event.AddonOpenSetting(addon.id)
+ )
val settingUrl = addon.installedState?.optionsPageUrl ?: return@setOnClickListener
val directions = if (addon.installedState?.openOptionsPageInTab == true) {
val components = it.context.components
@@ -213,7 +216,7 @@ class InstalledAddonDetailsFragment : Fragment() {
InstalledAddonDetailsFragmentDirections
.actionInstalledAddonFragmentToAddonInternalSettingsFragment(addon)
}
- Navigation.findNavController(this).navigate(directions)
+ Navigation.findNavController(this).navigateBlockingForAsyncNavGraph(directions)
}
}
}
@@ -224,7 +227,7 @@ class InstalledAddonDetailsFragment : Fragment() {
InstalledAddonDetailsFragmentDirections.actionInstalledAddonFragmentToAddonDetailsFragment(
addon
)
- Navigation.findNavController(view).navigate(directions)
+ Navigation.findNavController(view).navigateBlockingForAsyncNavGraph(directions)
}
}
@@ -234,7 +237,7 @@ class InstalledAddonDetailsFragment : Fragment() {
InstalledAddonDetailsFragmentDirections.actionInstalledAddonFragmentToAddonPermissionsDetailsFragment(
addon
)
- Navigation.findNavController(view).navigate(directions)
+ Navigation.findNavController(view).navigateBlockingForAsyncNavGraph(directions)
}
}
diff --git a/app/src/main/java/org/mozilla/fenix/android/DefaultActivityLifecycleCallbacks.kt b/app/src/main/java/org/mozilla/fenix/android/DefaultActivityLifecycleCallbacks.kt
new file mode 100644
index 000000000..a47480965
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/android/DefaultActivityLifecycleCallbacks.kt
@@ -0,0 +1,25 @@
+/* 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.android
+
+import android.app.Activity
+import android.app.Application
+import android.os.Bundle
+
+/**
+ * An inheritance of [Application.ActivityLifecycleCallbacks] where each method has a default
+ * implementation that does nothing. This allows classes that extend this interface to have
+ * more concise definitions if they don't implement some methods; this is in the spirit of
+ * other `Default*` classes, such as [androidx.lifecycle.DefaultLifecycleObserver].
+ */
+interface DefaultActivityLifecycleCallbacks : Application.ActivityLifecycleCallbacks {
+ override fun onActivityCreated(activity: Activity, bundle: Bundle?) {}
+ override fun onActivityStarted(activity: Activity) {}
+ override fun onActivityResumed(activity: Activity) {}
+ override fun onActivityPaused(activity: Activity) {}
+ override fun onActivityStopped(activity: Activity) {}
+ override fun onActivitySaveInstanceState(activity: Activity, bundle: Bundle) {}
+ override fun onActivityDestroyed(activity: Activity) {}
+}
diff --git a/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt b/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt
index 29b2ec9af..0307b146a 100644
--- a/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt
+++ b/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt
@@ -80,6 +80,7 @@ import mozilla.components.service.sync.logins.DefaultLoginValidationDelegate
import mozilla.components.support.base.feature.PermissionsFeature
import mozilla.components.support.base.feature.UserInteractionHandler
import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
+import mozilla.components.support.ktx.android.view.exitImmersiveModeIfNeeded
import mozilla.components.support.ktx.android.view.hideKeyboard
import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifAnyChanged
import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged
@@ -127,9 +128,9 @@ import java.lang.ref.WeakReference
import mozilla.components.feature.session.behavior.EngineViewBrowserToolbarBehavior
import mozilla.components.feature.webauthn.WebAuthnFeature
import mozilla.components.support.base.feature.ActivityResultHandler
+import org.mozilla.fenix.ext.navigateBlockingForAsyncNavGraph
+import mozilla.components.support.ktx.android.view.enterToImmersiveMode
import org.mozilla.fenix.GleanMetrics.PerfStartup
-import org.mozilla.fenix.ext.enterToImmersiveMode
-import org.mozilla.fenix.ext.exitImmersiveModeIfNeeded
import org.mozilla.fenix.ext.measureNoInline
import mozilla.components.feature.session.behavior.ToolbarPosition as MozacToolbarPosition
@@ -282,7 +283,8 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Activit
val readerMenuController = DefaultReaderModeController(
readerViewFeature,
view.readerViewControlsBar,
- isPrivate = activity.browsingModeManager.mode.isPrivate
+ isPrivate = activity.browsingModeManager.mode.isPrivate,
+ onReaderModeChanged = { activity.finishActionMode() }
)
val browserToolbarController = DefaultBrowserToolbarController(
store = store,
@@ -370,8 +372,12 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Activit
store = store,
sessionId = customTabSessionId,
stub = view.stubFindInPage,
- engineView = view.engineView,
- toolbar = browserToolbarView.view
+ engineView = engineView,
+ toolbarInfo = FindInPageIntegration.ToolbarInfo(
+ browserToolbarView.view,
+ !context.settings().shouldUseFixedTopToolbar && context.settings().isDynamicToolbarEnabled,
+ !context.settings().shouldUseBottomToolbar
+ )
),
owner = this,
view = view
@@ -552,7 +558,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Activit
showPage = true,
sessionId = getCurrentTab()?.id
)
- findNavController().navigate(directions)
+ findNavController().navigateBlockingForAsyncNavGraph(directions)
}
},
onNeedToRequestPermissions = { permissions ->
@@ -563,7 +569,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Activit
browserAnimator.captureEngineViewAndDrawStatically {
val directions =
NavGraphDirections.actionGlobalSavedLoginsAuthFragment()
- findNavController().navigate(directions)
+ findNavController().navigateBlockingForAsyncNavGraph(directions)
}
}
),
@@ -851,12 +857,6 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Activit
view: View
): List
- @CallSuper
- override fun onStart() {
- super.onStart()
- sitePermissionWifiIntegration.get()?.maybeAddWifiConnectedListener()
- }
-
@VisibleForTesting
internal fun observeRestoreComplete(store: BrowserStore, navController: NavController) {
val activity = activity as HomeActivity
@@ -980,7 +980,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Activit
}
override fun onBackLongPressed(): Boolean {
- findNavController().navigate(
+ findNavController().navigateBlockingForAsyncNavGraph(
NavGraphDirections.actionGlobalTabHistoryDialogFragment(
activeSessionId = customTabSessionId
)
diff --git a/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt b/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt
index 58515a908..ac70ff92c 100644
--- a/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt
+++ b/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt
@@ -33,6 +33,7 @@ import org.mozilla.fenix.components.FenixSnackbar
import org.mozilla.fenix.components.TabCollectionStorage
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.ext.components
+import org.mozilla.fenix.ext.navigateBlockingForAsyncNavGraph
import org.mozilla.fenix.ext.nav
import org.mozilla.fenix.ext.navigateSafe
import org.mozilla.fenix.ext.requireComponents
@@ -264,7 +265,7 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler {
)
.setText(view.context.getString(messageStringRes))
.setAction(requireContext().getString(R.string.create_collection_view)) {
- findNavController().navigate(
+ findNavController().navigateBlockingForAsyncNavGraph(
BrowserFragmentDirections.actionGlobalHome(focusOnAddressBar = false)
)
}
diff --git a/app/src/main/java/org/mozilla/fenix/browser/infobanner/InfoBanner.kt b/app/src/main/java/org/mozilla/fenix/browser/infobanner/InfoBanner.kt
index 1d1ba4041..700803141 100644
--- a/app/src/main/java/org/mozilla/fenix/browser/infobanner/InfoBanner.kt
+++ b/app/src/main/java/org/mozilla/fenix/browser/infobanner/InfoBanner.kt
@@ -33,8 +33,10 @@ open class InfoBanner(
private val dismissText: String,
private val actionText: String? = null,
private val dismissByHiding: Boolean = false,
- private val dismissAction: (() -> Unit)? = null,
- private val actionToPerform: (() -> Unit)? = null
+ @VisibleForTesting
+ internal val dismissAction: (() -> Unit)? = null,
+ @VisibleForTesting
+ internal val actionToPerform: (() -> Unit)? = null
) {
@SuppressLint("InflateParams")
@VisibleForTesting
diff --git a/app/src/main/java/org/mozilla/fenix/browser/readermode/ReaderModeController.kt b/app/src/main/java/org/mozilla/fenix/browser/readermode/ReaderModeController.kt
index 478f5dc89..bc24e7dfb 100644
--- a/app/src/main/java/org/mozilla/fenix/browser/readermode/ReaderModeController.kt
+++ b/app/src/main/java/org/mozilla/fenix/browser/readermode/ReaderModeController.kt
@@ -24,9 +24,11 @@ interface ReaderModeController {
class DefaultReaderModeController(
private val readerViewFeature: ViewBoundFeatureWrapper,
private val readerViewControlsBar: View,
- private val isPrivate: Boolean = false
+ private val isPrivate: Boolean = false,
+ private val onReaderModeChanged: () -> Unit = {}
) : ReaderModeController {
override fun hideReaderView() {
+ onReaderModeChanged()
readerViewFeature.withFeature {
it.hideReaderView()
it.hideControls()
@@ -34,6 +36,7 @@ class DefaultReaderModeController(
}
override fun showReaderView() {
+ onReaderModeChanged()
readerViewFeature.withFeature { it.showReaderView() }
}
diff --git a/app/src/main/java/org/mozilla/fenix/collections/CollectionsDialog.kt b/app/src/main/java/org/mozilla/fenix/collections/CollectionsDialog.kt
new file mode 100644
index 000000000..a5c6a8ba8
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/collections/CollectionsDialog.kt
@@ -0,0 +1,135 @@
+/* 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.collections
+
+import android.content.Context
+import android.view.LayoutInflater
+import android.widget.EditText
+import androidx.appcompat.app.AlertDialog
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import kotlinx.coroutines.MainScope
+import kotlinx.coroutines.launch
+import mozilla.components.browser.state.state.TabSessionState
+import mozilla.components.feature.tab.collections.TabCollection
+import mozilla.components.support.ktx.android.view.showKeyboard
+import org.mozilla.fenix.R
+import org.mozilla.fenix.components.TabCollectionStorage
+import org.mozilla.fenix.ext.getDefaultCollectionNumber
+
+/**
+ * A lambda that is invoked when a confirmation button in a [CollectionsDialog] is clicked.
+ *
+ * A [TabCollection] of the selected collected is passed to the delegate when confirmed. If null,
+ * then a new collection is created.
+ *
+ * A list of [TabSessionState] is returned that will be put into the collections storage.
+ */
+typealias OnPositiveButtonClick = (collection: TabCollection?) -> List
+
+/**
+ * A lambda that is invoked when a cancel button in a [CollectionsDialog] is clicked.
+ */
+typealias OnNegativeButtonClick = () -> Unit
+
+/**
+ * A data class for creating a dialog to prompt adding/creating a collection. See also [show].
+ *
+ * @property onPositiveButtonClick Invoked when a user clicks on a confirmation button in the dialog.
+ * @property onNegativeButtonClick Invoked when a user clicks on a cancel button in the dialog.
+ */
+data class CollectionsDialog(
+ val storage: TabCollectionStorage,
+ val onPositiveButtonClick: OnPositiveButtonClick,
+ val onNegativeButtonClick: OnNegativeButtonClick
+)
+
+/**
+ * Create and display a [CollectionsDialog] using [AlertDialog].
+ */
+fun CollectionsDialog.show(
+ context: Context
+) {
+ if (storage.cachedTabCollections.isEmpty()) {
+ showAddNewDialog(context, storage)
+ return
+ }
+
+ val collections = storage.cachedTabCollections.map { it.title }
+ val layout = LayoutInflater.from(context).inflate(R.layout.add_new_collection_dialog, null)
+ val list = layout.findViewById(R.id.recycler_view)
+
+ val builder = AlertDialog.Builder(context).setTitle(R.string.tab_tray_select_collection)
+ .setView(layout)
+ .setPositiveButton(android.R.string.ok) { dialog, _ ->
+ val selectedCollection =
+ (list.adapter as CollectionsListAdapter).getSelectedCollection()
+ val collection = storage.cachedTabCollections[selectedCollection]
+ val sessionList = onPositiveButtonClick.invoke(collection)
+
+ MainScope().launch {
+ storage.addTabsToCollection(collection, sessionList)
+ }
+
+ dialog.dismiss()
+ }.setNegativeButton(android.R.string.cancel) { dialog, _ ->
+ onNegativeButtonClick.invoke()
+
+ dialog.cancel()
+ }
+
+ val dialog = builder.create()
+ val collectionNames =
+ arrayOf(context.getString(R.string.tab_tray_add_new_collection)) + collections
+ val collectionsListAdapter = CollectionsListAdapter(collectionNames) {
+ dialog.dismiss()
+ showAddNewDialog(context, storage)
+ }
+
+ list.apply {
+ layoutManager = LinearLayoutManager(context)
+ adapter = collectionsListAdapter
+ }
+ dialog.show()
+}
+
+internal fun CollectionsDialog.showAddNewDialog(
+ context: Context,
+ collectionsStorage: TabCollectionStorage
+) {
+ val layout = LayoutInflater.from(context).inflate(R.layout.name_collection_dialog, null)
+ val collectionNameEditText: EditText = layout.findViewById(R.id.collection_name)
+
+ collectionNameEditText.setText(
+ context.getString(
+ R.string.create_collection_default_name,
+ collectionsStorage.cachedTabCollections.getDefaultCollectionNumber()
+ )
+ )
+
+ AlertDialog.Builder(context)
+ .setTitle(R.string.tab_tray_add_new_collection)
+ .setView(layout).setPositiveButton(android.R.string.ok) { dialog, _ ->
+ val sessionList = onPositiveButtonClick.invoke(null)
+
+ MainScope().launch {
+ storage.createCollection(
+ collectionNameEditText.text.toString(),
+ sessionList
+ )
+ }
+
+ dialog.dismiss()
+ }
+ .setNegativeButton(android.R.string.cancel) { dialog, _ ->
+ onNegativeButtonClick.invoke()
+ dialog.cancel()
+ }
+ .create()
+ .show()
+
+ collectionNameEditText.setSelection(0, collectionNameEditText.text.length)
+ collectionNameEditText.showKeyboard()
+}
diff --git a/app/src/main/java/org/mozilla/fenix/tabtray/CollectionsAdapter.kt b/app/src/main/java/org/mozilla/fenix/collections/CollectionsListAdapter.kt
similarity index 90%
rename from app/src/main/java/org/mozilla/fenix/tabtray/CollectionsAdapter.kt
rename to app/src/main/java/org/mozilla/fenix/collections/CollectionsListAdapter.kt
index 41fba7213..71ede56df 100644
--- a/app/src/main/java/org/mozilla/fenix/tabtray/CollectionsAdapter.kt
+++ b/app/src/main/java/org/mozilla/fenix/collections/CollectionsListAdapter.kt
@@ -2,7 +2,7 @@
* 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.tabtray
+package org.mozilla.fenix.collections
import android.view.LayoutInflater
import android.view.ViewGroup
@@ -14,10 +14,14 @@ import androidx.recyclerview.widget.RecyclerView
import mozilla.components.support.ktx.android.view.putCompoundDrawablesRelativeWithIntrinsicBounds
import org.mozilla.fenix.R
-internal class CollectionsAdapter(
+/**
+ * An adapter for displaying an option to create a new collection and the list of existing
+ * collections.
+ */
+class CollectionsListAdapter(
private val collections: Array,
private val onNewCollectionClicked: () -> Unit
-) : RecyclerView.Adapter() {
+) : RecyclerView.Adapter() {
@VisibleForTesting
internal var checkedPosition = 1
diff --git a/app/src/main/java/org/mozilla/fenix/components/AbstractBinding.kt b/app/src/main/java/org/mozilla/fenix/components/AbstractBinding.kt
new file mode 100644
index 000000000..cdc9fc755
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/components/AbstractBinding.kt
@@ -0,0 +1,43 @@
+/* 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.components
+
+import androidx.annotation.CallSuper
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.Flow
+import mozilla.components.lib.state.Action
+import mozilla.components.lib.state.State
+import mozilla.components.lib.state.Store
+import mozilla.components.lib.state.ext.flowScoped
+import mozilla.components.support.base.feature.LifecycleAwareFeature
+
+/**
+ * Helper class for creating small binding classes that are responsible for reacting to state
+ * changes.
+ *
+ * Taken with ♥️ from Focus.
+ */
+abstract class AbstractBinding(
+ private val store: Store
+) : LifecycleAwareFeature {
+ private var scope: CoroutineScope? = null
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @CallSuper
+ override fun start() {
+ scope = store.flowScoped { flow ->
+ onState(flow)
+ }
+ }
+
+ @CallSuper
+ override fun stop() {
+ scope?.cancel()
+ }
+
+ abstract suspend fun onState(flow: Flow)
+}
diff --git a/app/src/main/java/org/mozilla/fenix/components/Analytics.kt b/app/src/main/java/org/mozilla/fenix/components/Analytics.kt
index 109083de1..dddf03456 100644
--- a/app/src/main/java/org/mozilla/fenix/components/Analytics.kt
+++ b/app/src/main/java/org/mozilla/fenix/components/Analytics.kt
@@ -22,7 +22,6 @@ import org.mozilla.fenix.R
import org.mozilla.fenix.ReleaseChannel
import org.mozilla.fenix.components.metrics.AdjustMetricsService
import org.mozilla.fenix.components.metrics.GleanMetricsService
-import org.mozilla.fenix.components.metrics.LeanplumMetricsService
import org.mozilla.fenix.components.metrics.MetricController
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.settings
@@ -88,13 +87,10 @@ class Analytics(
)
}
- val leanplumMetricsService by lazyMonitored { LeanplumMetricsService(context as Application) }
-
val metrics: MetricController by lazyMonitored {
MetricController.create(
listOf(
GleanMetricsService(context, lazy { context.components.core.store }),
- leanplumMetricsService,
AdjustMetricsService(context as Application)
),
isDataTelemetryEnabled = { context.settings().isTelemetryEnabled },
diff --git a/app/src/main/java/org/mozilla/fenix/components/BackgroundServices.kt b/app/src/main/java/org/mozilla/fenix/components/BackgroundServices.kt
index 086d89771..eaa06cedf 100644
--- a/app/src/main/java/org/mozilla/fenix/components/BackgroundServices.kt
+++ b/app/src/main/java/org/mozilla/fenix/components/BackgroundServices.kt
@@ -216,14 +216,9 @@ internal class TelemetryAccountObserver(
}?.let {
metricController.track(it)
}
-
- // Used by Leanplum as a context variable.
- settings.fxaSignedIn = true
}
override fun onLoggedOut() {
metricController.track(Event.SyncAuthSignOut)
- // Used by Leanplum as a context variable.
- settings.fxaSignedIn = false
}
}
diff --git a/app/src/main/java/org/mozilla/fenix/components/Components.kt b/app/src/main/java/org/mozilla/fenix/components/Components.kt
index 6522fed85..a9e973a3c 100644
--- a/app/src/main/java/org/mozilla/fenix/components/Components.kt
+++ b/app/src/main/java/org/mozilla/fenix/components/Components.kt
@@ -24,10 +24,13 @@ import org.mozilla.fenix.Config
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.autofill.AutofillUnlockActivity
-import org.mozilla.fenix.perf.StrictModeManager
import org.mozilla.fenix.components.metrics.AppStartupTelemetry
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.settings
+import org.mozilla.fenix.perf.AppStartReasonProvider
+import org.mozilla.fenix.perf.StartupActivityLog
+import org.mozilla.fenix.perf.StartupStateProvider
+import org.mozilla.fenix.perf.StrictModeManager
import org.mozilla.fenix.perf.lazyMonitored
import org.mozilla.fenix.utils.ClipboardHandler
import org.mozilla.fenix.utils.Mockable
@@ -68,7 +71,8 @@ class Components(private val context: Context) {
core.sessionManager,
core.store,
core.webAppShortcutManager,
- core.topSitesStorage
+ core.topSitesStorage,
+ core.bookmarksStorage
)
}
@@ -173,4 +177,8 @@ class Components(private val context: Context) {
httpClient = core.client
)
}
+
+ val appStartReasonProvider by lazyMonitored { AppStartReasonProvider() }
+ val startupActivityLog by lazyMonitored { StartupActivityLog() }
+ val startupStateProvider by lazyMonitored { StartupStateProvider(startupActivityLog, appStartReasonProvider) }
}
diff --git a/app/src/main/java/org/mozilla/fenix/components/Core.kt b/app/src/main/java/org/mozilla/fenix/components/Core.kt
index a560f1288..837d00ee4 100644
--- a/app/src/main/java/org/mozilla/fenix/components/Core.kt
+++ b/app/src/main/java/org/mozilla/fenix/components/Core.kt
@@ -35,6 +35,7 @@ import mozilla.components.feature.downloads.DownloadMiddleware
import mozilla.components.feature.logins.exceptions.LoginExceptionStorage
import mozilla.components.feature.media.MediaSessionFeature
import mozilla.components.feature.media.middleware.RecordingDevicesMiddleware
+import mozilla.components.feature.prompts.PromptMiddleware
import mozilla.components.feature.pwa.ManifestStorage
import mozilla.components.feature.pwa.WebAppShortcutManager
import mozilla.components.feature.readerview.ReaderViewMiddleware
@@ -64,7 +65,6 @@ import org.mozilla.fenix.BuildConfig
import org.mozilla.fenix.Config
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
-import org.mozilla.fenix.TelemetryMiddleware
import org.mozilla.fenix.components.search.SearchMigration
import org.mozilla.fenix.downloads.DownloadService
import org.mozilla.fenix.ext.components
@@ -76,6 +76,7 @@ import org.mozilla.fenix.search.telemetry.ads.AdsTelemetry
import org.mozilla.fenix.search.telemetry.incontent.InContentTelemetry
import org.mozilla.fenix.settings.SupportUtils
import org.mozilla.fenix.settings.advanced.getSelectedLocale
+import org.mozilla.fenix.telemetry.TelemetryMiddleware
import org.mozilla.fenix.utils.Mockable
import org.mozilla.fenix.utils.getUndoDelay
@@ -194,7 +195,8 @@ class Core(
additionalBundledSearchEngineIds = listOf("reddit", "youtube"),
migration = SearchMigration(context)
),
- RecordingDevicesMiddleware(context)
+ RecordingDevicesMiddleware(context),
+ PromptMiddleware()
)
BrowserStore(
@@ -335,6 +337,13 @@ class Core(
SupportUtils.JD_URL
)
)
+
+ defaultTopSites.add(
+ Pair(
+ context.getString(R.string.default_top_site_pdd),
+ SupportUtils.PDD_URL
+ )
+ )
} else {
defaultTopSites.add(
Pair(
diff --git a/app/src/main/java/org/mozilla/fenix/components/FindInPageIntegration.kt b/app/src/main/java/org/mozilla/fenix/components/FindInPageIntegration.kt
index 1f03bb8d4..642e65a41 100644
--- a/app/src/main/java/org/mozilla/fenix/components/FindInPageIntegration.kt
+++ b/app/src/main/java/org/mozilla/fenix/components/FindInPageIntegration.kt
@@ -5,7 +5,10 @@
package org.mozilla.fenix.components
import android.view.View
+import android.view.ViewGroup.MarginLayoutParams
import android.view.ViewStub
+import androidx.annotation.VisibleForTesting
+import androidx.core.view.isVisible
import mozilla.components.browser.state.selector.findCustomTabOrSelectedTab
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.browser.toolbar.BrowserToolbar
@@ -13,30 +16,100 @@ import mozilla.components.concept.engine.EngineView
import mozilla.components.feature.findinpage.FindInPageFeature
import mozilla.components.feature.findinpage.view.FindInPageView
import mozilla.components.support.base.feature.LifecycleAwareFeature
+import org.mozilla.fenix.components.FindInPageIntegration.ToolbarInfo
import org.mozilla.fenix.utils.Mockable
+/**
+ * BrowserFragment delegate to handle all layout updates needed to show or hide the find in page bar.
+ *
+ * @param store [BrowserStore]
+ * @param sessionId ID of the [store] session in which the query will be performed.
+ * @param engineView the browser in which the queries will be made and which needs to be better positioned
+ * to suit the find in page bar.
+ * @param toolbarInfo [ToolbarInfo] used to configure the [BrowserToolbar] while the find in page bar is shown.
+ */
@Mockable
class FindInPageIntegration(
private val store: BrowserStore,
private val sessionId: String? = null,
stub: ViewStub,
private val engineView: EngineView,
- private val toolbar: BrowserToolbar
+ private val toolbarInfo: ToolbarInfo
) : InflationAwareFeature(stub) {
override fun onViewInflated(view: View): LifecycleAwareFeature {
return FindInPageFeature(store, view as FindInPageView, engineView) {
- toolbar.visibility = View.VISIBLE
+ restorePreviousLayout()
+
view.visibility = View.GONE
}
}
override fun onLaunch(view: View, feature: LifecycleAwareFeature) {
store.state.findCustomTabOrSelectedTab(sessionId)?.let { tab ->
- // Always hide the toolbar and display find in page query
- toolbar.visibility = View.GONE
+ prepareLayoutForFindBar()
+
view.visibility = View.VISIBLE
(feature as FindInPageFeature).bind(tab)
- view.layoutParams.height = toolbar.height
+ view.layoutParams.height = toolbarInfo.toolbar.height
}
}
+
+ @VisibleForTesting
+ internal fun restorePreviousLayout() {
+ toolbarInfo.toolbar.isVisible = true
+
+ val engineViewParent = getEngineViewParent()
+ val engineViewParentParams = getEngineViewsParentLayoutParams()
+ if (toolbarInfo.isToolbarPlacedAtTop) {
+ if (toolbarInfo.isToolbarDynamic) {
+ engineViewParent.translationY = toolbarInfo.toolbar.height.toFloat()
+ engineViewParentParams.bottomMargin = 0
+ } else {
+ engineViewParent.translationY = 0f
+ }
+ } else {
+ if (toolbarInfo.isToolbarDynamic) {
+ engineViewParentParams.bottomMargin = 0
+ }
+ }
+ }
+
+ @VisibleForTesting
+ internal fun prepareLayoutForFindBar() {
+ toolbarInfo.toolbar.isVisible = false
+
+ val engineViewParent = getEngineViewParent()
+ val engineViewParentParams = getEngineViewsParentLayoutParams()
+ if (toolbarInfo.isToolbarPlacedAtTop) {
+ if (toolbarInfo.isToolbarDynamic) {
+ // With a dynamic toolbar the EngineView extends to the entire (top and bottom) of the screen.
+ // And now with the toolbar expanded it is translated down immediately below the toolbar.
+ engineViewParent.translationY = 0f
+ engineViewParentParams.bottomMargin = toolbarInfo.toolbar.height
+ } else {
+ // With a fixed toolbar the EngineView is anchored below the toolbar with 0 Y translation.
+ engineViewParent.translationY = -toolbarInfo.toolbar.height.toFloat()
+ }
+ } else {
+ // With a bottom toolbar the EngineView is already anchored to the top of the screen.
+ // Need just to ensure space for the find in page bar under the engineView.
+ engineViewParentParams.bottomMargin = toolbarInfo.toolbar.height
+ }
+ }
+
+ @VisibleForTesting
+ internal fun getEngineViewParent() = engineView.asView().parent as View
+
+ @VisibleForTesting
+ internal fun getEngineViewsParentLayoutParams() = getEngineViewParent().layoutParams as MarginLayoutParams
+
+ /**
+ * Holder of all details needed about the Toolbar.
+ * Used to modify the layout of BrowserToolbar while the find in page bar is shown.
+ */
+ data class ToolbarInfo(
+ val toolbar: BrowserToolbar,
+ val isToolbarDynamic: Boolean,
+ val isToolbarPlacedAtTop: Boolean
+ )
}
diff --git a/app/src/main/java/org/mozilla/fenix/components/FxaServer.kt b/app/src/main/java/org/mozilla/fenix/components/FxaServer.kt
index c863f748e..b6f66cc7b 100644
--- a/app/src/main/java/org/mozilla/fenix/components/FxaServer.kt
+++ b/app/src/main/java/org/mozilla/fenix/components/FxaServer.kt
@@ -6,6 +6,7 @@ package org.mozilla.fenix.components
import android.content.Context
import mozilla.components.service.fxa.ServerConfig
import mozilla.components.service.fxa.ServerConfig.Server
+import org.mozilla.fenix.Config
import org.mozilla.fenix.ext.settings
/**
@@ -17,10 +18,18 @@ object FxaServer {
const val REDIRECT_URL = "urn:ietf:wg:oauth:2.0:oob:oauth-redirect-webchannel"
fun config(context: Context): ServerConfig {
+ // If a server override is configured, use that. Otherwise:
+ // - for all channels other than Mozilla Online, use Server.Release.
+ // - for Mozilla Online channel, if domestic server is allowed, use Server.CHINA; otherwise, use Server.RELEASE
val serverOverride = context.settings().overrideFxAServer
val tokenServerOverride = context.settings().overrideSyncTokenServer.ifEmpty { null }
if (serverOverride.isEmpty()) {
- return ServerConfig(Server.RELEASE, CLIENT_ID, REDIRECT_URL, tokenServerOverride)
+ val releaseServer = if (Config.channel.isMozillaOnline && context.settings().allowDomesticChinaFxaServer) {
+ Server.CHINA
+ } else {
+ Server.RELEASE
+ }
+ return ServerConfig(releaseServer, CLIENT_ID, REDIRECT_URL, tokenServerOverride)
}
return ServerConfig(serverOverride, CLIENT_ID, REDIRECT_URL, tokenServerOverride)
}
diff --git a/app/src/main/java/org/mozilla/fenix/components/PerformanceComponent.kt b/app/src/main/java/org/mozilla/fenix/components/PerformanceComponent.kt
index 8f0a677c4..3bbb338b9 100644
--- a/app/src/main/java/org/mozilla/fenix/components/PerformanceComponent.kt
+++ b/app/src/main/java/org/mozilla/fenix/components/PerformanceComponent.kt
@@ -5,6 +5,7 @@
package org.mozilla.fenix.components
import mozilla.components.support.utils.RunWhenReadyQueue
+import org.mozilla.fenix.perf.ColdStartupDurationTelemetry
import org.mozilla.fenix.perf.VisualCompletenessQueue
import org.mozilla.fenix.perf.lazyMonitored
@@ -13,4 +14,5 @@ import org.mozilla.fenix.perf.lazyMonitored
*/
class PerformanceComponent {
val visualCompletenessQueue by lazyMonitored { VisualCompletenessQueue(RunWhenReadyQueue()) }
+ val coldStartupDurationTelemetry by lazyMonitored { ColdStartupDurationTelemetry() }
}
diff --git a/app/src/main/java/org/mozilla/fenix/components/UseCases.kt b/app/src/main/java/org/mozilla/fenix/components/UseCases.kt
index 9249c8ab8..bd9a4fb21 100644
--- a/app/src/main/java/org/mozilla/fenix/components/UseCases.kt
+++ b/app/src/main/java/org/mozilla/fenix/components/UseCases.kt
@@ -8,6 +8,7 @@ import android.content.Context
import mozilla.components.browser.session.SessionManager
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.concept.engine.Engine
+import mozilla.components.concept.storage.BookmarksStorage
import mozilla.components.feature.app.links.AppLinksUseCases
import mozilla.components.feature.contextmenu.ContextMenuUseCases
import mozilla.components.feature.downloads.DownloadsUseCases
@@ -23,6 +24,7 @@ import mozilla.components.feature.tabs.TabsUseCases
import mozilla.components.feature.top.sites.TopSitesStorage
import mozilla.components.feature.top.sites.TopSitesUseCases
import mozilla.components.support.locale.LocaleUseCases
+import org.mozilla.fenix.components.bookmarks.BookmarksUseCase
import org.mozilla.fenix.perf.lazyMonitored
import org.mozilla.fenix.utils.Mockable
@@ -38,7 +40,8 @@ class UseCases(
private val sessionManager: SessionManager,
private val store: BrowserStore,
private val shortcutManager: WebAppShortcutManager,
- private val topSitesStorage: TopSitesStorage
+ private val topSitesStorage: TopSitesStorage,
+ private val bookmarksStorage: BookmarksStorage
) {
/**
* Use cases that provide engine interactions for a given browser session.
@@ -94,4 +97,9 @@ class UseCases(
* Use cases that handle locale management.
*/
val localeUseCases by lazyMonitored { LocaleUseCases(store) }
+
+ /**
+ * Use cases that provide bookmark management.
+ */
+ val bookmarksUseCases by lazyMonitored { BookmarksUseCase(bookmarksStorage) }
}
diff --git a/app/src/main/java/org/mozilla/fenix/components/accounts/FenixAccountManager.kt b/app/src/main/java/org/mozilla/fenix/components/accounts/FenixAccountManager.kt
new file mode 100644
index 000000000..fe5397bd1
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/components/accounts/FenixAccountManager.kt
@@ -0,0 +1,33 @@
+/* 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.components.accounts
+
+import android.content.Context
+import mozilla.components.service.fxa.manager.FxaAccountManager
+import org.mozilla.fenix.ext.components
+
+/**
+ * Component which holds a reference to [FxaAccountManager]. Manages account authentication,
+ * profiles, and profile state observers.
+ */
+open class FenixAccountManager(context: Context) {
+ val accountManager = context.components.backgroundServices.accountManager
+
+ val authenticatedAccount
+ get() = accountManager.authenticatedAccount() != null
+
+ val accountProfileEmail
+ get() = accountManager.accountProfile()?.email
+
+ /**
+ * Check if the current account is signed in and authenticated.
+ */
+ fun signedInToFxa(): Boolean {
+ val account = accountManager.authenticatedAccount()
+ val needsReauth = accountManager.accountNeedsReauth()
+
+ return account != null && !needsReauth
+ }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/components/bookmarks/BookmarksUseCase.kt b/app/src/main/java/org/mozilla/fenix/components/bookmarks/BookmarksUseCase.kt
new file mode 100644
index 000000000..e381312fa
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/components/bookmarks/BookmarksUseCase.kt
@@ -0,0 +1,42 @@
+/* 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.components.bookmarks
+
+import androidx.annotation.WorkerThread
+import mozilla.appservices.places.BookmarkRoot
+import mozilla.components.concept.storage.BookmarksStorage
+
+/**
+ * Use cases that allow for modifying bookmarks.
+ */
+class BookmarksUseCase(storage: BookmarksStorage) {
+
+ class AddBookmarksUseCase internal constructor(private val storage: BookmarksStorage) {
+
+ /**
+ * Adds a new bookmark with the provided [url] and [title].
+ *
+ * @return The result if the operation was executed or not. A bookmark may not be added if
+ * one with the identical [url] already exists.
+ */
+ @WorkerThread
+ suspend operator fun invoke(url: String, title: String, position: Int? = null): Boolean {
+ val canAdd = storage.getBookmarksWithUrl(url).firstOrNull { it.url == it.url } == null
+
+ if (canAdd) {
+ storage.addItem(
+ BookmarkRoot.Mobile.id,
+ url = url,
+ title = title,
+ position = position
+ )
+ }
+
+ return canAdd
+ }
+ }
+
+ val addBookmark by lazy { AddBookmarksUseCase(storage) }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/components/metrics/AppStartupTelemetry.kt b/app/src/main/java/org/mozilla/fenix/components/metrics/AppStartupTelemetry.kt
index dc3411304..333fda67f 100644
--- a/app/src/main/java/org/mozilla/fenix/components/metrics/AppStartupTelemetry.kt
+++ b/app/src/main/java/org/mozilla/fenix/components/metrics/AppStartupTelemetry.kt
@@ -33,6 +33,12 @@ import java.lang.reflect.Modifier.PRIVATE
* Sample = [source = COLD, type = APP_ICON, hasSavedInstanceState = false,launchTimeNanoSeconds = 1824000000]
* The basic idea is to collect these metrics from different phases of startup through
* [AppAllStartup] and finally report them on Activity's onResume() function.
+ *
+ * **THIS CLASS HAS A KNOWN FLAW:** for COLD start, it doesn't take into account if the process is
+ * already running when the app starts, possibly inflating results (e.g. a Service started the
+ * process 20min ago and only now is HomeActivity launching). Future telemetry implementations should
+ * probably move in the ideological direction of [org.mozilla.fenix.perf.ColdStartupDurationTelemetry]:
+ * simplicity rather than comprehensiveness.
*/
@Suppress("TooManyFunctions")
class AppStartupTelemetry(
diff --git a/app/src/main/java/org/mozilla/fenix/components/metrics/Event.kt b/app/src/main/java/org/mozilla/fenix/components/metrics/Event.kt
index c6d17dfed..aff2f88d8 100644
--- a/app/src/main/java/org/mozilla/fenix/components/metrics/Event.kt
+++ b/app/src/main/java/org/mozilla/fenix/components/metrics/Event.kt
@@ -182,6 +182,7 @@ sealed class Event {
object ClosedExistingTab : Event()
object TabsTrayPrivateModeTapped : Event()
object TabsTrayNormalModeTapped : Event()
+ object TabsTraySyncedModeTapped : Event()
object NewTabTapped : Event()
object NewPrivateTabTapped : Event()
object TabsTrayMenuOpened : Event()
@@ -224,6 +225,17 @@ sealed class Event {
object SearchSuggestionClicked : Event()
object OpenedTabSuggestionClicked : Event()
+ // Set default browser experiment metrics
+ object SetDefaultBrowserNewTabClicked : Event()
+ object CloseExperimentCardClicked : Event()
+ object ToolbarMenuShown : Event()
+ object SetDefaultBrowserToolbarMenuClicked : Event()
+ object SetDefaultBrowserSettingsScreenClicked : Event()
+
+ // Home menu interaction
+ object HomeMenuSettingsItemClicked : Event()
+ object HomeScreenDisplayed : Event()
+
// Interaction events with extras
data class TopSiteSwipeCarousel(val page: Int) : Event() {
@@ -325,6 +337,11 @@ sealed class Event {
get() = hashMapOf(Addons.openAddonInToolbarMenuKeys.addonId to addonId)
}
+ data class AddonOpenSetting(val addonId: String) : Event() {
+ override val extras: Map?
+ get() = hashMapOf(Addons.openAddonSettingKeys.addonId to addonId)
+ }
+
data class TipDisplayed(val identifier: String) : Event() {
override val extras: Map?
get() = hashMapOf(Tip.displayedKeys.identifier to identifier)
@@ -577,7 +594,7 @@ sealed class Event {
NEW_PRIVATE_TAB, SHARE, BACK, FORWARD, RELOAD, STOP, OPEN_IN_FENIX,
SAVE_TO_COLLECTION, ADD_TO_TOP_SITES, ADD_TO_HOMESCREEN, QUIT, READER_MODE_ON,
READER_MODE_OFF, OPEN_IN_APP, BOOKMARK, READER_MODE_APPEARANCE, ADDONS_MANAGER,
- BOOKMARKS, HISTORY, SYNC_TABS, DOWNLOADS
+ BOOKMARKS, HISTORY, SYNC_TABS, DOWNLOADS, SET_DEFAULT_BROWSER, SYNC_ACCOUNT
}
override val extras: Map?
@@ -597,7 +614,7 @@ sealed class Event {
data class AutoPlaySettingChanged(val setting: AutoplaySetting) : Event() {
enum class AutoplaySetting {
- BLOCK_CELLULAR, BLOCK_AUDIO, BLOCK_ALL
+ BLOCK_CELLULAR, BLOCK_AUDIO, BLOCK_ALL, ALLOW_ALL
}
override val extras: Map?
diff --git a/app/src/main/java/org/mozilla/fenix/components/metrics/GleanMetricsService.kt b/app/src/main/java/org/mozilla/fenix/components/metrics/GleanMetricsService.kt
index 6ae3601b3..7eb8ee48b 100644
--- a/app/src/main/java/org/mozilla/fenix/components/metrics/GleanMetricsService.kt
+++ b/app/src/main/java/org/mozilla/fenix/components/metrics/GleanMetricsService.kt
@@ -32,8 +32,11 @@ import org.mozilla.fenix.GleanMetrics.DownloadsMisc
import org.mozilla.fenix.GleanMetrics.DownloadsManagement
import org.mozilla.fenix.GleanMetrics.ErrorPage
import org.mozilla.fenix.GleanMetrics.Events
+import org.mozilla.fenix.GleanMetrics.ExperimentsDefaultBrowser
import org.mozilla.fenix.GleanMetrics.FindInPage
import org.mozilla.fenix.GleanMetrics.History
+import org.mozilla.fenix.GleanMetrics.HomeMenu
+import org.mozilla.fenix.GleanMetrics.HomeScreen
import org.mozilla.fenix.GleanMetrics.LoginDialog
import org.mozilla.fenix.GleanMetrics.Logins
import org.mozilla.fenix.GleanMetrics.MasterPassword
@@ -52,6 +55,8 @@ import org.mozilla.fenix.GleanMetrics.SearchDefaultEngine
import org.mozilla.fenix.GleanMetrics.SearchShortcuts
import org.mozilla.fenix.GleanMetrics.SearchSuggestions
import org.mozilla.fenix.GleanMetrics.SearchWidget
+import org.mozilla.fenix.GleanMetrics.SetDefaultNewtabExperiment
+import org.mozilla.fenix.GleanMetrics.SetDefaultSettingExperiment
import org.mozilla.fenix.GleanMetrics.SyncAccount
import org.mozilla.fenix.GleanMetrics.SyncAuth
import org.mozilla.fenix.GleanMetrics.SyncedTabs
@@ -192,6 +197,15 @@ private val Event.wrapper: EventWrapper<*>?
{ Events.browserMenuAction.record(it) },
{ Events.browserMenuActionKeys.valueOf(it) }
)
+ is Event.SetDefaultBrowserToolbarMenuClicked -> EventWrapper(
+ { ExperimentsDefaultBrowser.toolbarMenuClicked.record(it) }
+ )
+ is Event.ToolbarMenuShown -> EventWrapper(
+ { Events.toolbarMenuVisible.record(it) }
+ )
+ is Event.ChangedToDefaultBrowser -> EventWrapper(
+ { Events.defaultBrowserChanged.record(it) }
+ )
is Event.OpenedBookmark -> EventWrapper(
{ BookmarksManagement.open.record(it) }
)
@@ -586,6 +600,10 @@ private val Event.wrapper: EventWrapper<*>?
{ Addons.openAddonInToolbarMenu.record(it) },
{ Addons.openAddonInToolbarMenuKeys.valueOf(it) }
)
+ is Event.AddonOpenSetting -> EventWrapper(
+ { Addons.openAddonSetting.record(it) },
+ { Addons.openAddonSettingKeys.valueOf(it) }
+ )
is Event.TipDisplayed -> EventWrapper(
{ Tip.displayed.record(it) },
{ Tip.displayedKeys.valueOf(it) }
@@ -667,6 +685,9 @@ private val Event.wrapper: EventWrapper<*>?
is Event.TabsTrayNormalModeTapped -> EventWrapper(
{ TabsTray.normalModeTapped.record(it) }
)
+ is Event.TabsTraySyncedModeTapped -> EventWrapper(
+ { TabsTray.syncedModeTapped.record(it) }
+ )
is Event.NewTabTapped -> EventWrapper(
{ TabsTray.newTabTapped.record(it) }
)
@@ -805,6 +826,22 @@ private val Event.wrapper: EventWrapper<*>?
is Event.SecurePrefsReset -> EventWrapper(
{ AndroidKeystoreExperiment.reset.record(it) }
)
+ is Event.HomeMenuSettingsItemClicked -> EventWrapper(
+ { HomeMenu.settingsItemClicked.record(it) }
+ )
+
+ is Event.CloseExperimentCardClicked -> EventWrapper(
+ { SetDefaultNewtabExperiment.closeExperimentCardClicked.record(it) }
+ )
+ is Event.SetDefaultBrowserNewTabClicked -> EventWrapper(
+ { SetDefaultNewtabExperiment.setDefaultBrowserClicked.record(it) }
+ )
+ is Event.SetDefaultBrowserSettingsScreenClicked -> EventWrapper(
+ { SetDefaultSettingExperiment.setDefaultBrowserClicked.record(it) }
+ )
+ is Event.HomeScreenDisplayed -> EventWrapper(
+ { HomeScreen.homeScreenDisplayed.record(it) }
+ )
// Don't record other events in Glean:
is Event.AddBookmark -> null
@@ -815,7 +852,6 @@ private val Event.wrapper: EventWrapper<*>?
is Event.FennecToFenixMigrated -> null
is Event.AddonInstalled -> null
is Event.SearchWidgetInstalled -> null
- is Event.ChangedToDefaultBrowser -> null
is Event.SyncAuthFromSharedReuse, Event.SyncAuthFromSharedCopy -> null
}
diff --git a/app/src/main/java/org/mozilla/fenix/components/metrics/LeanplumMetricsService.kt b/app/src/main/java/org/mozilla/fenix/components/metrics/LeanplumMetricsService.kt
deleted file mode 100644
index 79526cd92..000000000
--- a/app/src/main/java/org/mozilla/fenix/components/metrics/LeanplumMetricsService.kt
+++ /dev/null
@@ -1,247 +0,0 @@
-/* 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.components.metrics
-
-import android.app.Application
-import android.net.Uri
-import android.util.Log
-import com.leanplum.Leanplum
-import com.leanplum.LeanplumActivityHelper
-import com.leanplum.annotations.Parser
-import com.leanplum.internal.LeanplumInternal
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.Dispatchers.Main
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-import mozilla.components.support.locale.LocaleManager
-import org.mozilla.fenix.BuildConfig
-import org.mozilla.fenix.components.metrics.MozillaProductDetector.MozillaProducts
-import org.mozilla.fenix.ext.settings
-import org.mozilla.fenix.home.intent.DeepLinkIntentProcessor
-import java.util.Locale
-import java.util.MissingResourceException
-import java.util.UUID.randomUUID
-
-private val Event.name: String?
- get() = when (this) {
- is Event.AddBookmark -> "E_Add_Bookmark"
- is Event.RemoveBookmark -> "E_Remove_Bookmark"
- is Event.OpenedBookmark -> "E_Opened_Bookmark"
- is Event.OpenedApp -> "E_Opened_App"
- is Event.OpenedAppFirstRun -> "E_Opened_App_FirstRun"
- is Event.InteractWithSearchURLArea -> "E_Interact_With_Search_URL_Area"
- is Event.CollectionSaved -> "E_Collection_Created"
- is Event.CollectionTabRestored -> "E_Collection_Tab_Opened"
- is Event.SyncAuthSignUp -> "E_FxA_New_Signup"
- is Event.SyncAuthSignIn, Event.SyncAuthPaired, Event.SyncAuthOtherExternal -> "E_Sign_In_FxA"
- is Event.SyncAuthFromSharedCopy, Event.SyncAuthFromSharedReuse -> "E_Sign_In_FxA_Fennec_to_Fenix"
- is Event.SyncAuthSignOut -> "E_Sign_Out_FxA"
- is Event.ClearedPrivateData -> "E_Cleared_Private_Data"
- is Event.DismissedOnboarding -> "E_Dismissed_Onboarding"
- is Event.FennecToFenixMigrated -> "E_Fennec_To_Fenix_Migrated"
- is Event.AddonInstalled -> "E_Addon_Installed"
- is Event.SearchWidgetInstalled -> "E_Search_Widget_Added"
- is Event.ChangedToDefaultBrowser -> "E_Changed_Default_To_Fenix"
- is Event.TrackingProtectionSettingChanged -> "E_Changed_ETP"
-
- // Do not track other events in Leanplum
- else -> null
- }
-
-class LeanplumMetricsService(
- private val application: Application
-) : MetricsService, DeepLinkIntentProcessor.DeepLinkVerifier {
- val scope = CoroutineScope(Dispatchers.IO)
- var leanplumJob: Job? = null
-
- data class Token(val id: String, val token: String) {
- enum class Type { Development, Production, Invalid }
-
- val type by lazy {
- when {
- token.take(ProdPrefix.length) == ProdPrefix -> Type.Production
- token.take(DevPrefix.length) == DevPrefix -> Type.Development
- else -> Type.Invalid
- }
- }
-
- companion object {
- private const val ProdPrefix = "prod"
- private const val DevPrefix = "dev"
- }
- }
-
- override val type = MetricServiceType.Marketing
- private val token = Token(LeanplumId, LeanplumToken)
-
- @Suppress("ComplexMethod")
- override fun start() {
-
- if (!application.settings().isMarketingTelemetryEnabled) return
-
- Leanplum.setIsTestModeEnabled(false)
- Leanplum.setApplicationContext(application)
- Leanplum.setDeviceId(randomUUID().toString())
- Parser.parseVariables(application)
-
- leanplumJob = scope.launch {
-
- val applicationSetLocale = LocaleManager.getCurrentLocale(application)
- val currentLocale = applicationSetLocale ?: Locale.getDefault()
- val languageCode =
- currentLocale.iso3LanguageOrNull
- ?: currentLocale.language.let {
- if (it.isNotBlank()) {
- it
- } else {
- currentLocale.toString()
- }
- }
-
- if (!isLeanplumEnabled(languageCode)) {
- Log.i(LOGTAG, "Leanplum is not available for this locale: $languageCode")
- return@launch
- }
-
- when (token.type) {
- Token.Type.Production -> Leanplum.setAppIdForProductionMode(token.id, token.token)
- Token.Type.Development -> Leanplum.setAppIdForDevelopmentMode(token.id, token.token)
- Token.Type.Invalid -> {
- Log.i(LOGTAG, "Invalid or missing Leanplum token")
- return@launch
- }
- }
-
- LeanplumActivityHelper.enableLifecycleCallbacks(application)
-
- val installedApps = MozillaProductDetector.getInstalledMozillaProducts(application)
-
- val trackingProtection = application.settings().run {
- when {
- !shouldUseTrackingProtection -> "none"
- useStandardTrackingProtection -> "standard"
- useStrictTrackingProtection -> "strict"
- else -> "custom"
- }
- }
-
- Leanplum.start(
- application, hashMapOf(
- "default_browser" to MozillaProductDetector.getMozillaBrowserDefault(application)
- .orEmpty(),
- "fennec_installed" to installedApps.contains(MozillaProducts.FIREFOX.productName),
- "focus_installed" to installedApps.contains(MozillaProducts.FOCUS.productName),
- "klar_installed" to installedApps.contains(MozillaProducts.KLAR.productName),
- "fxa_signed_in" to application.settings().fxaSignedIn,
- "fxa_has_synced_items" to application.settings().fxaHasSyncedItems,
- "search_widget_installed" to application.settings().searchWidgetInstalled,
- "tracking_protection_enabled" to application.settings().shouldUseTrackingProtection,
- "tracking_protection_setting" to trackingProtection,
- "fenix" to true
- )
- )
-
- withContext(Main) {
- LeanplumInternal.setCalledStart(true)
- LeanplumInternal.setHasStarted(true)
- LeanplumInternal.setStartedInBackground(true)
- Log.i(LOGTAG, "Started Leanplum with deviceId ${Leanplum.getDeviceId()}" +
- " and userId ${Leanplum.getUserId()}")
- }
- }
- }
-
- /**
- * Verifies a deep link and returns `true` for deep links that should be handled and `false` if
- * a deep link should be rejected.
- *
- * @See DeepLinkIntentProcessor.verifier
- */
- override fun verifyDeepLink(deepLink: Uri): Boolean {
- // We compare the local Leanplum device ID against the "uid" query parameter and only
- // accept deep links where both values match.
- val uid = deepLink.getQueryParameter("uid")
- return uid == Leanplum.getDeviceId()
- }
-
- override fun stop() {
- if (application.settings().isMarketingTelemetryEnabled) return
- // As written in LeanPlum SDK documentation, "This prevents Leanplum from communicating with the server."
- // as this "isTestMode" flag is checked before LeanPlum SDK does anything.
- // Also has the benefit effect of blocking the display of already downloaded messages.
- // The reverse of this - setIsTestModeEnabled(false) must be called before trying to init
- // LP in the same session.
- Leanplum.setIsTestModeEnabled(true)
-
- // This is just to allow restarting LP and it's functionality in the same app session
- // as LP stores it's state internally and check against it
- LeanplumInternal.setCalledStart(false)
- LeanplumInternal.setHasStarted(false)
- leanplumJob?.cancel()
- }
-
- override fun track(event: Event) {
- val leanplumExtras = event.extras
- ?.map { (key, value) -> key.toString() to value }
- ?.toMap()
-
- event.name?.also {
- Leanplum.track(it, leanplumExtras)
- }
- }
-
- override fun shouldTrack(event: Event): Boolean {
- return application.settings().isTelemetryEnabled &&
- token.type != Token.Type.Invalid && !event.name.isNullOrEmpty()
- }
-
- private fun isLeanplumEnabled(locale: String): Boolean {
- return LEANPLUM_ENABLED_LOCALES.contains(locale)
- }
-
- private val Locale.iso3LanguageOrNull: String?
- get() =
- try {
- this.isO3Language
- } catch (_: MissingResourceException) {
- null
- }
-
- companion object {
- private const val LOGTAG = "LeanplumMetricsService"
-
- private val LeanplumId: String
- // Debug builds have a null (nullable) LEANPLUM_ID
- get() = BuildConfig.LEANPLUM_ID.orEmpty()
- private val LeanplumToken: String
- // Debug builds have a null (nullable) LEANPLUM_TOKEN
- get() = BuildConfig.LEANPLUM_TOKEN.orEmpty()
-
- // Leanplum needs to be enabled for the following locales.
- // Irrespective of the actual device location.
- private val LEANPLUM_ENABLED_LOCALES = setOf(
- "eng", // English
- "zho", // Chinese
- "deu", // German
- "fra", // French
- "ita", // Italian
- "ind", // Indonesian
- "por", // Portuguese
- "spa", // Spanish; Castilian
- "pol", // Polish
- "rus", // Russian
- "hin", // Hindi
- "per", // Persian
- "fas", // Persian
- "ara", // Arabic
- "jpn" // Japanese
- )
-
- private const val PREFERENCE_NAME = "LEANPLUM_PREFERENCES"
- private const val DEVICE_ID_KEY = "LP_DEVICE_ID"
- }
-}
diff --git a/app/src/main/java/org/mozilla/fenix/components/metrics/MetricController.kt b/app/src/main/java/org/mozilla/fenix/components/metrics/MetricController.kt
index 181ff4ea2..f10182b46 100644
--- a/app/src/main/java/org/mozilla/fenix/components/metrics/MetricController.kt
+++ b/app/src/main/java/org/mozilla/fenix/components/metrics/MetricController.kt
@@ -5,7 +5,6 @@
package org.mozilla.fenix.components.metrics
import androidx.annotation.VisibleForTesting
-import com.leanplum.Leanplum
import mozilla.components.browser.awesomebar.facts.BrowserAwesomeBarFacts
import mozilla.components.browser.menu.facts.BrowserMenuFacts
import mozilla.components.browser.toolbar.facts.ToolbarFacts
@@ -179,7 +178,7 @@ internal class ReleaseMetricController(
}
Component.BROWSER_TOOLBAR to ToolbarFacts.Items.MENU -> {
- metadata?.get("customTab")?.let { Event.CustomTabsMenuOpened }
+ metadata?.get("customTab")?.let { Event.CustomTabsMenuOpened } ?: Event.ToolbarMenuShown
}
Component.BROWSER_MENU to BrowserMenuFacts.Items.WEB_EXTENSION_MENU_ITEM -> {
metadata?.get("id")?.let { Event.AddonsOpenInToolbarMenu(it.toString()) }
@@ -218,7 +217,6 @@ internal class ReleaseMetricController(
if (installedAddons is List<*>) {
settings.installedAddonsCount = installedAddons.size
settings.installedAddonsList = installedAddons.joinToString(",")
- Leanplum.setUserAttributes(mapOf("installed_addons" to installedAddons.size))
}
}
@@ -226,7 +224,6 @@ internal class ReleaseMetricController(
if (enabledAddons is List<*>) {
settings.enabledAddonsCount = enabledAddons.size
settings.enabledAddonsList = enabledAddons.joinToString()
- Leanplum.setUserAttributes(mapOf("enabled_addons" to enabledAddons.size))
}
}
diff --git a/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarController.kt b/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarController.kt
index 942390abd..fa303fc23 100644
--- a/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarController.kt
+++ b/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarController.kt
@@ -24,6 +24,7 @@ import org.mozilla.fenix.browser.readermode.ReaderModeController
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.components.metrics.MetricController
import org.mozilla.fenix.ext.components
+import org.mozilla.fenix.ext.navigateBlockingForAsyncNavGraph
import org.mozilla.fenix.ext.nav
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.home.HomeScreenViewModel
@@ -118,7 +119,7 @@ class DefaultBrowserToolbarController(
// When closing the last tab we must show the undo snackbar in the home fragment
if (store.state.getNormalOrPrivateTabs(it.content.private).count() == 1) {
homeViewModel.sessionToDelete = it.id
- navController.navigate(
+ navController.navigateBlockingForAsyncNavGraph(
BrowserFragmentDirections.actionGlobalHome()
)
} else {
@@ -132,7 +133,7 @@ class DefaultBrowserToolbarController(
Event.TabCounterMenuItemTapped(Event.TabCounterMenuItemTapped.Item.NEW_TAB)
)
activity.browsingModeManager.mode = BrowsingMode.Normal
- navController.navigate(
+ navController.navigateBlockingForAsyncNavGraph(
BrowserFragmentDirections.actionGlobalHome(focusOnAddressBar = true)
)
}
@@ -143,7 +144,7 @@ class DefaultBrowserToolbarController(
)
)
activity.browsingModeManager.mode = BrowsingMode.Private
- navController.navigate(
+ navController.navigateBlockingForAsyncNavGraph(
BrowserFragmentDirections.actionGlobalHome(focusOnAddressBar = true)
)
}
diff --git a/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarMenuController.kt b/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarMenuController.kt
index e5bc0f71e..8f7fdd0f0 100644
--- a/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarMenuController.kt
+++ b/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarMenuController.kt
@@ -39,8 +39,10 @@ import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.components.metrics.MetricController
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.getRootView
+import org.mozilla.fenix.ext.navigateBlockingForAsyncNavGraph
import org.mozilla.fenix.ext.nav
import org.mozilla.fenix.ext.navigateSafe
+import org.mozilla.fenix.ext.openSetDefaultBrowserOption
import org.mozilla.fenix.settings.deletebrowsingdata.deleteAndQuit
import org.mozilla.fenix.utils.Do
import org.mozilla.fenix.utils.Settings
@@ -91,7 +93,7 @@ class DefaultBrowserToolbarMenuController(
Do exhaustive when (item) {
// TODO: These can be removed for https://github.com/mozilla-mobile/fenix/issues/17870
// todo === Start ===
- is ToolbarMenu.Item.InstallToHomeScreen -> {
+ is ToolbarMenu.Item.InstallPwaToHomeScreen -> {
settings.installPwaOpened = true
MainScope().launch {
with(activity.components.useCases.webAppUseCases) {
@@ -128,6 +130,18 @@ class DefaultBrowserToolbarMenuController(
activity.finishAndRemoveTask()
}
}
+ // todo === End ===
+ is ToolbarMenu.Item.OpenInApp -> {
+ settings.openInAppOpened = true
+
+ val appLinksUseCases = activity.components.useCases.appLinksUseCases
+ val getRedirect = appLinksUseCases.appLinkRedirect
+ currentSession?.let {
+ val redirect = getRedirect.invoke(it.content.url)
+ redirect.appIntent?.flags = Intent.FLAG_ACTIVITY_NEW_TASK
+ appLinksUseCases.openAppLink.invoke(redirect.appIntent)
+ }
+ }
is ToolbarMenu.Item.Quit -> {
// We need to show the snackbar while the browsing data is deleting (if "Delete
// browsing data on quit" is activated). After the deletion is over, the snackbar
@@ -147,22 +161,9 @@ class DefaultBrowserToolbarMenuController(
readerModeController.showControls()
metrics.track(Event.ReaderModeAppearanceOpened)
}
- is ToolbarMenu.Item.OpenInApp -> {
- settings.openInAppOpened = true
-
- val appLinksUseCases = activity.components.useCases.appLinksUseCases
- val getRedirect = appLinksUseCases.appLinkRedirect
- currentSession?.let {
- val redirect = getRedirect.invoke(it.content.url)
- redirect.appIntent?.flags = Intent.FLAG_ACTIVITY_NEW_TASK
- appLinksUseCases.openAppLink.invoke(redirect.appIntent)
- }
- }
- // todo === End ===
-
is ToolbarMenu.Item.Back -> {
if (item.viewHistory) {
- navController.navigate(
+ navController.navigateBlockingForAsyncNavGraph(
BrowserFragmentDirections.actionGlobalTabHistoryDialogFragment(
activeSessionId = customTabSessionId
)
@@ -175,7 +176,7 @@ class DefaultBrowserToolbarMenuController(
}
is ToolbarMenu.Item.Forward -> {
if (item.viewHistory) {
- navController.navigate(
+ navController.navigateBlockingForAsyncNavGraph(
BrowserFragmentDirections.actionGlobalTabHistoryDialogFragment(
activeSessionId = customTabSessionId
)
@@ -212,7 +213,7 @@ class DefaultBrowserToolbarMenuController(
),
showPage = true
)
- navController.navigate(directions)
+ navController.navigateBlockingForAsyncNavGraph(directions)
}
is ToolbarMenu.Item.Settings -> browserAnimator.captureEngineViewAndDrawStatically {
val directions = BrowserFragmentDirections.actionBrowserFragmentToSettingsFragment()
@@ -224,6 +225,19 @@ class DefaultBrowserToolbarMenuController(
BrowserFragmentDirections.actionBrowserFragmentToSyncedTabsFragment()
)
}
+ is ToolbarMenu.Item.SyncAccount -> {
+ val directions = if (item.signedIn) {
+ BrowserFragmentDirections.actionGlobalAccountSettingsFragment()
+ } else {
+ BrowserFragmentDirections.actionGlobalTurnOnSync()
+ }
+ browserAnimator.captureEngineViewAndDrawStatically {
+ navController.nav(
+ R.id.browserFragment,
+ directions
+ )
+ }
+ }
is ToolbarMenu.Item.RequestDesktop -> {
currentSession?.let {
sessionUseCases.requestDesktopSite.invoke(
@@ -335,10 +349,14 @@ class DefaultBrowserToolbarMenuController(
)
}
is ToolbarMenu.Item.NewTab -> {
- navController.navigate(
+ navController.navigateBlockingForAsyncNavGraph(
BrowserFragmentDirections.actionGlobalHome(focusOnAddressBar = true)
)
}
+ is ToolbarMenu.Item.SetDefaultBrowser -> {
+ metrics.track(Event.SetDefaultBrowserToolbarMenuClicked)
+ activity.openSetDefaultBrowserOption()
+ }
}
}
@@ -356,15 +374,12 @@ class DefaultBrowserToolbarMenuController(
@Suppress("ComplexMethod")
private fun trackToolbarItemInteraction(item: ToolbarMenu.Item) {
val eventItem = when (item) {
- // TODO: These can be removed for https://github.com/mozilla-mobile/fenix/issues/17870
- // todo === Start ===
is ToolbarMenu.Item.OpenInFenix -> Event.BrowserMenuItemTapped.Item.OPEN_IN_FENIX
- is ToolbarMenu.Item.InstallToHomeScreen -> Event.BrowserMenuItemTapped.Item.ADD_TO_HOMESCREEN
+ is ToolbarMenu.Item.InstallPwaToHomeScreen -> Event.BrowserMenuItemTapped.Item.ADD_TO_HOMESCREEN
is ToolbarMenu.Item.Quit -> Event.BrowserMenuItemTapped.Item.QUIT
+ is ToolbarMenu.Item.OpenInApp -> Event.BrowserMenuItemTapped.Item.OPEN_IN_APP
is ToolbarMenu.Item.CustomizeReaderView ->
Event.BrowserMenuItemTapped.Item.READER_MODE_APPEARANCE
- is ToolbarMenu.Item.OpenInApp -> Event.BrowserMenuItemTapped.Item.OPEN_IN_APP
- // todo === End ===
is ToolbarMenu.Item.Back -> Event.BrowserMenuItemTapped.Item.BACK
is ToolbarMenu.Item.Forward -> Event.BrowserMenuItemTapped.Item.FORWARD
is ToolbarMenu.Item.Reload -> Event.BrowserMenuItemTapped.Item.RELOAD
@@ -382,12 +397,14 @@ class DefaultBrowserToolbarMenuController(
is ToolbarMenu.Item.AddToTopSites -> Event.BrowserMenuItemTapped.Item.ADD_TO_TOP_SITES
is ToolbarMenu.Item.AddToHomeScreen -> Event.BrowserMenuItemTapped.Item.ADD_TO_HOMESCREEN
is ToolbarMenu.Item.SyncedTabs -> Event.BrowserMenuItemTapped.Item.SYNC_TABS
+ is ToolbarMenu.Item.SyncAccount -> Event.BrowserMenuItemTapped.Item.SYNC_ACCOUNT
is ToolbarMenu.Item.Bookmark -> Event.BrowserMenuItemTapped.Item.BOOKMARK
is ToolbarMenu.Item.AddonsManager -> Event.BrowserMenuItemTapped.Item.ADDONS_MANAGER
is ToolbarMenu.Item.Bookmarks -> Event.BrowserMenuItemTapped.Item.BOOKMARKS
is ToolbarMenu.Item.History -> Event.BrowserMenuItemTapped.Item.HISTORY
is ToolbarMenu.Item.Downloads -> Event.BrowserMenuItemTapped.Item.DOWNLOADS
is ToolbarMenu.Item.NewTab -> Event.BrowserMenuItemTapped.Item.NEW_TAB
+ is ToolbarMenu.Item.SetDefaultBrowser -> Event.BrowserMenuItemTapped.Item.SET_DEFAULT_BROWSER
}
metrics.track(Event.BrowserMenuItemTapped(eventItem))
diff --git a/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarView.kt b/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarView.kt
index a5d666f21..154a9c63e 100644
--- a/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarView.kt
+++ b/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarView.kt
@@ -168,14 +168,13 @@ class BrowserToolbarView(
} else {
menuToolbar = DefaultToolbarMenu(
context = this,
+ store = components.core.store,
hasAccountProblem = components.backgroundServices.accountManager.accountNeedsReauth(),
- shouldReverseItems = toolbarPosition == ToolbarPosition.TOP,
onItemTapped = {
it.performHapticIfNeeded(view)
interactor.onBrowserToolbarMenuItemTapped(it)
},
lifecycleOwner = lifecycleOwner,
- store = components.core.store,
bookmarksStorage = bookmarkStorage,
isPinningSupported = isPinningSupported
)
diff --git a/app/src/main/java/org/mozilla/fenix/components/toolbar/DefaultToolbarMenu.kt b/app/src/main/java/org/mozilla/fenix/components/toolbar/DefaultToolbarMenu.kt
index 38ac495d2..a4b4737b8 100644
--- a/app/src/main/java/org/mozilla/fenix/components/toolbar/DefaultToolbarMenu.kt
+++ b/app/src/main/java/org/mozilla/fenix/components/toolbar/DefaultToolbarMenu.kt
@@ -7,6 +7,7 @@ package org.mozilla.fenix.components.toolbar
import android.content.Context
import androidx.annotation.ColorRes
import androidx.annotation.VisibleForTesting
+import androidx.annotation.VisibleForTesting.PRIVATE
import androidx.core.content.ContextCompat.getColor
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
@@ -21,6 +22,7 @@ import mozilla.components.browser.menu.item.BrowserMenuDivider
import mozilla.components.browser.menu.item.BrowserMenuHighlightableItem
import mozilla.components.browser.menu.item.BrowserMenuImageSwitch
import mozilla.components.browser.menu.item.BrowserMenuImageText
+import mozilla.components.browser.menu.item.BrowserMenuImageTextCheckboxButton
import mozilla.components.browser.menu.item.BrowserMenuItemToolbar
import mozilla.components.browser.menu.item.WebExtensionPlaceholderMenuItem
import mozilla.components.browser.state.selector.findTab
@@ -33,13 +35,19 @@ import mozilla.components.lib.state.ext.flowScoped
import mozilla.components.support.ktx.android.content.getColorFromAttr
import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifAnyChanged
import org.mozilla.fenix.FeatureFlags
+import org.mozilla.fenix.FeatureFlags.tabsTrayRewrite
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
+import org.mozilla.fenix.components.accounts.FenixAccountManager
+import org.mozilla.fenix.experiments.ExperimentBranch
+import org.mozilla.fenix.experiments.Experiments
import org.mozilla.fenix.ext.asActivity
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.settings
+import org.mozilla.fenix.ext.withExperiment
import org.mozilla.fenix.theme.ThemeManager
+import org.mozilla.fenix.utils.BrowsersCache
/**
* Builds the toolbar object used with the 3-dot menu in the browser fragment.
@@ -50,13 +58,12 @@ import org.mozilla.fenix.theme.ThemeManager
* @param lifecycleOwner View lifecycle owner used to determine when to cancel UI jobs.
* @param bookmarksStorage Used to check if a page is bookmarked.
*/
-@Suppress("LargeClass", "LongParameterList")
+@Suppress("LargeClass", "LongParameterList", "TooManyFunctions")
@ExperimentalCoroutinesApi
-class DefaultToolbarMenu(
+open class DefaultToolbarMenu(
private val context: Context,
private val store: BrowserStore,
hasAccountProblem: Boolean = false,
- shouldReverseItems: Boolean,
private val onItemTapped: (ToolbarMenu.Item) -> Unit = {},
private val lifecycleOwner: LifecycleOwner,
private val bookmarksStorage: BookmarksStorage,
@@ -65,7 +72,11 @@ class DefaultToolbarMenu(
private var isCurrentUrlBookmarked = false
private var isBookmarkedJob: Job? = null
- private val isTopToolbarSelected = shouldReverseItems
+
+ private val shouldDeleteDataOnQuit = context.settings().shouldDeleteBrowsingDataOnQuit
+ private val shouldUseBottomToolbar = context.settings().shouldUseBottomToolbar
+ private val accountManager = FenixAccountManager(context)
+
private val selectedSession: TabSessionState?
get() = store.state.selectedTab
@@ -77,13 +88,16 @@ class DefaultToolbarMenu(
} else {
oldCoreMenuItems
},
- endOfMenuAlwaysVisible = !shouldReverseItems,
+ endOfMenuAlwaysVisible = shouldUseBottomToolbar,
store = store,
- webExtIconTintColorResource = primaryTextColor(),
+ style = WebExtensionBrowserMenuBuilder.Style(
+ webExtIconTintColorResource = primaryTextColor(),
+ addonsManagerMenuItemDrawableRes = R.drawable.ic_addons_extensions
+ ),
onAddonsManagerTapped = {
onItemTapped.invoke(ToolbarMenu.Item.AddonsManager)
},
- appendExtensionSubMenuAtStart = !shouldReverseItems
+ appendExtensionSubMenuAtStart = shouldUseBottomToolbar
)
}
@@ -137,7 +151,7 @@ class DefaultToolbarMenu(
}
val share = BrowserMenuItemToolbar.Button(
- imageResource = R.drawable.ic_share_filled,
+ imageResource = R.drawable.ic_share,
contentDescription = context.getString(R.string.browser_menu_share),
iconTintColorResource = primaryTextColor(),
listener = {
@@ -148,7 +162,7 @@ class DefaultToolbarMenu(
registerForIsBookmarkedUpdates()
if (FeatureFlags.toolbarMenuFeature) {
- BrowserMenuItemToolbar(listOf(back, forward, share, refresh))
+ BrowserMenuItemToolbar(listOf(back, forward, share, refresh), isSticky = true)
} else {
val bookmark = BrowserMenuItemToolbar.TwoStateButton(
primaryImageResource = R.drawable.ic_bookmark_filled,
@@ -163,8 +177,7 @@ class DefaultToolbarMenu(
secondaryImageTintResource = primaryTextColor(),
disableInSecondaryState = false
) {
- if (!isCurrentUrlBookmarked) isCurrentUrlBookmarked = true
- onItemTapped.invoke(ToolbarMenu.Item.Bookmark)
+ handleBookmarkItemTapped()
}
BrowserMenuItemToolbar(listOf(back, forward, bookmark, share, refresh))
@@ -172,24 +185,43 @@ class DefaultToolbarMenu(
}
// Predicates that need to be repeatedly called as the session changes
- private fun canAddToHomescreen(): Boolean =
+ @VisibleForTesting(otherwise = PRIVATE)
+ fun canAddToHomescreen(): Boolean =
selectedSession != null && isPinningSupported &&
!context.components.useCases.webAppUseCases.isInstallable()
- private fun canInstall(): Boolean =
+ @VisibleForTesting(otherwise = PRIVATE)
+ fun canInstall(): Boolean =
selectedSession != null && isPinningSupported &&
context.components.useCases.webAppUseCases.isInstallable()
- private fun shouldShowOpenInApp(): Boolean = selectedSession?.let { session ->
+ @VisibleForTesting(otherwise = PRIVATE)
+ fun shouldShowOpenInApp(): Boolean = selectedSession?.let { session ->
val appLink = context.components.useCases.appLinksUseCases.appLinkRedirect
appLink(session.content.url).hasExternalApp()
} ?: false
- private fun shouldShowReaderViewCustomization(): Boolean = selectedSession?.let {
+ @VisibleForTesting(otherwise = PRIVATE)
+ fun shouldShowReaderViewCustomization(): Boolean = selectedSession?.let {
store.state.findTab(it.id)?.readerState?.active
} ?: false
// End of predicates //
+ val installToHomescreen = BrowserMenuHighlightableItem(
+ label = context.getString(R.string.browser_menu_install_on_homescreen),
+ startImageResource = R.drawable.ic_add_to_homescreen,
+ iconTintColorResource = primaryTextColor(),
+ highlight = BrowserMenuHighlight.LowPriority(
+ label = context.getString(R.string.browser_menu_install_on_homescreen),
+ notificationTint = getColor(context, R.color.whats_new_notification_color)
+ ),
+ isHighlighted = {
+ !context.settings().installPwaOpened
+ }
+ ) {
+ onItemTapped.invoke(ToolbarMenu.Item.InstallPwaToHomeScreen)
+ }
+
private val oldCoreMenuItems by lazy {
val settings = BrowserMenuHighlightableItem(
label = context.getString(R.string.browser_menu_settings),
@@ -244,21 +276,6 @@ class DefaultToolbarMenu(
onItemTapped.invoke(ToolbarMenu.Item.SyncedTabs)
}
- val installToHomescreen = BrowserMenuHighlightableItem(
- label = context.getString(R.string.browser_menu_install_on_homescreen),
- startImageResource = R.drawable.ic_add_to_homescreen,
- iconTintColorResource = primaryTextColor(),
- highlight = BrowserMenuHighlight.LowPriority(
- label = context.getString(R.string.browser_menu_install_on_homescreen),
- notificationTint = getColor(context, R.color.whats_new_notification_color)
- ),
- isHighlighted = {
- !context.settings().installPwaOpened
- }
- ) {
- onItemTapped.invoke(ToolbarMenu.Item.InstallToHomeScreen)
- }
-
val findInPage = BrowserMenuImageText(
label = context.getString(R.string.browser_menu_find_in_page),
imageResource = R.drawable.mozac_ic_search,
@@ -348,6 +365,9 @@ class DefaultToolbarMenu(
BrowserMenuDivider(),
reportSiteIssuePlaceholder,
findInPage,
+ getSetDefaultBrowserItem()?.let { BrowserMenuDivider() },
+ getSetDefaultBrowserItem(),
+ getSetDefaultBrowserItem()?.let { BrowserMenuDivider() },
addToTopSites,
addToHomescreen.apply { visible = ::canAddToHomescreen },
installToHomescreen.apply { visible = ::canInstall },
@@ -359,157 +379,195 @@ class DefaultToolbarMenu(
menuToolbar
)
- if (shouldReverseItems) {
- menuItems.reversed()
- } else {
+ if (shouldUseBottomToolbar) {
menuItems
+ } else {
+ menuItems.reversed()
}
}
- private val newCoreMenuItems by lazy {
- val newTabItem = BrowserMenuImageText(
- context.getString(R.string.library_new_tab),
- R.drawable.ic_new,
- primaryTextColor()
- ) {
- onItemTapped.invoke(ToolbarMenu.Item.NewTab)
- }
+ val newTabItem = BrowserMenuImageText(
+ context.getString(R.string.library_new_tab),
+ R.drawable.ic_new,
+ primaryTextColor()
+ ) {
+ onItemTapped.invoke(ToolbarMenu.Item.NewTab)
+ }
- val bookmarksItem = BrowserMenuImageText(
- context.getString(R.string.library_bookmarks),
- R.drawable.ic_bookmark_filled,
- primaryTextColor()
- ) {
- onItemTapped.invoke(ToolbarMenu.Item.Bookmarks)
- }
+ val historyItem = BrowserMenuImageText(
+ context.getString(R.string.library_history),
+ R.drawable.ic_history,
+ primaryTextColor()
+ ) {
+ onItemTapped.invoke(ToolbarMenu.Item.History)
+ }
- val historyItem = BrowserMenuImageText(
- context.getString(R.string.library_history),
- R.drawable.ic_history,
- primaryTextColor()
- ) {
- onItemTapped.invoke(ToolbarMenu.Item.History)
- }
+ val downloadsItem = BrowserMenuImageText(
+ context.getString(R.string.library_downloads),
+ R.drawable.ic_download,
+ primaryTextColor()
+ ) {
+ onItemTapped.invoke(ToolbarMenu.Item.Downloads)
+ }
- val downloadsItem = BrowserMenuImageText(
- context.getString(R.string.library_downloads),
- R.drawable.ic_download,
- primaryTextColor()
- ) {
- onItemTapped.invoke(ToolbarMenu.Item.Downloads)
- }
+ val extensionsItem = WebExtensionPlaceholderMenuItem(
+ id = WebExtensionPlaceholderMenuItem.MAIN_EXTENSIONS_MENU_ID
+ )
- val extensionsItem = BrowserMenuImageText(
- context.getString(R.string.browser_menu_extensions),
- R.drawable.ic_addons_extensions,
- primaryTextColor()
- ) {
- onItemTapped.invoke(ToolbarMenu.Item.AddonsManager)
- }
+ val findInPageItem = BrowserMenuImageText(
+ label = context.getString(R.string.browser_menu_find_in_page),
+ imageResource = R.drawable.mozac_ic_search,
+ iconTintColorResource = primaryTextColor()
+ ) {
+ onItemTapped.invoke(ToolbarMenu.Item.FindInPage)
+ }
- val syncedTabs = BrowserMenuImageText(
- label = context.getString(R.string.synced_tabs),
- imageResource = R.drawable.ic_synced_tabs,
- iconTintColorResource = primaryTextColor()
- ) {
- onItemTapped.invoke(ToolbarMenu.Item.SyncedTabs)
+ val desktopSiteItem = BrowserMenuImageSwitch(
+ imageResource = R.drawable.ic_desktop,
+ label = context.getString(R.string.browser_menu_desktop_site),
+ initialState = {
+ selectedSession?.content?.desktopMode ?: false
}
+ ) { checked ->
+ onItemTapped.invoke(ToolbarMenu.Item.RequestDesktop(checked))
+ }
- val findInPageItem = BrowserMenuImageText(
- label = context.getString(R.string.browser_menu_find_in_page),
- imageResource = R.drawable.mozac_ic_search,
- iconTintColorResource = primaryTextColor()
- ) {
- onItemTapped.invoke(ToolbarMenu.Item.FindInPage)
- }
+ val customizeReaderView = BrowserMenuImageText(
+ label = context.getString(R.string.browser_menu_customize_reader_view),
+ imageResource = R.drawable.ic_readermode_appearance,
+ iconTintColorResource = primaryTextColor()
+ ) {
+ onItemTapped.invoke(ToolbarMenu.Item.CustomizeReaderView)
+ }
- val desktopSiteItem = BrowserMenuImageSwitch(
- imageResource = R.drawable.ic_desktop,
- label = context.getString(R.string.browser_menu_desktop_site),
- initialState = {
- selectedSession?.content?.desktopMode ?: false
- }
- ) { checked ->
- onItemTapped.invoke(ToolbarMenu.Item.RequestDesktop(checked))
- }
+ val openInApp = BrowserMenuHighlightableItem(
+ label = context.getString(R.string.browser_menu_open_app_link),
+ startImageResource = R.drawable.ic_open_in_app,
+ iconTintColorResource = primaryTextColor(),
+ highlight = BrowserMenuHighlight.LowPriority(
+ label = context.getString(R.string.browser_menu_open_app_link),
+ notificationTint = getColor(context, R.color.whats_new_notification_color)
+ ),
+ isHighlighted = { !context.settings().openInAppOpened }
+ ) {
+ onItemTapped.invoke(ToolbarMenu.Item.OpenInApp)
+ }
- val customizeReaderView = BrowserMenuImageText(
- label = context.getString(R.string.browser_menu_customize_reader_view),
- imageResource = R.drawable.ic_readermode_appearance,
- iconTintColorResource = primaryTextColor()
- ) {
- onItemTapped.invoke(ToolbarMenu.Item.CustomizeReaderView)
- }
+ val reportSiteIssuePlaceholder = WebExtensionPlaceholderMenuItem(
+ id = WebCompatReporterFeature.WEBCOMPAT_REPORTER_EXTENSION_ID
+ )
+
+ val addToHomeScreenItem = BrowserMenuImageText(
+ label = context.getString(R.string.browser_menu_add_to_homescreen),
+ imageResource = R.drawable.ic_add_to_homescreen,
+ iconTintColorResource = primaryTextColor(),
+ isCollapsingMenuLimit = true
+ ) {
+ onItemTapped.invoke(ToolbarMenu.Item.AddToHomeScreen)
+ }
- val openInApp = BrowserMenuHighlightableItem(
- label = context.getString(R.string.browser_menu_open_app_link),
- startImageResource = R.drawable.ic_open_in_app,
- iconTintColorResource = primaryTextColor(),
- highlight = BrowserMenuHighlight.LowPriority(
- label = context.getString(R.string.browser_menu_open_app_link),
- notificationTint = getColor(context, R.color.whats_new_notification_color)
- ),
- isHighlighted = { !context.settings().openInAppOpened }
- ) {
- onItemTapped.invoke(ToolbarMenu.Item.OpenInApp)
- }
+ val addToTopSitesItem = BrowserMenuImageText(
+ label = context.getString(R.string.browser_menu_add_to_top_sites),
+ imageResource = R.drawable.ic_top_sites,
+ iconTintColorResource = primaryTextColor()
+ ) {
+ onItemTapped.invoke(ToolbarMenu.Item.AddToTopSites)
+ }
- val reportSiteIssuePlaceholder = WebExtensionPlaceholderMenuItem(
- id = WebCompatReporterFeature.WEBCOMPAT_REPORTER_EXTENSION_ID
- )
+ val saveToCollectionItem = BrowserMenuImageText(
+ label = context.getString(R.string.browser_menu_save_to_collection_2),
+ imageResource = R.drawable.ic_tab_collection,
+ iconTintColorResource = primaryTextColor()
+ ) {
+ onItemTapped.invoke(ToolbarMenu.Item.SaveToCollection)
+ }
- val addToHomeScreenItem = BrowserMenuImageText(
- label = context.getString(R.string.browser_menu_add_to_homescreen),
- imageResource = R.drawable.ic_add_to_homescreen,
- iconTintColorResource = primaryTextColor()
- ) {
- onItemTapped.invoke(ToolbarMenu.Item.AddToHomeScreen)
- }
+ val settingsItem = BrowserMenuHighlightableItem(
+ label = context.getString(R.string.browser_menu_settings),
+ startImageResource = R.drawable.ic_settings,
+ iconTintColorResource = if (hasAccountProblem)
+ ThemeManager.resolveAttribute(R.attr.syncDisconnected, context) else
+ primaryTextColor(),
+ textColorResource = if (hasAccountProblem)
+ ThemeManager.resolveAttribute(R.attr.primaryText, context) else
+ primaryTextColor(),
+ highlight = BrowserMenuHighlight.HighPriority(
+ endImageResource = R.drawable.ic_sync_disconnected,
+ backgroundTint = context.getColorFromAttr(R.attr.syncDisconnectedBackground),
+ canPropagate = false
+ ),
+ isHighlighted = { hasAccountProblem }
+ ) {
+ onItemTapped.invoke(ToolbarMenu.Item.Settings)
+ }
- val addToTopSitesItem = BrowserMenuImageText(
- label = context.getString(R.string.browser_menu_add_to_top_sites),
- imageResource = R.drawable.ic_top_sites,
- iconTintColorResource = primaryTextColor()
- ) {
- onItemTapped.invoke(ToolbarMenu.Item.AddToTopSites)
- }
+ val bookmarksItem = BrowserMenuImageTextCheckboxButton(
+ imageResource = R.drawable.ic_bookmarks_menu,
+ iconTintColorResource = primaryTextColor(),
+ label = context.getString(R.string.library_bookmarks),
+ labelListener = {
+ onItemTapped.invoke(ToolbarMenu.Item.Bookmarks)
+ },
+ primaryStateIconResource = R.drawable.ic_bookmark_outline,
+ secondaryStateIconResource = R.drawable.ic_bookmark_filled,
+ tintColorResource = menuItemButtonTintColor(),
+ primaryLabel = context.getString(R.string.browser_menu_add),
+ secondaryLabel = context.getString(R.string.browser_menu_edit),
+ isInPrimaryState = { !isCurrentUrlBookmarked }
+ ) {
+ handleBookmarkItemTapped()
+ }
- val saveToCollectionItem = BrowserMenuImageText(
- label = context.getString(R.string.browser_menu_save_to_collection_2),
- imageResource = R.drawable.ic_tab_collection,
- iconTintColorResource = primaryTextColor()
- ) {
- onItemTapped.invoke(ToolbarMenu.Item.SaveToCollection)
- }
+ val deleteDataOnQuit = BrowserMenuImageText(
+ label = context.getString(R.string.delete_browsing_data_on_quit_action),
+ imageResource = R.drawable.ic_exit,
+ iconTintColorResource = primaryTextColor()
+ ) {
+ onItemTapped.invoke(ToolbarMenu.Item.Quit)
+ }
- val settingsItem = BrowserMenuHighlightableItem(
- label = context.getString(R.string.browser_menu_settings),
- startImageResource = R.drawable.ic_settings,
- iconTintColorResource = primaryTextColor(),
- textColorResource = if (hasAccountProblem)
- ThemeManager.resolveAttribute(R.attr.primaryText, context) else
- primaryTextColor(),
- highlight = BrowserMenuHighlight.HighPriority(
- endImageResource = R.drawable.ic_sync_disconnected,
- backgroundTint = context.getColorFromAttr(R.attr.syncDisconnectedBackground),
- canPropagate = false
- ),
- isHighlighted = { hasAccountProblem }
- ) {
- onItemTapped.invoke(ToolbarMenu.Item.Settings)
+ val syncedTabsItem = BrowserMenuImageText(
+ context.getString(R.string.synced_tabs),
+ R.drawable.ic_synced_tabs,
+ primaryTextColor()
+ ) {
+ onItemTapped.invoke(ToolbarMenu.Item.SyncedTabs)
+ }
+
+ private fun getSyncItemTitle(): String {
+ val authenticatedAccount = accountManager.authenticatedAccount
+ val email = accountManager.accountProfileEmail
+
+ return if (authenticatedAccount && !email.isNullOrEmpty()) {
+ email
+ } else {
+ context.getString(R.string.sync_menu_sign_in)
}
+ }
+
+ val syncMenuItem = BrowserMenuImageText(
+ getSyncItemTitle(),
+ R.drawable.ic_signed_out,
+ primaryTextColor()
+ ) {
+ onItemTapped.invoke(ToolbarMenu.Item.SyncAccount(accountManager.signedInToFxa()))
+ }
+ @VisibleForTesting(otherwise = PRIVATE)
+ val newCoreMenuItems by lazy {
val menuItems =
listOfNotNull(
- if (isTopToolbarSelected) menuToolbar else null,
+ if (shouldUseBottomToolbar) null else menuToolbar,
newTabItem,
BrowserMenuDivider(),
bookmarksItem,
historyItem,
downloadsItem,
extensionsItem,
- syncedTabs,
+ if (tabsTrayRewrite) syncMenuItem else syncedTabsItem,
BrowserMenuDivider(),
+ getSetDefaultBrowserItem(),
+ getSetDefaultBrowserItem()?.let { BrowserMenuDivider() },
findInPageItem,
desktopSiteItem,
customizeReaderView.apply { visible = ::shouldShowReaderViewCustomization },
@@ -517,21 +575,32 @@ class DefaultToolbarMenu(
reportSiteIssuePlaceholder,
BrowserMenuDivider(),
addToHomeScreenItem.apply { visible = ::canAddToHomescreen },
+ installToHomescreen.apply { visible = ::canInstall },
addToTopSitesItem,
saveToCollectionItem,
BrowserMenuDivider(),
settingsItem,
- if (isTopToolbarSelected) null else BrowserMenuDivider(),
- if (isTopToolbarSelected) null else menuToolbar
+ if (shouldDeleteDataOnQuit) deleteDataOnQuit else null,
+ if (shouldUseBottomToolbar) BrowserMenuDivider() else null,
+ if (shouldUseBottomToolbar) menuToolbar else null
)
menuItems
}
+ private fun handleBookmarkItemTapped() {
+ if (!isCurrentUrlBookmarked) isCurrentUrlBookmarked = true
+ onItemTapped.invoke(ToolbarMenu.Item.Bookmark)
+ }
+
@ColorRes
@VisibleForTesting
internal fun primaryTextColor() = ThemeManager.resolveAttribute(R.attr.primaryText, context)
+ @ColorRes
+ @VisibleForTesting
+ internal fun menuItemButtonTintColor() = ThemeManager.resolveAttribute(R.attr.menuItemButtonTintColor, context)
+
@VisibleForTesting
internal fun registerForIsBookmarkedUpdates() {
store.flowScoped(lifecycleOwner) { flow ->
@@ -558,4 +627,24 @@ class DefaultToolbarMenu(
.any { it.url == newUrl }
}
}
+
+ private fun getSetDefaultBrowserItem(): BrowserMenuImageText? {
+ val experiments = context.components.analytics.experiments
+ val browsers = BrowsersCache.all(context)
+
+ return experiments.withExperiment(Experiments.DEFAULT_BROWSER) { experimentBranch ->
+ if (experimentBranch == ExperimentBranch.DEFAULT_BROWSER_TOOLBAR_MENU &&
+ !browsers.isFirefoxDefaultBrowser
+ ) {
+ return@withExperiment BrowserMenuImageText(
+ label = context.getString(R.string.preferences_set_as_default_browser),
+ imageResource = R.mipmap.ic_launcher
+ ) {
+ onItemTapped.invoke(ToolbarMenu.Item.SetDefaultBrowser)
+ }
+ } else {
+ null
+ }
+ }
+ }
}
diff --git a/app/src/main/java/org/mozilla/fenix/components/toolbar/ToolbarMenu.kt b/app/src/main/java/org/mozilla/fenix/components/toolbar/ToolbarMenu.kt
index 126a949e1..ec384c188 100644
--- a/app/src/main/java/org/mozilla/fenix/components/toolbar/ToolbarMenu.kt
+++ b/app/src/main/java/org/mozilla/fenix/components/toolbar/ToolbarMenu.kt
@@ -20,12 +20,14 @@ interface ToolbarMenu {
object OpenInFenix : Item()
object SaveToCollection : Item()
object AddToTopSites : Item()
- object InstallToHomeScreen : Item()
+ object InstallPwaToHomeScreen : Item()
object AddToHomeScreen : Item()
object SyncedTabs : Item()
+ data class SyncAccount(val signedIn: Boolean) : Item()
object AddonsManager : Item()
object Quit : Item()
object OpenInApp : Item()
+ object SetDefaultBrowser : Item()
object Bookmark : Item()
object CustomizeReaderView : Item()
object Bookmarks : Item()
diff --git a/app/src/main/java/org/mozilla/fenix/customtabs/ExternalAppBrowserActivity.kt b/app/src/main/java/org/mozilla/fenix/customtabs/ExternalAppBrowserActivity.kt
index 689a6aa4c..a7546b378 100644
--- a/app/src/main/java/org/mozilla/fenix/customtabs/ExternalAppBrowserActivity.kt
+++ b/app/src/main/java/org/mozilla/fenix/customtabs/ExternalAppBrowserActivity.kt
@@ -60,6 +60,9 @@ open class ExternalAppBrowserActivity : HomeActivity() {
homeActivityInitTimeStampNanoSeconds,
rootContainer
)
+
+ // coldStartupDurationTelemetry.onHomeActivityOnCreate is intentionally omitted so we don't
+ // include even more unpredictable code paths in the results.
}
override fun navigateToBrowserOnColdStart() {
diff --git a/app/src/main/java/org/mozilla/fenix/downloads/DynamicDownloadDialogBehavior.kt b/app/src/main/java/org/mozilla/fenix/downloads/DynamicDownloadDialogBehavior.kt
index a5d640bae..6fc09d277 100644
--- a/app/src/main/java/org/mozilla/fenix/downloads/DynamicDownloadDialogBehavior.kt
+++ b/app/src/main/java/org/mozilla/fenix/downloads/DynamicDownloadDialogBehavior.kt
@@ -50,7 +50,8 @@ class DynamicDownloadDialogBehavior(
/**
* Reference to [EngineView] used to check user's [android.view.MotionEvent]s.
*/
- private var engineView: EngineView? = null
+ @VisibleForTesting
+ internal var engineView: EngineView? = null
/**
* Depending on how user's touch was consumed by EngineView / current website,
@@ -64,7 +65,9 @@ class DynamicDownloadDialogBehavior(
*/
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal val shouldScroll: Boolean
- get() = engineView?.getInputResult() == EngineView.InputResult.INPUT_RESULT_HANDLED
+ get() = engineView?.getInputResultDetail()?.let {
+ (it.canScrollToBottom() || it.canScrollToTop())
+ } ?: false
override fun onStartNestedScroll(
coordinatorLayout: CoordinatorLayout,
@@ -78,7 +81,7 @@ class DynamicDownloadDialogBehavior(
shouldSnapAfterScroll = type == ViewCompat.TYPE_TOUCH
snapAnimator.cancel()
true
- } else if (engineView?.getInputResult() == EngineView.InputResult.INPUT_RESULT_UNHANDLED) {
+ } else if (engineView?.getInputResultDetail()?.isTouchUnhandled() == true) {
// Force expand the notification dialog if event is unhandled, otherwise user could get stuck in a
// state where they cannot show it
forceExpand(child)
diff --git a/app/src/main/java/org/mozilla/fenix/experiments/Experiments.kt b/app/src/main/java/org/mozilla/fenix/experiments/Experiments.kt
index 9a9a39036..92808c5ff 100644
--- a/app/src/main/java/org/mozilla/fenix/experiments/Experiments.kt
+++ b/app/src/main/java/org/mozilla/fenix/experiments/Experiments.kt
@@ -7,8 +7,8 @@ package org.mozilla.fenix.experiments
class Experiments {
companion object {
const val A_A_NIMBUS_VALIDATION = "fenix-nimbus-validation-v3"
- const val BOOKMARK_ICON = "fenix-bookmark-list-icon"
const val ANDROID_KEYSTORE = "fenix-android-keystore"
+ const val DEFAULT_BROWSER = "fenix-default-browser"
}
}
@@ -18,5 +18,8 @@ class ExperimentBranch {
const val CONTROL = "control"
const val A1 = "a1"
const val A2 = "a2"
+ const val DEFAULT_BROWSER_TOOLBAR_MENU = "default_browser_toolbar_menu"
+ const val DEFAULT_BROWSER_NEW_TAB_BANNER = "default_browser_newtab_banner"
+ const val DEFAULT_BROWSER_SETTINGS_MENU = "default_browser_settings_menu"
}
}
diff --git a/app/src/main/java/org/mozilla/fenix/ext/Activity.kt b/app/src/main/java/org/mozilla/fenix/ext/Activity.kt
index db5feca2b..6469a4c88 100644
--- a/app/src/main/java/org/mozilla/fenix/ext/Activity.kt
+++ b/app/src/main/java/org/mozilla/fenix/ext/Activity.kt
@@ -8,6 +8,14 @@ import android.app.Activity
import android.view.View
import android.view.WindowManager
import mozilla.components.concept.base.crash.Breadcrumb
+import android.app.role.RoleManager
+import android.content.Intent
+import android.os.Build
+import android.provider.Settings
+import androidx.core.os.bundleOf
+import org.mozilla.fenix.BrowserDirection
+import org.mozilla.fenix.HomeActivity
+import org.mozilla.fenix.settings.SupportUtils
/**
* Attempts to call immersive mode using the View to hide the status bar and navigation buttons.
@@ -15,8 +23,17 @@ import mozilla.components.concept.base.crash.Breadcrumb
* We don't use the equivalent function from Android Components because the stable flag messes
* with the toolbar. See #1998 and #3272.
*/
+@Deprecated(
+ message = "Use the Android Component implementation instead.",
+ replaceWith = ReplaceWith(
+ "enterToImmersiveMode()",
+ "mozilla.components.support.ktx.android.view.enterToImmersiveMode"
+ )
+)
fun Activity.enterToImmersiveMode() {
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
+ // This will be addressed on https://github.com/mozilla-mobile/fenix/issues/17804
+ @Suppress("DEPRECATION")
window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
@@ -24,19 +41,6 @@ fun Activity.enterToImmersiveMode() {
or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY)
}
-/**
- * Attempts to come out from immersive mode using the View.
- */
-fun Activity.exitImmersiveModeIfNeeded() {
- if (WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON and window.attributes.flags == 0) {
- // We left immersive mode already.
- return
- }
-
- window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
- window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE
-}
-
fun Activity.breadcrumb(
message: String,
data: Map = emptyMap()
@@ -52,3 +56,59 @@ fun Activity.breadcrumb(
)
)
}
+
+/**
+ * Opens Android's Manage Default Apps Settings if possible.
+ */
+fun Activity.openSetDefaultBrowserOption() {
+ when {
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q -> {
+ getSystemService(RoleManager::class.java).also {
+ if (it.isRoleAvailable(RoleManager.ROLE_BROWSER) && !it.isRoleHeld(
+ RoleManager.ROLE_BROWSER
+ )
+ ) {
+ startActivityForResult(
+ it.createRequestRoleIntent(RoleManager.ROLE_BROWSER),
+ REQUEST_CODE_BROWSER_ROLE
+ )
+ } else {
+ navigateToDefaultBrowserAppsSettings()
+ }
+ }
+ }
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.N -> {
+ navigateToDefaultBrowserAppsSettings()
+ }
+ else -> {
+ (this as HomeActivity).openToBrowserAndLoad(
+ searchTermOrURL = SupportUtils.getSumoURLForTopic(
+ this,
+ SupportUtils.SumoTopic.SET_AS_DEFAULT_BROWSER
+ ),
+ newTab = true,
+ from = BrowserDirection.FromSettings
+ )
+ }
+ }
+}
+
+private fun Activity.navigateToDefaultBrowserAppsSettings() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ val intent = Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS)
+ intent.putExtra(
+ SETTINGS_SELECT_OPTION_KEY,
+ DEFAULT_BROWSER_APP_OPTION
+ )
+ intent.putExtra(
+ SETTINGS_SHOW_FRAGMENT_ARGS,
+ bundleOf(SETTINGS_SELECT_OPTION_KEY to DEFAULT_BROWSER_APP_OPTION)
+ )
+ startActivity(intent)
+ }
+}
+
+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"
diff --git a/app/src/main/java/org/mozilla/fenix/ext/NavController.kt b/app/src/main/java/org/mozilla/fenix/ext/NavController.kt
index 4034c2472..8ba9cebfd 100644
--- a/app/src/main/java/org/mozilla/fenix/ext/NavController.kt
+++ b/app/src/main/java/org/mozilla/fenix/ext/NavController.kt
@@ -2,6 +2,9 @@
* 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/. */
+// We suppress the calls to `navigate` since we invoke the Android `NavController.navigate` through
+// this file. Detekt checks for the `navigate()` function calls, which should be ignored in this file.
+@file:Suppress("MozillaNavigateCheck")
package org.mozilla.fenix.ext
import androidx.annotation.IdRes
@@ -10,12 +13,15 @@ import androidx.navigation.NavDirections
import androidx.navigation.NavOptions
import io.sentry.Sentry
import org.mozilla.fenix.components.isSentryEnabled
+import org.mozilla.fenix.perf.NavGraphProvider
/**
* Navigate from the fragment with [id] using the given [directions].
* If the id doesn't match the current destination, an error is recorded.
*/
fun NavController.nav(@IdRes id: Int?, directions: NavDirections, navOptions: NavOptions? = null) {
+ NavGraphProvider.blockForNavGraphInflation(this)
+
if (id == null || this.currentDestination?.id == id) {
this.navigate(directions, navOptions)
} else {
@@ -23,6 +29,21 @@ fun NavController.nav(@IdRes id: Int?, directions: NavDirections, navOptions: Na
}
}
+fun NavController.navigateBlockingForAsyncNavGraph(resId: Int) {
+ NavGraphProvider.blockForNavGraphInflation(this)
+ this.navigate(resId)
+}
+
+fun NavController.navigateBlockingForAsyncNavGraph(directions: NavDirections) {
+ NavGraphProvider.blockForNavGraphInflation(this)
+ this.navigate(directions)
+}
+
+fun NavController.navigateBlockingForAsyncNavGraph(directions: NavDirections, navOptions: NavOptions?) {
+ NavGraphProvider.blockForNavGraphInflation(this)
+ this.navigate(directions, navOptions)
+}
+
fun NavController.alreadyOnDestination(@IdRes destId: Int?): Boolean {
return destId?.let { currentDestination?.id == it || popBackStack(it, false) } ?: false
}
@@ -38,6 +59,6 @@ fun NavController.navigateSafe(
directions: NavDirections
) {
if (currentDestination?.id == resId) {
- this.navigate(directions)
+ this.navigateBlockingForAsyncNavGraph(directions)
}
}
diff --git a/app/src/main/java/org/mozilla/fenix/ext/String.kt b/app/src/main/java/org/mozilla/fenix/ext/String.kt
index 64052c359..19c84249e 100644
--- a/app/src/main/java/org/mozilla/fenix/ext/String.kt
+++ b/app/src/main/java/org/mozilla/fenix/ext/String.kt
@@ -7,6 +7,7 @@ package org.mozilla.fenix.ext
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
+import android.text.Editable
import android.util.Patterns
import android.webkit.URLUtil
import androidx.core.net.toUri
@@ -114,6 +115,11 @@ fun String.simplifiedUrl(): String {
return afterScheme
}
+/**
+ * Returns an [Editable] for the provided string.
+ */
+fun String.toEditable(): Editable = Editable.Factory.getInstance().newEditable(this)
+
suspend fun bitmapForUrl(url: String, client: Client): Bitmap? = withContext(Dispatchers.IO) {
// Code below will cache it in Gecko's cache, which ensures that as long as we've fetched it once,
// we will be able to display this avatar as long as the cache isn't purged (e.g. via 'clear user data').
diff --git a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt
index d909c8779..a43261f41 100644
--- a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt
+++ b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt
@@ -89,6 +89,7 @@ import org.mozilla.fenix.GleanMetrics.PerfStartup
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.browser.BrowserAnimator.Companion.getToolbarNavOptions
+import org.mozilla.fenix.browser.BrowserFragmentDirections
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.components.FenixSnackbar
import org.mozilla.fenix.components.PrivateShortcutCreateManager
@@ -102,6 +103,7 @@ import org.mozilla.fenix.components.toolbar.FenixTabCounterMenu
import org.mozilla.fenix.components.toolbar.ToolbarPosition
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.hideToolbar
+import org.mozilla.fenix.ext.navigateBlockingForAsyncNavGraph
import org.mozilla.fenix.ext.measureNoInline
import org.mozilla.fenix.ext.metrics
import org.mozilla.fenix.ext.nav
@@ -225,7 +227,8 @@ class HomeFragment : Fragment() {
)
).getTip()
},
- showCollectionPlaceholder = false
+ showCollectionPlaceholder = components.settings.showCollectionsPlaceholderOnHome,
+ showSetAsDefaultBrowserCard = components.settings.shouldShowSetAsDefaultBrowserCard()
)
)
}
@@ -252,6 +255,7 @@ class HomeFragment : Fragment() {
restoreUseCase = components.useCases.tabsUseCases.restore,
reloadUrlUseCase = components.useCases.sessionUseCases.reload,
selectTabUseCase = components.useCases.tabsUseCases.selectTab,
+ requestDesktopSiteUseCase = components.useCases.sessionUseCases.requestDesktopSite,
fragmentStore = homeFragmentStore,
navController = findNavController(),
viewLifecycleScope = viewLifecycleOwner.lifecycleScope,
@@ -359,82 +363,83 @@ class HomeFragment : Fragment() {
@Suppress("LongMethod", "ComplexMethod")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) =
- PerfStartup.homeFragmentOnViewCreated.measureNoInline { // weird indent so we don't have to break blame.
- super.onViewCreated(view, savedInstanceState)
-
- observeSearchEngineChanges()
- createHomeMenu(requireContext(), WeakReference(view.menuButton))
- createTabCounterMenu(view)
-
- view.menuButton.setColorFilter(
- ContextCompat.getColor(
- requireContext(),
- ThemeManager.resolveAttribute(R.attr.primaryText, requireContext())
+ PerfStartup.homeFragmentOnViewCreated.measureNoInline {
+ super.onViewCreated(view, savedInstanceState)
+ context?.metrics?.track(Event.HomeScreenDisplayed)
+
+ observeSearchEngineChanges()
+ createHomeMenu(requireContext(), WeakReference(view.menuButton))
+ createTabCounterMenu(view)
+
+ view.menuButton.setColorFilter(
+ ContextCompat.getColor(
+ requireContext(),
+ ThemeManager.resolveAttribute(R.attr.primaryText, requireContext())
+ )
)
- )
- view.toolbar.compoundDrawablePadding =
- view.resources.getDimensionPixelSize(R.dimen.search_bar_search_engine_icon_padding)
- view.toolbar_wrapper.setOnClickListener {
- navigateToSearch()
- requireComponents.analytics.metrics.track(Event.SearchBarTapped(Event.SearchBarTapped.Source.HOME))
- }
-
- view.toolbar_wrapper.setOnLongClickListener {
- ToolbarPopupWindow.show(
- WeakReference(it),
- handlePasteAndGo = sessionControlInteractor::onPasteAndGo,
- handlePaste = sessionControlInteractor::onPaste,
- copyVisible = false
- )
- true
- }
+ view.toolbar.compoundDrawablePadding =
+ view.resources.getDimensionPixelSize(R.dimen.search_bar_search_engine_icon_padding)
+ view.toolbar_wrapper.setOnClickListener {
+ navigateToSearch()
+ requireComponents.analytics.metrics.track(Event.SearchBarTapped(Event.SearchBarTapped.Source.HOME))
+ }
- view.tab_button.setOnClickListener {
- openTabTray()
- }
+ view.toolbar_wrapper.setOnLongClickListener {
+ ToolbarPopupWindow.show(
+ WeakReference(it),
+ handlePasteAndGo = sessionControlInteractor::onPasteAndGo,
+ handlePaste = sessionControlInteractor::onPaste,
+ copyVisible = false
+ )
+ true
+ }
- PrivateBrowsingButtonView(
- privateBrowsingButton,
- browsingModeManager
- ) { newMode ->
- if (newMode == BrowsingMode.Private) {
- requireContext().settings().incrementNumTimesPrivateModeOpened()
+ view.tab_button.setOnClickListener {
+ openTabTray()
}
- if (onboarding.userHasBeenOnboarded()) {
- homeFragmentStore.dispatch(
- HomeFragmentAction.ModeChange(Mode.fromBrowsingMode(newMode))
- )
+ PrivateBrowsingButtonView(
+ privateBrowsingButton,
+ browsingModeManager
+ ) { newMode ->
+ if (newMode == BrowsingMode.Private) {
+ requireContext().settings().incrementNumTimesPrivateModeOpened()
+ }
+
+ if (onboarding.userHasBeenOnboarded()) {
+ homeFragmentStore.dispatch(
+ HomeFragmentAction.ModeChange(Mode.fromBrowsingMode(newMode))
+ )
+ }
}
- }
- consumeFrom(requireComponents.core.store) {
- updateTabCounter(it)
- }
+ consumeFrom(requireComponents.core.store) {
+ updateTabCounter(it)
+ }
- homeViewModel.sessionToDelete?.also {
- if (it == ALL_NORMAL_TABS || it == ALL_PRIVATE_TABS) {
- removeAllTabsAndShowSnackbar(it)
- } else {
- removeTabAndShowSnackbar(it)
+ homeViewModel.sessionToDelete?.also {
+ if (it == ALL_NORMAL_TABS || it == ALL_PRIVATE_TABS) {
+ removeAllTabsAndShowSnackbar(it)
+ } else {
+ removeTabAndShowSnackbar(it)
+ }
}
- }
- homeViewModel.sessionToDelete = null
+ homeViewModel.sessionToDelete = null
- updateTabCounter(requireComponents.core.store.state)
+ updateTabCounter(requireComponents.core.store.state)
- if (bundleArgs.getBoolean(FOCUS_ON_ADDRESS_BAR)) {
- navigateToSearch()
- } else if (bundleArgs.getLong(FOCUS_ON_COLLECTION, -1) >= 0) {
- // No need to scroll to async'd loaded TopSites if we want to scroll to collections.
- homeViewModel.shouldScrollToTopSites = false
- /* Triggered when the user has added a tab to a collection and has tapped
- * the View action on the [TabsTrayDialogFragment] snackbar.*/
- scrollAndAnimateCollection(bundleArgs.getLong(FOCUS_ON_COLLECTION, -1))
+ if (bundleArgs.getBoolean(FOCUS_ON_ADDRESS_BAR)) {
+ navigateToSearch()
+ } else if (bundleArgs.getLong(FOCUS_ON_COLLECTION, -1) >= 0) {
+ // No need to scroll to async'd loaded TopSites if we want to scroll to collections.
+ homeViewModel.shouldScrollToTopSites = false
+ /* Triggered when the user has added a tab to a collection and has tapped
+ * the View action on the [TabsTrayDialogFragment] snackbar.*/
+ scrollAndAnimateCollection(bundleArgs.getLong(FOCUS_ON_COLLECTION, -1))
+ }
}
- }
private fun observeSearchEngineChanges() {
consumeFlow(store) { flow ->
@@ -531,7 +536,7 @@ class HomeFragment : Fragment() {
requireContext().getString(R.string.snackbar_deleted_undo),
{
requireComponents.useCases.tabsUseCases.undo.invoke()
- findNavController().navigate(
+ findNavController().navigateBlockingForAsyncNavGraph(
HomeFragmentDirections.actionGlobalBrowser(null)
)
},
@@ -622,7 +627,8 @@ class HomeFragment : Fragment() {
}
private fun navToSavedLogins() {
- findNavController().navigate(HomeFragmentDirections.actionGlobalSavedLoginsAuthFragment())
+ findNavController().navigateBlockingForAsyncNavGraph(
+ HomeFragmentDirections.actionGlobalSavedLoginsAuthFragment())
}
private fun dispatchModeChanges(mode: Mode) {
@@ -738,6 +744,7 @@ class HomeFragment : Fragment() {
private fun hideOnboardingAndOpenSearch() {
hideOnboardingIfNeeded()
+ appBarLayout?.setExpanded(true, true)
navigateToSearch()
}
@@ -777,14 +784,27 @@ class HomeFragment : Fragment() {
R.id.homeFragment,
HomeFragmentDirections.actionGlobalSettingsFragment()
)
+ requireComponents.analytics.metrics.track(Event.HomeMenuSettingsItemClicked)
}
- HomeMenu.Item.SyncedTabs -> {
+ HomeMenu.Item.SyncTabs -> {
hideOnboardingIfNeeded()
nav(
R.id.homeFragment,
HomeFragmentDirections.actionGlobalSyncedTabsFragment()
)
}
+ is HomeMenu.Item.SyncAccount -> {
+ hideOnboardingIfNeeded()
+ val directions = if (it.signedIn) {
+ BrowserFragmentDirections.actionGlobalAccountSettingsFragment()
+ } else {
+ BrowserFragmentDirections.actionGlobalTurnOnSync()
+ }
+ nav(
+ R.id.homeFragment,
+ directions
+ )
+ }
HomeMenu.Item.Bookmarks -> {
hideOnboardingIfNeeded()
nav(
@@ -841,19 +861,22 @@ class HomeFragment : Fragment() {
}
)
}
- HomeMenu.Item.Sync -> {
+ HomeMenu.Item.ReconnectSync -> {
hideOnboardingIfNeeded()
nav(
R.id.homeFragment,
HomeFragmentDirections.actionGlobalAccountProblemFragment()
)
}
- HomeMenu.Item.AddonsManager -> {
+ HomeMenu.Item.Extensions -> {
nav(
R.id.homeFragment,
HomeFragmentDirections.actionGlobalAddonsManagementFragment()
)
}
+ is HomeMenu.Item.DesktopMode -> {
+ context.settings().openNextTabInDesktopMode = it.checked
+ }
}
},
onHighlightPresent = { menuButtonView.get()?.setHighlight(it) },
diff --git a/app/src/main/java/org/mozilla/fenix/home/HomeFragmentStore.kt b/app/src/main/java/org/mozilla/fenix/home/HomeFragmentStore.kt
index f1284ee6a..59b25cc40 100644
--- a/app/src/main/java/org/mozilla/fenix/home/HomeFragmentStore.kt
+++ b/app/src/main/java/org/mozilla/fenix/home/HomeFragmentStore.kt
@@ -48,7 +48,8 @@ data class HomeFragmentState(
val mode: Mode,
val topSites: List,
val tip: Tip? = null,
- val showCollectionPlaceholder: Boolean
+ val showCollectionPlaceholder: Boolean,
+ val showSetAsDefaultBrowserCard: Boolean
) : State
sealed class HomeFragmentAction : Action {
@@ -69,6 +70,7 @@ sealed class HomeFragmentAction : Action {
data class TopSitesChange(val topSites: List) : HomeFragmentAction()
data class RemoveTip(val tip: Tip) : HomeFragmentAction()
object RemoveCollectionsPlaceholder : HomeFragmentAction()
+ object RemoveSetDefaultBrowserCard : HomeFragmentAction()
}
private fun homeFragmentStateReducer(
@@ -102,5 +104,6 @@ private fun homeFragmentStateReducer(
is HomeFragmentAction.RemoveCollectionsPlaceholder -> {
state.copy(showCollectionPlaceholder = false)
}
+ is HomeFragmentAction.RemoveSetDefaultBrowserCard -> state.copy(showSetAsDefaultBrowserCard = false)
}
}
diff --git a/app/src/main/java/org/mozilla/fenix/home/HomeMenu.kt b/app/src/main/java/org/mozilla/fenix/home/HomeMenu.kt
index 6c1a32760..cf89bd2bb 100644
--- a/app/src/main/java/org/mozilla/fenix/home/HomeMenu.kt
+++ b/app/src/main/java/org/mozilla/fenix/home/HomeMenu.kt
@@ -13,15 +13,20 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import mozilla.components.browser.menu.BrowserMenuBuilder
import mozilla.components.browser.menu.BrowserMenuHighlight
+import mozilla.components.browser.menu.BrowserMenuItem
import mozilla.components.browser.menu.ext.getHighlight
import mozilla.components.browser.menu.item.BrowserMenuDivider
import mozilla.components.browser.menu.item.BrowserMenuHighlightableItem
+import mozilla.components.browser.menu.item.BrowserMenuImageSwitch
import mozilla.components.browser.menu.item.BrowserMenuImageText
import mozilla.components.concept.sync.AccountObserver
import mozilla.components.concept.sync.AuthType
import mozilla.components.concept.sync.OAuthAccount
import mozilla.components.support.ktx.android.content.getColorFromAttr
+import org.mozilla.fenix.FeatureFlags
+import org.mozilla.fenix.FeatureFlags.tabsTrayRewrite
import org.mozilla.fenix.R
+import org.mozilla.fenix.components.accounts.FenixAccountManager
import org.mozilla.fenix.experiments.ExperimentBranch
import org.mozilla.fenix.experiments.Experiments
import org.mozilla.fenix.ext.components
@@ -30,6 +35,7 @@ import org.mozilla.fenix.ext.withExperiment
import org.mozilla.fenix.theme.ThemeManager
import org.mozilla.fenix.whatsnew.WhatsNew
+@Suppress("LargeClass", "LongMethod")
class HomeMenu(
private val lifecycleOwner: LifecycleOwner,
private val context: Context,
@@ -38,26 +44,28 @@ class HomeMenu(
private val onHighlightPresent: (BrowserMenuHighlight) -> Unit = {}
) {
sealed class Item {
+ object Bookmarks : Item()
+ object History : Item()
+ object Downloads : Item()
+ object Extensions : Item()
+ object SyncTabs : Item()
+ data class SyncAccount(val signedIn: Boolean) : Item()
object WhatsNew : Item()
object Help : Item()
- object AddonsManager : Item()
object Settings : Item()
- object SyncedTabs : Item()
- object History : Item()
- object Bookmarks : Item()
- object Downloads : Item()
object Quit : Item()
- object Sync : Item()
+ object ReconnectSync : Item()
+ data class DesktopMode(val checked: Boolean) : Item()
}
- private val primaryTextColor =
- ThemeManager.resolveAttribute(R.attr.primaryText, context)
- private val syncDisconnectedColor = ThemeManager.resolveAttribute(R.attr.syncDisconnected, context)
- private val syncDisconnectedBackgroundColor = context.getColorFromAttr(R.attr.syncDisconnectedBackground)
+ private val primaryTextColor = ThemeManager.resolveAttribute(R.attr.primaryText, context)
+ private val syncDisconnectedColor =
+ ThemeManager.resolveAttribute(R.attr.syncDisconnected, context)
+ private val syncDisconnectedBackgroundColor =
+ context.getColorFromAttr(R.attr.syncDisconnectedBackground)
- private val menuCategoryTextColor =
- ThemeManager.resolveAttribute(R.attr.menuCategoryText, context)
private val shouldUseBottomToolbar = context.settings().shouldUseBottomToolbar
+ private val accountManager = FenixAccountManager(context)
// 'Reconnect' and 'Quit' items aren't needed most of the time, so we'll only create the if necessary.
private val reconnectToSyncItem by lazy {
@@ -72,7 +80,7 @@ class HomeMenu(
),
isHighlighted = { true }
) {
- onItemTapped.invoke(Item.Sync)
+ onItemTapped.invoke(Item.ReconnectSync)
}
}
@@ -86,7 +94,34 @@ class HomeMenu(
}
}
- private val coreMenuItems by lazy {
+ val syncedTabsItem = BrowserMenuImageText(
+ context.getString(R.string.synced_tabs),
+ R.drawable.ic_synced_tabs,
+ primaryTextColor
+ ) {
+ onItemTapped.invoke(Item.SyncTabs)
+ }
+
+ private fun getSyncItemTitle(): String {
+ val authenticatedAccount = accountManager.authenticatedAccount
+ val email = accountManager.accountProfileEmail
+
+ return if (authenticatedAccount && !email.isNullOrEmpty()) {
+ email
+ } else {
+ context.getString(R.string.sync_menu_sign_in)
+ }
+ }
+
+ val syncSignInMenuItem = BrowserMenuImageText(
+ getSyncItemTitle(),
+ R.drawable.ic_synced_tabs,
+ primaryTextColor
+ ) {
+ onItemTapped.invoke(Item.SyncAccount(accountManager.signedInToFxa()))
+ }
+
+ private val oldCoreMenuItems by lazy {
val whatsNewItem = BrowserMenuHighlightableItem(
context.getString(R.string.browser_menu_whats_new),
R.drawable.ic_whats_new,
@@ -98,17 +133,10 @@ class HomeMenu(
) {
onItemTapped.invoke(Item.WhatsNew)
}
-
val experiments = context.components.analytics.experiments
- val bookmarksIcon = experiments.withExperiment(Experiments.BOOKMARK_ICON) {
- when (it) {
- ExperimentBranch.TREATMENT -> R.drawable.ic_bookmark_list
- else -> R.drawable.ic_bookmark_filled
- }
- }
val bookmarksItem = BrowserMenuImageText(
context.getString(R.string.library_bookmarks),
- bookmarksIcon,
+ R.drawable.ic_bookmark_list,
primaryTextColor
) {
onItemTapped.invoke(Item.Bookmarks)
@@ -141,7 +169,7 @@ class HomeMenu(
R.drawable.ic_addons_extensions,
primaryTextColor
) {
- onItemTapped.invoke(Item.AddonsManager)
+ onItemTapped.invoke(Item.Extensions)
}
val settingsItem = BrowserMenuImageText(
@@ -152,14 +180,6 @@ class HomeMenu(
onItemTapped.invoke(Item.Settings)
}
- val syncedTabsItem = BrowserMenuImageText(
- context.getString(R.string.library_synced_tabs),
- R.drawable.ic_synced_tabs,
- primaryTextColor
- ) {
- onItemTapped.invoke(Item.SyncedTabs)
- }
-
val helpItem = BrowserMenuImageText(
context.getString(R.string.browser_menu_help),
R.drawable.ic_help,
@@ -211,9 +231,140 @@ class HomeMenu(
}
}
+ val desktopItem = BrowserMenuImageSwitch(
+ imageResource = R.drawable.ic_desktop,
+ label = context.getString(R.string.browser_menu_desktop_site),
+ initialState = { context.settings().openNextTabInDesktopMode }
+ ) { checked ->
+ onItemTapped.invoke(Item.DesktopMode(checked))
+ }
+
+ @Suppress("ComplexMethod")
+ private fun newCoreMenuItems(): List {
+ val experiments = context.components.analytics.experiments
+ val settings = context.components.settings
+
+ val bookmarksItem = BrowserMenuImageText(
+ context.getString(R.string.library_bookmarks),
+ R.drawable.ic_bookmark_list,
+ primaryTextColor
+ ) {
+ onItemTapped.invoke(Item.Bookmarks)
+ }
+
+ // We want to validate that the Nimbus experiments library is working, from the android UI
+ // all the way back to the data science backend. We're not testing the user's preference
+ // or response, we're end-to-end testing the experiments platform.
+ // So here, we're running multiple identical branches with the same treatment, and if the
+ // user isn't targeted, then we get still get the same treatment.
+ // The `let` block is degenerate here, but left here so as to document the form of how experiments
+ // are implemented here.
+ val historyIcon = experiments.withExperiment(Experiments.A_A_NIMBUS_VALIDATION) {
+ when (it) {
+ ExperimentBranch.A1 -> R.drawable.ic_history
+ ExperimentBranch.A2 -> R.drawable.ic_history
+ else -> R.drawable.ic_history
+ }
+ }
+ val historyItem = BrowserMenuImageText(
+ context.getString(R.string.library_history),
+ historyIcon,
+ primaryTextColor
+ ) {
+ onItemTapped.invoke(Item.History)
+ }
+
+ val downloadsItem = BrowserMenuImageText(
+ context.getString(R.string.library_downloads),
+ R.drawable.ic_download,
+ primaryTextColor
+ ) {
+ onItemTapped.invoke(Item.Downloads)
+ }
+
+ val extensionsItem = BrowserMenuImageText(
+ context.getString(R.string.browser_menu_add_ons),
+ R.drawable.ic_addons_extensions,
+ primaryTextColor
+ ) {
+ onItemTapped.invoke(Item.Extensions)
+ }
+
+ val whatsNewItem = BrowserMenuHighlightableItem(
+ context.getString(R.string.browser_menu_whats_new),
+ R.drawable.ic_whats_new,
+ iconTintColorResource = primaryTextColor,
+ highlight = BrowserMenuHighlight.LowPriority(
+ notificationTint = getColor(context, R.color.whats_new_notification_color)
+ ),
+ isHighlighted = { WhatsNew.shouldHighlightWhatsNew(context) }
+ ) {
+ onItemTapped.invoke(Item.WhatsNew)
+ }
+
+ val helpItem = BrowserMenuImageText(
+ context.getString(R.string.browser_menu_help),
+ R.drawable.ic_help,
+ primaryTextColor
+ ) {
+ onItemTapped.invoke(Item.Help)
+ }
+
+ val settingsItem = BrowserMenuImageText(
+ context.getString(R.string.browser_menu_settings),
+ R.drawable.ic_settings,
+ primaryTextColor
+ ) {
+ onItemTapped.invoke(Item.Settings)
+ }
+
+ // Only query account manager if it has been initialized.
+ // We don't want to cause its initialization just for this check.
+ val accountAuthItem =
+ if (context.components.backgroundServices.accountManagerAvailableQueue.isReady() &&
+ context.components.backgroundServices.accountManager.accountNeedsReauth()) {
+ reconnectToSyncItem
+ } else {
+ null
+ }
+
+ val menuItems = listOfNotNull(
+ bookmarksItem,
+ historyItem,
+ downloadsItem,
+ extensionsItem,
+ if (tabsTrayRewrite) syncSignInMenuItem else syncedTabsItem,
+ accountAuthItem,
+ BrowserMenuDivider(),
+ desktopItem,
+ BrowserMenuDivider(),
+ whatsNewItem,
+ helpItem,
+ settingsItem,
+ if (settings.shouldDeleteBrowsingDataOnQuit) quitItem else null
+ ).also { items ->
+ items.getHighlight()?.let { onHighlightPresent(it) }
+ }
+
+ return menuItems
+ }
+
init {
+ val menuItems = if (FeatureFlags.toolbarMenuFeature) {
+ newCoreMenuItems()
+ } else {
+ oldCoreMenuItems
+ }
+
// Report initial state.
- onMenuBuilderChanged(BrowserMenuBuilder(coreMenuItems))
+ onMenuBuilderChanged(BrowserMenuBuilder(menuItems))
+
+ val menuItemsWithReconnectItem = if (FeatureFlags.toolbarMenuFeature) {
+ menuItems
+ } else {
+ // reconnect item is manually added to the beginning of the list
+ listOf(reconnectToSyncItem) + menuItems
+ }
// Observe account state changes, and update menu item builder with a new set of items.
context.components.backgroundServices.accountManagerAvailableQueue.runIfReadyOrQueue {
@@ -224,9 +375,11 @@ class HomeMenu(
context.components.backgroundServices.accountManager.register(object : AccountObserver {
override fun onAuthenticationProblems() {
lifecycleOwner.lifecycleScope.launch(Dispatchers.Main) {
- onMenuBuilderChanged(BrowserMenuBuilder(
- listOf(reconnectToSyncItem) + coreMenuItems
- ))
+ onMenuBuilderChanged(
+ BrowserMenuBuilder(
+ menuItemsWithReconnectItem
+ )
+ )
}
}
@@ -234,7 +387,7 @@ class HomeMenu(
lifecycleOwner.lifecycleScope.launch(Dispatchers.Main) {
onMenuBuilderChanged(
BrowserMenuBuilder(
- coreMenuItems
+ menuItems
)
)
}
@@ -244,7 +397,7 @@ class HomeMenu(
lifecycleOwner.lifecycleScope.launch(Dispatchers.Main) {
onMenuBuilderChanged(
BrowserMenuBuilder(
- coreMenuItems
+ menuItems
)
)
}
diff --git a/app/src/main/java/org/mozilla/fenix/home/intent/CrashReporterIntentProcessor.kt b/app/src/main/java/org/mozilla/fenix/home/intent/CrashReporterIntentProcessor.kt
index dfc6feac4..7f3238e08 100644
--- a/app/src/main/java/org/mozilla/fenix/home/intent/CrashReporterIntentProcessor.kt
+++ b/app/src/main/java/org/mozilla/fenix/home/intent/CrashReporterIntentProcessor.kt
@@ -8,6 +8,7 @@ import android.content.Intent
import androidx.navigation.NavController
import mozilla.components.lib.crash.Crash
import org.mozilla.fenix.NavGraphDirections
+import org.mozilla.fenix.ext.navigateBlockingForAsyncNavGraph
/**
* When the app crashes, the user has the option to report it.
@@ -26,6 +27,6 @@ class CrashReporterIntentProcessor : HomeIntentProcessor {
private fun openToCrashReporter(intent: Intent, navController: NavController) {
val directions = NavGraphDirections.actionGlobalCrashReporter(intent)
- navController.navigate(directions)
+ navController.navigateBlockingForAsyncNavGraph(directions)
}
}
diff --git a/app/src/main/java/org/mozilla/fenix/home/intent/DeepLinkIntentProcessor.kt b/app/src/main/java/org/mozilla/fenix/home/intent/DeepLinkIntentProcessor.kt
deleted file mode 100644
index 5d1893db5..000000000
--- a/app/src/main/java/org/mozilla/fenix/home/intent/DeepLinkIntentProcessor.kt
+++ /dev/null
@@ -1,153 +0,0 @@
-/* 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.home.intent
-
-import android.content.Context
-import android.content.Intent
-import android.net.Uri
-import android.os.Build
-import android.os.Build.VERSION.SDK_INT
-import android.provider.Settings
-import androidx.navigation.NavController
-import mozilla.components.concept.engine.EngineSession
-import mozilla.components.support.base.log.logger.Logger
-import org.mozilla.fenix.BrowserDirection
-import org.mozilla.fenix.BuildConfig
-import org.mozilla.fenix.GlobalDirections
-import org.mozilla.fenix.HomeActivity
-import org.mozilla.fenix.browser.browsingmode.BrowsingMode
-import org.mozilla.fenix.components.SearchWidgetCreator
-import org.mozilla.fenix.ext.alreadyOnDestination
-
-/**
- * Deep links in the form of `fenix://host` open different parts of the app.
- *
- * @param verifier [DeepLinkVerifier] that will be used to verify deep links before handling them.
- */
-class DeepLinkIntentProcessor(
- private val activity: HomeActivity,
- private val verifier: DeepLinkVerifier
-) : HomeIntentProcessor {
- private val logger = Logger("DeepLinkIntentProcessor")
-
- override fun process(intent: Intent, navController: NavController, out: Intent): Boolean {
- val scheme = intent.scheme?.equals(BuildConfig.DEEP_LINK_SCHEME, ignoreCase = true) ?: return false
- return if (scheme) {
- intent.data?.let { handleDeepLink(it, navController) }
- true
- } else {
- false
- }
- }
-
- @Suppress("ComplexMethod")
- private fun handleDeepLink(deepLink: Uri, navController: NavController) {
- if (!verifier.verifyDeepLink(deepLink)) {
- logger.warn("Invalid deep link: $deepLink")
- return
- }
-
- handleDeepLinkSideEffects(deepLink)
-
- val globalDirections = when (deepLink.host) {
- "home", "enable_private_browsing" -> GlobalDirections.Home
- "urls_bookmarks" -> GlobalDirections.Bookmarks
- "urls_history" -> GlobalDirections.History
- "settings" -> GlobalDirections.Settings
- "turn_on_sync" -> GlobalDirections.Sync
- "settings_search_engine" -> GlobalDirections.SearchEngine
- "settings_accessibility" -> GlobalDirections.Accessibility
- "settings_delete_browsing_data" -> GlobalDirections.DeleteData
- "settings_addon_manager" -> GlobalDirections.SettingsAddonManager
- "settings_logins" -> GlobalDirections.SettingsLogins
- "settings_tracking_protection" -> GlobalDirections.SettingsTrackingProtection
- // We'd like to highlight views within the fragment
- // https://github.com/mozilla-mobile/fenix/issues/11856
- // The current version of UI has these features in more complex screens.
- "settings_privacy" -> GlobalDirections.Settings
- "home_collections" -> GlobalDirections.Home
-
- else -> return
- }
-
- if (!navController.alreadyOnDestination(globalDirections.destinationId)) {
- navController.navigate(globalDirections.navDirections)
- }
- }
-
- /**
- * Handle links that require more than just simple navigation.
- */
- private fun handleDeepLinkSideEffects(deepLink: Uri) {
- when (deepLink.host) {
- "enable_private_browsing" -> {
- activity.browsingModeManager.mode = BrowsingMode.Private
- }
- "make_default_browser" -> {
- if (SDK_INT >= Build.VERSION_CODES.N) {
- val settingsIntent = Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS)
- activity.startActivity(settingsIntent)
- }
- }
- "open" -> {
- val url = deepLink.getQueryParameter("url")
- if (url == null || !url.startsWith("https://")) {
- logger.info("Not opening deep link: $url")
- return
- }
-
- activity.openToBrowserAndLoad(
- url,
- newTab = true,
- from = BrowserDirection.FromGlobal,
- flags = EngineSession.LoadUrlFlags.external()
- )
- }
- "settings_notifications" -> {
- val intent = notificationSettings(activity)
- activity.startActivity(intent)
- }
- "install_search_widget" -> {
- if (SDK_INT >= Build.VERSION_CODES.O) {
- SearchWidgetCreator.createSearchWidget(activity)
- }
- }
- }
- }
-
- private fun notificationSettings(context: Context, channel: String? = null) =
- Intent().apply {
- when {
- SDK_INT >= Build.VERSION_CODES.O -> {
- action = channel?.let {
- putExtra(Settings.EXTRA_CHANNEL_ID, it)
- Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS
- } ?: Settings.ACTION_APP_NOTIFICATION_SETTINGS
- putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName)
- }
- SDK_INT >= Build.VERSION_CODES.LOLLIPOP -> {
- action = "android.settings.APP_NOTIFICATION_SETTINGS"
- putExtra("app_package", context.packageName)
- putExtra("app_uid", context.applicationInfo.uid)
- }
- else -> {
- action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
- addCategory(Intent.CATEGORY_DEFAULT)
- data = Uri.parse("package:" + context.packageName)
- }
- }
- }
-
- /**
- * Interface for a class that verifies deep links before they get handled.
- */
- interface DeepLinkVerifier {
- /**
- * Verifies the given deep link and returns `true` for verified deep links or `false` for
- * rejected deep links.
- */
- fun verifyDeepLink(deepLink: Uri): Boolean
- }
-}
diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlAdapter.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlAdapter.kt
index e8d22d4c2..74434731d 100644
--- a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlAdapter.kt
+++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlAdapter.kt
@@ -24,6 +24,7 @@ import org.mozilla.fenix.home.sessioncontrol.viewholders.NoCollectionsMessageVie
import org.mozilla.fenix.home.sessioncontrol.viewholders.PrivateBrowsingDescriptionViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.TabInCollectionViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.TopSitePagerViewHolder
+import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.ExperimentDefaultBrowserCardViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.OnboardingAutomaticSignInViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.OnboardingFinishViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.OnboardingHeaderViewHolder
@@ -116,6 +117,8 @@ sealed class AdapterItem(@LayoutRes val viewType: Int) {
val state: OnboardingState.SignedOutCanAutoSignIn
) : AdapterItem(OnboardingAutomaticSignInViewHolder.LAYOUT_ID)
+ object ExperimentDefaultBrowserCard : AdapterItem(ExperimentDefaultBrowserCardViewHolder.LAYOUT_ID)
+
object OnboardingThemePicker : AdapterItem(OnboardingThemePickerViewHolder.LAYOUT_ID)
object OnboardingTrackingProtection :
AdapterItem(OnboardingTrackingProtectionViewHolder.LAYOUT_ID)
@@ -207,6 +210,8 @@ class SessionControlAdapter(
OnboardingToolbarPositionPickerViewHolder.LAYOUT_ID -> OnboardingToolbarPositionPickerViewHolder(
view
)
+ ExperimentDefaultBrowserCardViewHolder.LAYOUT_ID -> ExperimentDefaultBrowserCardViewHolder(view, interactor)
+
else -> throw IllegalStateException()
}
}
diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlController.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlController.kt
index 8339d8c87..318dcb163 100644
--- a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlController.kt
+++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlController.kt
@@ -39,6 +39,7 @@ import org.mozilla.fenix.components.tips.Tip
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.metrics
import org.mozilla.fenix.ext.nav
+import org.mozilla.fenix.ext.openSetDefaultBrowserOption
import org.mozilla.fenix.home.HomeFragment
import org.mozilla.fenix.home.HomeFragmentAction
import org.mozilla.fenix.home.HomeFragmentDirections
@@ -167,6 +168,16 @@ interface SessionControlController {
* @see [CollectionInteractor.onCollectionMenuOpened] and [TopSiteInteractor.onTopSiteMenuOpened]
*/
fun handleMenuOpened()
+
+ /**
+ * @see [ExperimentCardInteractor.onSetDefaultBrowserClicked]
+ */
+ fun handleSetDefaultBrowser()
+
+ /**
+ * @see [ExperimentCardInteractor.onCloseExperimentCardClicked]
+ */
+ fun handleCloseExperimentCard()
}
@Suppress("TooManyFunctions", "LargeClass")
@@ -181,6 +192,7 @@ class DefaultSessionControlController(
private val restoreUseCase: TabsUseCases.RestoreUseCase,
private val reloadUrlUseCase: SessionUseCases.ReloadUrlUseCase,
private val selectTabUseCase: TabsUseCases.SelectTabUseCase,
+ private val requestDesktopSiteUseCase: SessionUseCases.RequestDesktopSiteUseCase,
private val fragmentStore: HomeFragmentStore,
private val navController: NavController,
private val viewLifecycleScope: CoroutineScope,
@@ -403,6 +415,10 @@ class DefaultSessionControlController(
selectTab = true,
startLoading = true
)
+
+ if (settings.openNextTabInDesktopMode) {
+ activity.handleRequestDesktopMode()
+ }
activity.openToBrowser(BrowserDirection.FromHome)
}
@@ -548,4 +564,16 @@ class DefaultSessionControlController(
)
navController.nav(R.id.homeFragment, directions)
}
+
+ override fun handleSetDefaultBrowser() {
+ settings.userDismissedExperimentCard = true
+ metrics.track(Event.SetDefaultBrowserNewTabClicked)
+ activity.openSetDefaultBrowserOption()
+ }
+
+ override fun handleCloseExperimentCard() {
+ settings.userDismissedExperimentCard = true
+ metrics.track(Event.CloseExperimentCardClicked)
+ fragmentStore.dispatch(HomeFragmentAction.RemoveSetDefaultBrowserCard)
+ }
}
diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlInteractor.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlInteractor.kt
index 33bdd0f31..c88fa0fc1 100644
--- a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlInteractor.kt
+++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlInteractor.kt
@@ -190,6 +190,18 @@ interface TopSiteInteractor {
fun onTopSiteMenuOpened()
}
+interface ExperimentCardInteractor {
+ /**
+ * Called when set default browser button is clicked
+ */
+ fun onSetDefaultBrowserClicked()
+
+ /**
+ * Called when close button on experiment card
+ */
+ fun onCloseExperimentCardClicked()
+}
+
/**
* Interactor for the Home screen.
* Provides implementations for the CollectionInteractor, OnboardingInteractor,
@@ -199,7 +211,7 @@ interface TopSiteInteractor {
class SessionControlInteractor(
private val controller: SessionControlController
) : CollectionInteractor, OnboardingInteractor, TopSiteInteractor, TipInteractor,
- TabSessionInteractor, ToolbarInteractor {
+ TabSessionInteractor, ToolbarInteractor, ExperimentCardInteractor {
override fun onCollectionAddTabTapped(collection: TabCollection) {
controller.handleCollectionAddTabTapped(collection)
}
@@ -295,4 +307,12 @@ class SessionControlInteractor(
override fun onTopSiteMenuOpened() {
controller.handleMenuOpened()
}
+
+ override fun onSetDefaultBrowserClicked() {
+ controller.handleSetDefaultBrowser()
+ }
+
+ override fun onCloseExperimentCardClicked() {
+ controller.handleCloseExperimentCard()
+ }
}
diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlView.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlView.kt
index 3258fb31d..859d23d1a 100644
--- a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlView.kt
+++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlView.kt
@@ -12,7 +12,6 @@ import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.extensions.LayoutContainer
import mozilla.components.feature.tab.collections.TabCollection
import mozilla.components.feature.top.sites.TopSite
-import org.mozilla.fenix.R
import org.mozilla.fenix.components.tips.Tip
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.home.HomeFragmentState
@@ -22,18 +21,23 @@ import org.mozilla.fenix.home.OnboardingState
// This method got a little complex with the addition of the tab tray feature flag
// When we remove the tabs from the home screen this will get much simpler again.
-@Suppress("ComplexMethod")
+@Suppress("ComplexMethod", "LongParameterList")
private fun normalModeAdapterItems(
topSites: List,
collections: List,
expandedCollections: Set,
tip: Tip?,
- showCollectionsPlaceholder: Boolean
+ showCollectionsPlaceholder: Boolean,
+ showSetAsDefaultBrowserCard: Boolean
): List {
val items = mutableListOf()
tip?.let { items.add(AdapterItem.TipItem(it)) }
+ if (showSetAsDefaultBrowserCard) {
+ items.add(AdapterItem.ExperimentDefaultBrowserCard)
+ }
+
if (topSites.isNotEmpty()) {
items.add(AdapterItem.TopSitePager(topSites))
}
@@ -71,6 +75,13 @@ private fun privateModeAdapterItems() = listOf(AdapterItem.PrivateBrowsingDescri
private fun onboardingAdapterItems(onboardingState: OnboardingState): List {
val items: MutableList = mutableListOf(AdapterItem.OnboardingHeader)
+ items.addAll(
+ listOf(
+ AdapterItem.OnboardingThemePicker,
+ AdapterItem.OnboardingToolbarPositionPicker,
+ AdapterItem.OnboardingTrackingProtection
+ )
+ )
// Customize FxA items based on where we are with the account state:
items.addAll(
when (onboardingState) {
@@ -90,14 +101,6 @@ private fun onboardingAdapterItems(onboardingState: OnboardingState): List = when (mode) {
collections,
expandedCollections,
tip,
- showCollectionPlaceholder
+ showCollectionPlaceholder,
+ showSetAsDefaultBrowserCard
)
is Mode.Private -> privateModeAdapterItems()
is Mode.Onboarding -> onboardingAdapterItems(mode.state)
diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/ExperimentDefaultBrowserCardViewHolder.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/ExperimentDefaultBrowserCardViewHolder.kt
new file mode 100644
index 000000000..27476132e
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/ExperimentDefaultBrowserCardViewHolder.kt
@@ -0,0 +1,36 @@
+/* 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.home.sessioncontrol.viewholders.onboarding
+
+import android.view.View
+import androidx.recyclerview.widget.RecyclerView
+import kotlinx.android.synthetic.main.experiment_default_browser.view.*
+import org.mozilla.fenix.R
+import org.mozilla.fenix.ext.increaseTapArea
+import org.mozilla.fenix.home.sessioncontrol.SessionControlInteractor
+
+class ExperimentDefaultBrowserCardViewHolder(
+ view: View,
+ private val interactor: SessionControlInteractor
+) : RecyclerView.ViewHolder(view) {
+
+ init {
+ view.set_default_browser.setOnClickListener {
+ interactor.onSetDefaultBrowserClicked()
+ }
+
+ view.close.apply {
+ increaseTapArea(CLOSE_BUTTON_EXTRA_DPS)
+ setOnClickListener {
+ interactor.onCloseExperimentCardClicked()
+ }
+ }
+ }
+
+ companion object {
+ internal const val LAYOUT_ID = R.layout.experiment_default_browser
+ private const val CLOSE_BUTTON_EXTRA_DPS = 38
+ }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingManualSignInViewHolder.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingManualSignInViewHolder.kt
index 98155f061..a40cc55de 100644
--- a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingManualSignInViewHolder.kt
+++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingManualSignInViewHolder.kt
@@ -10,35 +10,26 @@ import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.onboarding_manual_signin.view.*
import org.mozilla.fenix.R
import org.mozilla.fenix.components.metrics.Event
-import org.mozilla.fenix.ext.addUnderline
import org.mozilla.fenix.ext.components
+import org.mozilla.fenix.ext.navigateBlockingForAsyncNavGraph
import org.mozilla.fenix.home.HomeFragmentDirections
-import org.mozilla.fenix.onboarding.OnboardingController
-import org.mozilla.fenix.onboarding.OnboardingInteractor
class OnboardingManualSignInViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private val headerText = view.header_text
init {
- val interactor = OnboardingInteractor(OnboardingController(itemView.context))
-
view.fxa_sign_in_button.setOnClickListener {
it.context.components.analytics.metrics.track(Event.OnboardingManualSignIn)
val directions = HomeFragmentDirections.actionGlobalTurnOnSync()
- Navigation.findNavController(view).navigate(directions)
- }
-
- view.learn_more.addUnderline()
- view.learn_more.setOnClickListener {
- interactor.onLearnMoreClicked()
+ Navigation.findNavController(view).navigateBlockingForAsyncNavGraph(directions)
}
}
fun bind() {
val context = itemView.context
- headerText.text = context.getString(R.string.onboarding_account_sign_in_header)
+ headerText.text = context.getString(R.string.onboarding_account_sign_in_header_1)
}
companion object {
diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingPrivacyNoticeViewHolder.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingPrivacyNoticeViewHolder.kt
index 668c17cee..9f4d752c7 100644
--- a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingPrivacyNoticeViewHolder.kt
+++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingPrivacyNoticeViewHolder.kt
@@ -18,7 +18,7 @@ class OnboardingPrivacyNoticeViewHolder(
) : RecyclerView.ViewHolder(view) {
init {
- view.header_text.setOnboardingIcon(R.drawable.ic_onboarding_privacy_notice)
+ view.header_text.setOnboardingIcon(R.drawable.ic_info)
val appName = view.context.getString(R.string.app_name)
view.description_text.text = view.context.getString(R.string.onboarding_privacy_notice_description2, appName)
diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingTrackingProtectionViewHolder.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingTrackingProtectionViewHolder.kt
index 9d2e00013..7c3b52610 100644
--- a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingTrackingProtectionViewHolder.kt
+++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingTrackingProtectionViewHolder.kt
@@ -5,7 +5,6 @@
package org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding
import android.view.View
-import androidx.appcompat.widget.SwitchCompat
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.onboarding_tracking_protection.view.*
import org.mozilla.fenix.R
@@ -20,29 +19,20 @@ class OnboardingTrackingProtectionViewHolder(view: View) : RecyclerView.ViewHold
private var standardTrackingProtection: OnboardingRadioButton
private var strictTrackingProtection: OnboardingRadioButton
- private var trackingProtectionToggle: SwitchCompat
init {
view.header_text.setOnboardingIcon(R.drawable.ic_onboarding_tracking_protection)
- trackingProtectionToggle = view.tracking_protection_toggle
standardTrackingProtection = view.tracking_protection_standard_option
strictTrackingProtection = view.tracking_protection_strict_default
view.description_text.text = view.context.getString(
- R.string.onboarding_tracking_protection_description_2
+ R.string.onboarding_tracking_protection_description_3
)
- trackingProtectionToggle.apply {
- isChecked = view.context.settings().shouldUseTrackingProtection
- setOnCheckedChangeListener { _, isChecked ->
- updateTrackingProtectionSetting(isChecked)
- updateRadioGroupState(isChecked)
- }
- }
-
- setupRadioGroup(trackingProtectionToggle.isChecked)
- updateRadioGroupState(trackingProtectionToggle.isChecked)
+ val isTrackingProtectionEnabled = view.context.settings().shouldUseTrackingProtection
+ setupRadioGroup(isTrackingProtectionEnabled)
+ updateRadioGroupState(isTrackingProtectionEnabled)
}
private fun setupRadioGroup(isChecked: Boolean) {
@@ -74,15 +64,6 @@ class OnboardingTrackingProtectionViewHolder(view: View) : RecyclerView.ViewHold
strictTrackingProtection.isEnabled = isChecked
}
- private fun updateTrackingProtectionSetting(enabled: Boolean) {
- itemView.context.settings().shouldUseTrackingProtection = enabled
- with(itemView.context.components) {
- val policy = core.trackingProtectionPolicyFactory.createTrackingProtectionPolicy()
- useCases.settingsUseCases.updateTrackingProtection.invoke(policy)
- useCases.sessionUseCases.reload.invoke()
- }
- }
-
private fun updateTrackingProtectionPolicy() {
itemView.context?.components?.let {
val policy = it.core.trackingProtectionPolicyFactory
diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/topsites/TopSiteItemViewHolder.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/topsites/TopSiteItemViewHolder.kt
index 48c4ee3e1..5ee55eb98 100644
--- a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/topsites/TopSiteItemViewHolder.kt
+++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/topsites/TopSiteItemViewHolder.kt
@@ -14,6 +14,7 @@ import kotlinx.android.synthetic.main.top_site_item.*
import mozilla.components.browser.menu.BrowserMenuBuilder
import mozilla.components.browser.menu.item.SimpleBrowserMenuItem
import mozilla.components.feature.top.sites.TopSite
+import mozilla.components.feature.top.sites.TopSite.Type.DEFAULT
import mozilla.components.feature.top.sites.TopSite.Type.FRECENT
import mozilla.components.feature.top.sites.TopSite.Type.PINNED
import org.mozilla.fenix.R
@@ -63,7 +64,7 @@ class TopSiteItemViewHolder(
fun bind(topSite: TopSite) {
top_site_title.text = topSite.title
- pin_indicator.visibility = if (topSite.type == PINNED) {
+ pin_indicator.visibility = if (topSite.type == PINNED || topSite.type == DEFAULT) {
View.VISIBLE
} else {
View.GONE
@@ -79,6 +80,9 @@ class TopSiteItemViewHolder(
SupportUtils.JD_URL -> {
favicon_image.setImageDrawable(getDrawable(itemView.context, R.drawable.ic_jd))
}
+ SupportUtils.PDD_URL -> {
+ favicon_image.setImageDrawable(getDrawable(itemView.context, R.drawable.ic_pdd))
+ }
else -> {
itemView.context.components.core.icons.loadIntoView(favicon_image, topSite.url)
}
diff --git a/app/src/main/java/org/mozilla/fenix/library/LibrarySiteItemView.kt b/app/src/main/java/org/mozilla/fenix/library/LibrarySiteItemView.kt
index c444c0489..67ade043d 100644
--- a/app/src/main/java/org/mozilla/fenix/library/LibrarySiteItemView.kt
+++ b/app/src/main/java/org/mozilla/fenix/library/LibrarySiteItemView.kt
@@ -19,34 +19,8 @@ import org.mozilla.fenix.R
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.increaseTapArea
import org.mozilla.fenix.ext.loadIntoView
-
-/**
- * Interactor for items that can be selected on the bookmarks and history screens.
- */
-interface SelectionInteractor {
- /**
- * Called when an item is tapped to open it.
- * @param item the tapped item to open.
- */
- fun open(item: T)
-
- /**
- * Called when an item is long pressed and selection mode is started,
- * or when selection mode has already started an an item is tapped.
- * @param item the item to select.
- */
- fun select(item: T)
-
- /**
- * Called when a selected item is tapped in selection mode and should no longer be selected.
- * @param item the item to deselect.
- */
- fun deselect(item: T)
-}
-
-interface SelectionHolder {
- val selectedItems: Set
-}
+import org.mozilla.fenix.selection.SelectionHolder
+import org.mozilla.fenix.selection.SelectionInteractor
class LibrarySiteItemView @JvmOverloads constructor(
context: Context,
diff --git a/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkController.kt b/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkController.kt
index 0b80684e4..3dc18e26d 100644
--- a/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkController.kt
+++ b/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkController.kt
@@ -88,7 +88,7 @@ class DefaultBookmarkController(
}
override fun handleBookmarkEdit(node: BookmarkNode) {
- navigate(BookmarkFragmentDirections.actionBookmarkFragmentToBookmarkEditFragment(node.guid))
+ navigateToGivenDirection(BookmarkFragmentDirections.actionBookmarkFragmentToBookmarkEditFragment(node.guid))
}
override fun handleBookmarkSelected(node: BookmarkNode) {
@@ -118,7 +118,7 @@ class DefaultBookmarkController(
}
override fun handleBookmarkSharing(item: BookmarkNode) {
- navigate(
+ navigateToGivenDirection(
BookmarkFragmentDirections.actionGlobalShareFragment(
data = arrayOf(ShareData(url = item.url, title = item.title))
)
@@ -182,7 +182,7 @@ class DefaultBookmarkController(
}
}
- private fun navigate(directions: NavDirections) {
+ private fun navigateToGivenDirection(directions: NavDirections) {
invokePendingDeletion.invoke()
navController.nav(R.id.bookmarkFragment, directions)
}
diff --git a/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkFragment.kt b/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkFragment.kt
index 8769e35af..4b93a4f31 100644
--- a/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkFragment.kt
+++ b/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkFragment.kt
@@ -202,7 +202,7 @@ class BookmarkFragment : LibraryPageFragment(), UserInteractionHan
true
}
R.id.add_bookmark_folder -> {
- navigate(
+ navigateToBookmarkFragment(
BookmarkFragmentDirections
.actionBookmarkFragmentToBookmarkAddFolderFragment()
)
@@ -226,7 +226,7 @@ class BookmarkFragment : LibraryPageFragment(), UserInteractionHan
val shareTabs = bookmarkStore.state.mode.selectedItems.map {
ShareData(url = it.url, title = it.title)
}
- navigate(
+ navigateToBookmarkFragment(
BookmarkFragmentDirections.actionGlobalShareFragment(
data = shareTabs.toTypedArray()
)
@@ -243,10 +243,10 @@ class BookmarkFragment : LibraryPageFragment(), UserInteractionHan
private fun showTabTray() {
invokePendingDeletion()
- navigate(BookmarkFragmentDirections.actionGlobalTabTrayDialogFragment())
+ navigateToBookmarkFragment(BookmarkFragmentDirections.actionGlobalTabTrayDialogFragment())
}
- private fun navigate(directions: NavDirections) {
+ private fun navigateToBookmarkFragment(directions: NavDirections) {
invokePendingDeletion()
findNavController().nav(
R.id.bookmarkFragment,
diff --git a/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkFragmentStore.kt b/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkFragmentStore.kt
index da5a14ae2..26901ddeb 100644
--- a/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkFragmentStore.kt
+++ b/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkFragmentStore.kt
@@ -9,7 +9,7 @@ import mozilla.components.concept.storage.BookmarkNode
import mozilla.components.lib.state.Action
import mozilla.components.lib.state.State
import mozilla.components.lib.state.Store
-import org.mozilla.fenix.library.SelectionHolder
+import org.mozilla.fenix.selection.SelectionHolder
class BookmarkFragmentStore(
initialState: BookmarkFragmentState
diff --git a/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkView.kt b/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkView.kt
index 5887a871d..d08bfd97e 100644
--- a/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkView.kt
+++ b/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkView.kt
@@ -15,8 +15,9 @@ import mozilla.components.concept.storage.BookmarkNode
import mozilla.components.support.base.feature.UserInteractionHandler
import org.mozilla.fenix.NavGraphDirections
import org.mozilla.fenix.R
+import org.mozilla.fenix.ext.navigateBlockingForAsyncNavGraph
import org.mozilla.fenix.library.LibraryPageView
-import org.mozilla.fenix.library.SelectionInteractor
+import org.mozilla.fenix.selection.SelectionInteractor
/**
* Interface for the Bookmarks view.
@@ -119,7 +120,7 @@ class BookmarkView(
adapter = bookmarkAdapter
}
view.bookmark_folders_sign_in.setOnClickListener {
- navController.navigate(NavGraphDirections.actionGlobalTurnOnSync())
+ navController.navigateBlockingForAsyncNavGraph(NavGraphDirections.actionGlobalTurnOnSync())
}
view.swipe_refresh.setOnRefreshListener {
interactor.onRequestSync()
diff --git a/app/src/main/java/org/mozilla/fenix/library/downloads/DownloadAdapter.kt b/app/src/main/java/org/mozilla/fenix/library/downloads/DownloadAdapter.kt
index 043053049..a24ccef52 100644
--- a/app/src/main/java/org/mozilla/fenix/library/downloads/DownloadAdapter.kt
+++ b/app/src/main/java/org/mozilla/fenix/library/downloads/DownloadAdapter.kt
@@ -8,7 +8,7 @@ import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
-import org.mozilla.fenix.library.SelectionHolder
+import org.mozilla.fenix.selection.SelectionHolder
import org.mozilla.fenix.library.downloads.viewholders.DownloadsListItemViewHolder
class DownloadAdapter(
diff --git a/app/src/main/java/org/mozilla/fenix/library/downloads/DownloadView.kt b/app/src/main/java/org/mozilla/fenix/library/downloads/DownloadView.kt
index 82ad310b1..b122ef8f0 100644
--- a/app/src/main/java/org/mozilla/fenix/library/downloads/DownloadView.kt
+++ b/app/src/main/java/org/mozilla/fenix/library/downloads/DownloadView.kt
@@ -17,7 +17,7 @@ import kotlinx.android.synthetic.main.component_history.view.swipe_refresh
import mozilla.components.support.base.feature.UserInteractionHandler
import org.mozilla.fenix.R
import org.mozilla.fenix.library.LibraryPageView
-import org.mozilla.fenix.library.SelectionInteractor
+import org.mozilla.fenix.selection.SelectionInteractor
/**
* Interface for the DownloadViewInteractor. This interface is implemented by objects that want
diff --git a/app/src/main/java/org/mozilla/fenix/library/downloads/viewholders/DownloadsListItemViewHolder.kt b/app/src/main/java/org/mozilla/fenix/library/downloads/viewholders/DownloadsListItemViewHolder.kt
index 46e76067d..5a80e3719 100644
--- a/app/src/main/java/org/mozilla/fenix/library/downloads/viewholders/DownloadsListItemViewHolder.kt
+++ b/app/src/main/java/org/mozilla/fenix/library/downloads/viewholders/DownloadsListItemViewHolder.kt
@@ -11,7 +11,7 @@ import kotlinx.android.synthetic.main.download_list_item.view.*
import kotlinx.android.synthetic.main.library_site_item.view.*
import mozilla.components.feature.downloads.toMegabyteOrKilobyteString
import org.mozilla.fenix.R
-import org.mozilla.fenix.library.SelectionHolder
+import org.mozilla.fenix.selection.SelectionHolder
import org.mozilla.fenix.library.downloads.DownloadInteractor
import org.mozilla.fenix.library.downloads.DownloadItem
import org.mozilla.fenix.ext.getIcon
diff --git a/app/src/main/java/org/mozilla/fenix/library/history/HistoryAdapter.kt b/app/src/main/java/org/mozilla/fenix/library/history/HistoryAdapter.kt
index e82bac914..32b43b221 100644
--- a/app/src/main/java/org/mozilla/fenix/library/history/HistoryAdapter.kt
+++ b/app/src/main/java/org/mozilla/fenix/library/history/HistoryAdapter.kt
@@ -11,7 +11,7 @@ import android.view.ViewGroup
import androidx.paging.PagedListAdapter
import androidx.recyclerview.widget.DiffUtil
import org.mozilla.fenix.R
-import org.mozilla.fenix.library.SelectionHolder
+import org.mozilla.fenix.selection.SelectionHolder
import org.mozilla.fenix.library.history.viewholders.HistoryListItemViewHolder
import java.util.Calendar
import java.util.Date
diff --git a/app/src/main/java/org/mozilla/fenix/library/history/HistoryController.kt b/app/src/main/java/org/mozilla/fenix/library/history/HistoryController.kt
index 96ef57698..8d9114a43 100644
--- a/app/src/main/java/org/mozilla/fenix/library/history/HistoryController.kt
+++ b/app/src/main/java/org/mozilla/fenix/library/history/HistoryController.kt
@@ -17,6 +17,7 @@ import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.components.FenixSnackbar
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.components.metrics.MetricController
+import org.mozilla.fenix.ext.navigateBlockingForAsyncNavGraph
@Suppress("TooManyFunctions")
interface HistoryController {
@@ -94,7 +95,7 @@ class DefaultHistoryController(
}
override fun handleShare(item: HistoryItem) {
- navController.navigate(
+ navController.navigateBlockingForAsyncNavGraph(
HistoryFragmentDirections.actionGlobalShareFragment(
data = arrayOf(ShareData(url = item.url, title = item.title))
)
@@ -110,7 +111,7 @@ class DefaultHistoryController(
}
override fun handleEnterRecentlyClosed() {
- navController.navigate(
+ navController.navigateBlockingForAsyncNavGraph(
HistoryFragmentDirections.actionGlobalRecentlyClosed(),
NavOptions.Builder().setPopUpTo(R.id.recentlyClosedFragment, true).build()
)
diff --git a/app/src/main/java/org/mozilla/fenix/library/history/HistoryFragment.kt b/app/src/main/java/org/mozilla/fenix/library/history/HistoryFragment.kt
index e3a826621..7d96b91b7 100644
--- a/app/src/main/java/org/mozilla/fenix/library/history/HistoryFragment.kt
+++ b/app/src/main/java/org/mozilla/fenix/library/history/HistoryFragment.kt
@@ -304,10 +304,10 @@ class HistoryFragment : LibraryPageFragment(), UserInteractionHandl
val directions = HistoryFragmentDirections.actionGlobalShareFragment(
data = data.toTypedArray()
)
- navigate(directions)
+ navigateToHistoryFragment(directions)
}
- private fun navigate(directions: NavDirections) {
+ private fun navigateToHistoryFragment(directions: NavDirections) {
invokePendingDeletion()
findNavController().nav(
R.id.historyFragment,
diff --git a/app/src/main/java/org/mozilla/fenix/library/history/HistoryView.kt b/app/src/main/java/org/mozilla/fenix/library/history/HistoryView.kt
index 3bfb5076a..5afbdc3d6 100644
--- a/app/src/main/java/org/mozilla/fenix/library/history/HistoryView.kt
+++ b/app/src/main/java/org/mozilla/fenix/library/history/HistoryView.kt
@@ -17,7 +17,7 @@ import mozilla.components.support.base.feature.UserInteractionHandler
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.library.LibraryPageView
-import org.mozilla.fenix.library.SelectionInteractor
+import org.mozilla.fenix.selection.SelectionInteractor
import org.mozilla.fenix.theme.ThemeManager
/**
diff --git a/app/src/main/java/org/mozilla/fenix/library/history/viewholders/HistoryListItemViewHolder.kt b/app/src/main/java/org/mozilla/fenix/library/history/viewholders/HistoryListItemViewHolder.kt
index 82a6e0eaa..2198713a9 100644
--- a/app/src/main/java/org/mozilla/fenix/library/history/viewholders/HistoryListItemViewHolder.kt
+++ b/app/src/main/java/org/mozilla/fenix/library/history/viewholders/HistoryListItemViewHolder.kt
@@ -15,7 +15,7 @@ import org.mozilla.fenix.R
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.hideAndDisable
import org.mozilla.fenix.ext.showAndEnable
-import org.mozilla.fenix.library.SelectionHolder
+import org.mozilla.fenix.selection.SelectionHolder
import org.mozilla.fenix.library.history.HistoryFragmentState
import org.mozilla.fenix.library.history.HistoryInteractor
import org.mozilla.fenix.library.history.HistoryItem
diff --git a/app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedController.kt b/app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedController.kt
index a4461a246..47f1b2935 100644
--- a/app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedController.kt
+++ b/app/src/main/java/org/mozilla/fenix/library/recentlyclosed/RecentlyClosedController.kt
@@ -19,6 +19,7 @@ import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.components.FenixSnackbar
+import org.mozilla.fenix.ext.navigateBlockingForAsyncNavGraph
interface RecentlyClosedController {
fun handleOpen(item: RecoverableTab, mode: BrowsingMode? = null)
@@ -48,7 +49,7 @@ class DefaultRecentlyClosedController(
}
override fun handleNavigateToHistory() {
- navController.navigate(
+ navController.navigateBlockingForAsyncNavGraph(
RecentlyClosedFragmentDirections.actionGlobalHistoryFragment(),
NavOptions.Builder().setPopUpTo(R.id.historyFragment, true).build()
)
@@ -64,7 +65,7 @@ class DefaultRecentlyClosedController(
}
override fun handleShare(item: RecoverableTab) {
- navController.navigate(
+ navController.navigateBlockingForAsyncNavGraph(
RecentlyClosedFragmentDirections.actionGlobalShareFragment(
data = arrayOf(ShareData(url = item.url, title = item.title))
)
diff --git a/app/src/main/java/org/mozilla/fenix/nimbus/NimbusDetailsFragment.kt b/app/src/main/java/org/mozilla/fenix/nimbus/NimbusDetailsFragment.kt
new file mode 100644
index 000000000..7caf576bd
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/nimbus/NimbusDetailsFragment.kt
@@ -0,0 +1,59 @@
+/* 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.nimbus
+
+import android.os.Bundle
+import android.view.View
+import androidx.fragment.app.Fragment
+import androidx.navigation.fragment.navArgs
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import mozilla.components.service.nimbus.ui.NimbusDetailAdapter
+import org.mozilla.fenix.R
+import org.mozilla.fenix.ext.showToolbar
+
+/**
+ * A fragment to show the details of a Nimbus experiment.
+ */
+class NimbusDetailsFragment : Fragment(R.layout.mozac_service_nimbus_experiment_details) {
+
+ private val args by navArgs()
+ private var adapter: NimbusDetailAdapter? = null
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ bindRecyclerView(view)
+ }
+
+ override fun onResume() {
+ super.onResume()
+ showToolbar(args.experiment)
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ // Letting go of the resources to avoid memory leak.
+ adapter = null
+ }
+
+ private fun bindRecyclerView(view: View) {
+ val recyclerView = view.findViewById(R.id.nimbus_experiment_branches_list)
+ recyclerView.layoutManager = LinearLayoutManager(requireContext())
+
+ val shouldRefresh = adapter != null
+
+ // Dummy data until we have the appropriate Nimbus API.
+ val branches = listOf(
+ "Control",
+ "Treatment"
+ )
+
+ if (!shouldRefresh) {
+ adapter = NimbusDetailAdapter(branches)
+ }
+
+ recyclerView.adapter = adapter
+ }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/nimbus/NimbusExperimentsFragment.kt b/app/src/main/java/org/mozilla/fenix/nimbus/NimbusExperimentsFragment.kt
new file mode 100644
index 000000000..4a8ec904f
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/nimbus/NimbusExperimentsFragment.kt
@@ -0,0 +1,85 @@
+/* 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.nimbus
+
+import android.os.Bundle
+import android.view.View
+import android.widget.TextView
+import androidx.core.view.isVisible
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.lifecycleScope
+import androidx.navigation.fragment.findNavController
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import kotlinx.coroutines.Dispatchers.IO
+import kotlinx.coroutines.Dispatchers.Main
+import kotlinx.coroutines.launch
+import mozilla.components.service.nimbus.ui.NimbusExperimentAdapter
+import mozilla.components.support.base.log.logger.Logger
+import org.mozilla.fenix.R
+import org.mozilla.fenix.ext.components
+import org.mozilla.fenix.ext.runIfFragmentIsAttached
+import org.mozilla.fenix.ext.showToolbar
+
+/**
+ * Fragment use for managing Nimbus experiments.
+ */
+@Suppress("TooGenericExceptionCaught")
+class NimbusExperimentsFragment : Fragment(R.layout.mozac_service_nimbus_experiments) {
+
+ private var adapter: NimbusExperimentAdapter? = null
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ bindRecyclerView(view)
+ }
+
+ override fun onResume() {
+ super.onResume()
+ showToolbar(getString(R.string.preferences_nimbus_experiments))
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ // Letting go of the resources to avoid memory leak.
+ adapter = null
+ }
+
+ private fun bindRecyclerView(view: View) {
+ val experimentsView = NimbusExperimentsView(
+ navController = findNavController()
+ )
+
+ val recyclerView = view.findViewById(R.id.nimbus_experiments_list)
+ recyclerView.layoutManager = LinearLayoutManager(requireContext())
+
+ val shouldRefresh = adapter != null
+
+ lifecycleScope.launch(IO) {
+ try {
+ val experiments =
+ requireContext().components.analytics.experiments.getActiveExperiments()
+
+ lifecycleScope.launch(Main) {
+ runIfFragmentIsAttached {
+ if (!shouldRefresh) {
+ adapter = NimbusExperimentAdapter(
+ experimentsView,
+ experiments
+ )
+ }
+
+ view.findViewById(R.id.nimbus_experiments_empty_message).isVisible =
+ false
+ recyclerView.adapter = adapter
+ }
+ }
+ } catch (e: Throwable) {
+ Logger.error("Failed to getActiveExperiments()", e)
+ view.findViewById(R.id.nimbus_experiments_empty_message).isVisible = true
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/nimbus/NimbusExperimentsView.kt b/app/src/main/java/org/mozilla/fenix/nimbus/NimbusExperimentsView.kt
new file mode 100644
index 000000000..1bc6e030f
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/nimbus/NimbusExperimentsView.kt
@@ -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.nimbus
+
+import androidx.navigation.NavController
+import mozilla.components.service.nimbus.ui.NimbusExperimentsAdapterDelegate
+import org.mozilla.experiments.nimbus.EnrolledExperiment
+import org.mozilla.fenix.ext.navigateBlockingForAsyncNavGraph
+
+/**
+ * View used for managing Nimbus experiments.
+ */
+class NimbusExperimentsView(
+ private val navController: NavController
+) : NimbusExperimentsAdapterDelegate {
+
+ override fun onExperimentItemClicked(experiment: EnrolledExperiment) {
+ val directions =
+ NimbusExperimentsFragmentDirections.actionNimbusExperimentsFragmentToNimbusDetailsFragment(
+ experiment.userFacingName
+ )
+
+ navController.navigateBlockingForAsyncNavGraph(directions)
+ }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/onboarding/OnboardingController.kt b/app/src/main/java/org/mozilla/fenix/onboarding/OnboardingController.kt
deleted file mode 100644
index 3e1945103..000000000
--- a/app/src/main/java/org/mozilla/fenix/onboarding/OnboardingController.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-/* 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.onboarding
-
-import android.content.Context
-import org.mozilla.fenix.BrowserDirection
-import org.mozilla.fenix.HomeActivity
-import org.mozilla.fenix.settings.SupportUtils
-
-class OnboardingController(
- private val context: Context
-) {
- fun handleLearnMoreClicked() {
- (context as HomeActivity).openToBrowserAndLoad(
- searchTermOrURL = SupportUtils.getFirefoxAccountSumoUrl(),
- newTab = true,
- from = BrowserDirection.FromHome
- )
- }
-}
diff --git a/app/src/main/java/org/mozilla/fenix/onboarding/OnboardingInteractor.kt b/app/src/main/java/org/mozilla/fenix/onboarding/OnboardingInteractor.kt
deleted file mode 100644
index ad11d459c..000000000
--- a/app/src/main/java/org/mozilla/fenix/onboarding/OnboardingInteractor.kt
+++ /dev/null
@@ -1,14 +0,0 @@
-/* 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.onboarding
-
-class OnboardingInteractor(private val onboardingController: OnboardingController) {
-
- /**
- * Called when the user clicks the learn more link
- * @param url the url the suggestion was providing
- */
- fun onLearnMoreClicked() = onboardingController.handleLearnMoreClicked()
-}
diff --git a/app/src/main/java/org/mozilla/fenix/perf/AppStartReasonProvider.kt b/app/src/main/java/org/mozilla/fenix/perf/AppStartReasonProvider.kt
new file mode 100644
index 000000000..1b54f1589
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/perf/AppStartReasonProvider.kt
@@ -0,0 +1,101 @@
+/* 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.perf
+
+import android.app.Activity
+import android.app.Application
+import android.os.Bundle
+import android.os.Handler
+import android.os.Looper
+import androidx.lifecycle.DefaultLifecycleObserver
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.ProcessLifecycleOwner
+import mozilla.components.support.base.log.logger.Logger
+import org.mozilla.fenix.GleanMetrics.Metrics
+import org.mozilla.fenix.android.DefaultActivityLifecycleCallbacks
+
+private val logger = Logger("AppStartReasonProvider")
+
+/**
+ * Provides the reason this [Application] instance was started: see [StartReason] for options
+ * and [reason] for details.
+ *
+ * [registerInAppOnCreate] must be called for this class to work correctly.
+ *
+ * This class relies on specific lifecycle method call orders and main thread Runnable scheduling
+ * that could potentially change between OEMs and OS versions: **be careful when using it.** This
+ * implementation was tested on the Moto G5 Android 8.1.0 and the Pixel 2 Android 11.
+ */
+class AppStartReasonProvider {
+
+ enum class StartReason {
+ /** We don't know yet what caused this [Application] instance to be started. */
+ TO_BE_DETERMINED,
+
+ /** This [Application] instance was started due to an Activity trying to start. */
+ ACTIVITY,
+
+ /**
+ * This [Application] instance was started due to a component that is not an Activity:
+ * this may include Services, BroadcastReceivers, and ContentProviders. It may be possible
+ * to distinguish between these but it hasn't been necessary to do so yet.
+ */
+ NON_ACTIVITY,
+ }
+
+ /**
+ * The reason this [Application] instance was started. This will not be set immediately
+ * but is expected to be available by the time the first frame is drawn for the foreground Activity.
+ */
+ var reason = StartReason.TO_BE_DETERMINED
+ private set
+
+ /**
+ * Registers the handlers needed by this class: this is expected to be called from
+ * [Application.onCreate].
+ */
+ fun registerInAppOnCreate(application: Application) {
+ ProcessLifecycleOwner.get().lifecycle.addObserver(ProcessLifecycleObserver())
+ application.registerActivityLifecycleCallbacks(ActivityLifecycleCallbacks())
+ }
+
+ private inner class ProcessLifecycleObserver : DefaultLifecycleObserver {
+ override fun onCreate(owner: LifecycleOwner) {
+ Handler(Looper.getMainLooper()).post {
+ // If the Application was started by an Activity, this Runnable should execute
+ // after we learn the Activity was created. If the App was started by a Service,
+ // this Runnable should execute before the first Activity is created.
+ reason = when (reason) {
+ StartReason.TO_BE_DETERMINED -> StartReason.NON_ACTIVITY
+ StartReason.ACTIVITY -> reason /* the start reason is already known: do nothing. */
+ StartReason.NON_ACTIVITY -> {
+ Metrics.startReasonProcessError.set(true)
+ logger.error("AppStartReasonProvider.Process...onCreate unexpectedly called twice")
+ reason
+ }
+ }
+ }
+
+ owner.lifecycle.removeObserver(this) // we don't update the state further.
+ }
+ }
+
+ private inner class ActivityLifecycleCallbacks : DefaultActivityLifecycleCallbacks {
+ override fun onActivityCreated(activity: Activity, bundle: Bundle?) {
+ // See ProcessLifecycleObserver.onCreate for details.
+ reason = when (reason) {
+ StartReason.TO_BE_DETERMINED -> StartReason.ACTIVITY
+ StartReason.NON_ACTIVITY -> reason /* the start reason is already known: do nothing. */
+ StartReason.ACTIVITY -> {
+ Metrics.startReasonActivityError.set(true)
+ logger.error("AppStartReasonProvider.Activity...onCreate unexpectedly called twice")
+ reason
+ }
+ }
+
+ activity.application.unregisterActivityLifecycleCallbacks(this) // we don't update the state further.
+ }
+ }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/perf/ColdStartupDurationTelemetry.kt b/app/src/main/java/org/mozilla/fenix/perf/ColdStartupDurationTelemetry.kt
new file mode 100644
index 000000000..28d757c26
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/perf/ColdStartupDurationTelemetry.kt
@@ -0,0 +1,72 @@
+/* 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.perf
+
+import android.content.Intent
+import android.os.SystemClock
+import android.view.View
+import androidx.core.view.doOnPreDraw
+import mozilla.components.support.base.log.logger.Logger
+import mozilla.components.support.utils.SafeIntent
+import org.mozilla.fenix.GleanMetrics.PerfStartup
+import org.mozilla.fenix.HomeActivity
+import java.util.concurrent.TimeUnit
+
+private val logger = Logger("ColdStartupDuration")
+
+/**
+ * A class to record COLD start up telemetry. This class is intended to improve upon our mistakes from the
+ * [org.mozilla.fenix.components.metrics.AppStartupTelemetry] class by being simple-to-implement and
+ * simple-to-analyze (i.e. works in GLAM) rather than being a "perfect" and comprehensive measurement.
+ *
+ * This class relies on external state providers like [StartupStateProvider] that are tricky to
+ * implement correctly so take the results with a grain of salt.
+ */
+class ColdStartupDurationTelemetry {
+
+ fun onHomeActivityOnCreate(
+ visualCompletenessQueue: VisualCompletenessQueue,
+ startupStateProvider: StartupStateProvider,
+ safeIntent: SafeIntent,
+ rootContainer: View
+ ) {
+ // Optimization: it might be expensive to post runnables so we can short-circuit
+ // with a subset of the later logic.
+ if (startupStateProvider.shouldShortCircuitColdStart()) {
+ logger.debug("Not measuring: is not cold start (short-circuit)")
+ return
+ }
+
+ rootContainer.doOnPreDraw {
+ // This block takes 0ms on a Moto G5: it doesn't seem long enough to optimize.
+ val firstFrameNanos = SystemClock.elapsedRealtimeNanos()
+ if (startupStateProvider.isColdStartForStartedActivity(HomeActivity::class.java)) {
+ visualCompletenessQueue.queue.runIfReadyOrQueue {
+ recordColdStartupTelemetry(safeIntent, firstFrameNanos)
+ }
+ }
+ }
+ }
+
+ private fun recordColdStartupTelemetry(safeIntent: SafeIntent, firstFrameNanos: Long) {
+ // This code duplicates the logic for determining how we should handle this intent which
+ // could result in inconsistent results: e.g. the browser might get a VIEW intent but it's
+ // malformed so the app treats it as a MAIN intent but here we record VIEW. However, the
+ // logic for determining the end state is distributed and buried & inspecting the end state
+ // is fragile (e.g. if the browser was open, was it a MAIN w/ session restore or VIEW?) so we
+ // use this simpler solution even if it's imperfect. Hopefully, the success cases will
+ // outnumber the edge cases into statistical insignificance.
+ val (metric, typeForLog) = when (safeIntent.action) {
+ Intent.ACTION_MAIN -> Pair(PerfStartup.coldMainAppToFirstFrame, "MAIN")
+ Intent.ACTION_VIEW -> Pair(PerfStartup.coldViewAppToFirstFrame, "VIEW")
+ else -> Pair(PerfStartup.coldUnknwnAppToFirstFrame, "UNKNOWN")
+ }
+
+ val startNanos = StartupTimeline.frameworkStartMeasurement.applicationInitNanos
+ val durationMillis = TimeUnit.NANOSECONDS.toMillis(firstFrameNanos - startNanos)
+ metric.accumulateSamples(longArrayOf(durationMillis))
+ logger.info("COLD $typeForLog Application. to first frame: $durationMillis ms")
+ }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/perf/NavGraphProvider.kt b/app/src/main/java/org/mozilla/fenix/perf/NavGraphProvider.kt
new file mode 100644
index 000000000..a6b60a5ca
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/perf/NavGraphProvider.kt
@@ -0,0 +1,59 @@
+/* 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.perf
+
+import androidx.lifecycle.LifecycleCoroutineScope
+import androidx.navigation.NavController
+import java.util.WeakHashMap
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+import org.mozilla.fenix.R
+
+/**
+ * This class asynchronously loads the navigation graph XML. This is a performance optimization:
+ * large nav graphs take ~29ms to inflate on the Moto G5 (#16900). This also seemingly prevents the
+ * HomeFragment layout XML from being unnecessarily inflated when it isn't used, improving perf by
+ * ~148ms on the Moto G5 for VIEW start up (#18245) though it was unintentional and we may wish to
+ * implement more intentional for that.
+ *
+ * This class is defined as an Object, rather than as a class instance in our Components, because
+ * it needs to be called by the [NavController] extension function which can't easily access Components.
+ *
+ * To use this class properly, [inflateNavGraphAsync] must be called first before
+ * [blockForNavGraphInflation] using the same [NavController] instance.
+ */
+object NavGraphProvider {
+
+ // We want to store member state on the NavController. However, there is no way to do this.
+ // Instead, we store state as part of a map: NavController instance -> State. In order to
+ // garbage collect our data when the NavController is no longer relevant, we use a WeakHashMap.
+ private val map = WeakHashMap()
+
+ fun inflateNavGraphAsync(navController: NavController, lifecycleScope: LifecycleCoroutineScope) {
+ val inflationJob = lifecycleScope.launch(Dispatchers.IO) {
+ val inflater = navController.navInflater
+ navController.graph = inflater.inflate(R.navigation.nav_graph)
+ }
+
+ map[navController] = inflationJob
+ }
+
+ /**
+ * The job should block the main thread if it isn't completed so that the NavGraph can be loaded
+ * before any navigation is done.
+ *
+ * [inflateNavGraphAsync] must be called before this method.
+ *
+ * @throws IllegalStateException if [inflateNavGraphAsync] wasn't called first for this [NavController]
+ */
+ fun blockForNavGraphInflation(navController: NavController) {
+ val inflationJob = map[navController] ?: throw IllegalStateException("Expected " +
+ "`NavGraphProvider.inflateNavGraphAsync` to be called before this method with the same " +
+ "`NavController` instance. If this occurred in a test, you probably need to add the " +
+ "DisableNavGraphProviderAssertionRule.")
+ runBlockingIncrement { inflationJob.join() }
+ }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/perf/StartupActivityLog.kt b/app/src/main/java/org/mozilla/fenix/perf/StartupActivityLog.kt
new file mode 100644
index 000000000..279a576de
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/perf/StartupActivityLog.kt
@@ -0,0 +1,105 @@
+/* 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.perf
+
+import android.app.Activity
+import android.app.Application
+import android.os.Bundle
+import androidx.annotation.VisibleForTesting
+import androidx.annotation.VisibleForTesting.NONE
+import androidx.annotation.VisibleForTesting.PRIVATE
+import androidx.lifecycle.DefaultLifecycleObserver
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.ProcessLifecycleOwner
+import mozilla.components.support.base.log.Log
+import mozilla.components.support.base.log.logger.Logger
+import org.mozilla.fenix.android.DefaultActivityLifecycleCallbacks
+
+private val logger = Logger("StartupActivityLog")
+
+/**
+ * A record of the [Activity] created, started, and stopped events as well as [Application]
+ * foreground and background events. See [log] for the log. This class is expected to be
+ * registered in [Application.onCreate] by calling [registerInAppOnCreate].
+ *
+ * To prevent this list from growing infinitely, we clear the list when the application is stopped.
+ * This is acceptable from the current requirements: we never need to inspect more than the current
+ * start up.
+ */
+class StartupActivityLog {
+
+ private val _log = mutableListOf()
+ val log: List = _log
+
+ fun registerInAppOnCreate(
+ application: Application,
+ processLifecycleOwner: LifecycleOwner = ProcessLifecycleOwner.get()
+ ) {
+ processLifecycleOwner.lifecycle.addObserver(StartupLogAppLifecycleObserver())
+ application.registerActivityLifecycleCallbacks(StartupLogActivityLifecycleCallbacks())
+ }
+
+ @VisibleForTesting(otherwise = NONE)
+ fun getObserversForTesting() = Pair(StartupLogAppLifecycleObserver(), StartupLogActivityLifecycleCallbacks())
+
+ @VisibleForTesting(otherwise = PRIVATE)
+ fun logEntries(loggerArg: Logger = logger, logLevel: Log.Priority = Log.logLevel) {
+ // Optimization: we want to avoid the potentially expensive conversions
+ // to Strings if we're not going to log anyway.
+ if (logLevel > Log.Priority.DEBUG) {
+ return
+ }
+
+ val transformedEntries = log.map { when (it) {
+ is LogEntry.AppStarted -> "App-STARTED"
+ is LogEntry.AppStopped -> "App-STOPPED"
+ is LogEntry.ActivityCreated -> "${it.activityClass.simpleName}-CREATED"
+ is LogEntry.ActivityStarted -> "${it.activityClass.simpleName}-STARTED"
+ is LogEntry.ActivityStopped -> "${it.activityClass.simpleName}-STOPPED"
+ } }
+
+ loggerArg.debug(transformedEntries.toString())
+ }
+
+ @VisibleForTesting(otherwise = PRIVATE)
+ inner class StartupLogAppLifecycleObserver : DefaultLifecycleObserver {
+ override fun onStart(owner: LifecycleOwner) {
+ _log.add(LogEntry.AppStarted)
+ }
+
+ override fun onStop(owner: LifecycleOwner) {
+ logEntries()
+ _log.clear() // Optimization: see class kdoc for details.
+ _log.add(LogEntry.AppStopped)
+ }
+ }
+
+ @VisibleForTesting(otherwise = PRIVATE)
+ inner class StartupLogActivityLifecycleCallbacks : DefaultActivityLifecycleCallbacks {
+ override fun onActivityCreated(activity: Activity, bundle: Bundle?) {
+ _log.add(LogEntry.ActivityCreated(activity::class.java))
+ }
+
+ override fun onActivityStarted(activity: Activity) {
+ _log.add(LogEntry.ActivityStarted(activity::class.java))
+ }
+
+ override fun onActivityStopped(activity: Activity) {
+ _log.add(LogEntry.ActivityStopped(activity::class.java))
+ }
+ }
+
+ /**
+ * A log entry with its detailed information for the [StartupActivityLog].
+ */
+ sealed class LogEntry {
+ object AppStarted : LogEntry()
+ object AppStopped : LogEntry()
+
+ data class ActivityCreated(val activityClass: Class) : LogEntry()
+ data class ActivityStarted(val activityClass: Class) : LogEntry()
+ data class ActivityStopped(val activityClass: Class) : LogEntry()
+ }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/perf/StartupFrameworkStartMeasurement.kt b/app/src/main/java/org/mozilla/fenix/perf/StartupFrameworkStartMeasurement.kt
index a63f0bb61..359ff44f6 100644
--- a/app/src/main/java/org/mozilla/fenix/perf/StartupFrameworkStartMeasurement.kt
+++ b/app/src/main/java/org/mozilla/fenix/perf/StartupFrameworkStartMeasurement.kt
@@ -26,7 +26,8 @@ internal class StartupFrameworkStartMeasurement(
private var isMetricSet = false
- private var applicationInitNanos = -1L
+ var applicationInitNanos = -1L
+ private set
private var isApplicationInitCalled = false
fun onApplicationInit() {
diff --git a/app/src/main/java/org/mozilla/fenix/perf/StartupPathProvider.kt b/app/src/main/java/org/mozilla/fenix/perf/StartupPathProvider.kt
new file mode 100644
index 000000000..f6b6f375f
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/perf/StartupPathProvider.kt
@@ -0,0 +1,107 @@
+/* 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.perf
+
+import android.app.Activity
+import android.content.Intent
+import androidx.annotation.VisibleForTesting
+import androidx.annotation.VisibleForTesting.NONE
+import androidx.annotation.VisibleForTesting.PRIVATE
+import androidx.lifecycle.DefaultLifecycleObserver
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+
+/**
+ * The "path" that this activity started in. See the
+ * [Fenix perf glossary](https://wiki.mozilla.org/index.php?title=Performance/Fenix/Glossary)
+ * for specific definitions.
+ *
+ * This should be a member variable of [Activity] because its data is tied to the lifecycle of an
+ * Activity. Call [attachOnActivityOnCreate] & [onIntentReceived] for this class to work correctly.
+ */
+class StartupPathProvider {
+
+ /**
+ * The path the application took to
+ * [Fenix perf glossary](https://wiki.mozilla.org/index.php?title=Performance/Fenix/Glossary)
+ * for specific definitions.
+ */
+ enum class StartupPath {
+ MAIN,
+ VIEW,
+
+ /**
+ * The start up path if we received an Intent but we're unable to categorize it into other buckets.
+ */
+ UNKNOWN,
+
+ /**
+ * The start up path has not been set. This state includes:
+ * - this API is accessed before it is set
+ * - if no intent is received before the activity is STARTED (e.g. app switcher)
+ */
+ NOT_SET
+ }
+
+ /**
+ * Returns the [StartupPath] for the currently started activity. This value will be set
+ * after an [Intent] is received that causes this activity to move into the STARTED state.
+ */
+ var startupPathForActivity = StartupPath.NOT_SET
+ private set
+
+ private var wasResumedSinceStartedState = false
+
+ fun attachOnActivityOnCreate(lifecycle: Lifecycle, intent: Intent?) {
+ lifecycle.addObserver(StartupPathLifecycleObserver())
+ onIntentReceived(intent)
+ }
+
+ // N.B.: this method duplicates the actual logic for determining what page to open.
+ // Unfortunately, it's difficult to re-use that logic because it occurs in many places throughout
+ // the code so we do the simple thing for now and duplicate it. It's noticeably different from
+ // what you might expect: e.g. ACTION_MAIN can open a URL and if ACTION_VIEW provides an invalid
+ // URL, it'll perform a MAIN action. However, it's fairly representative of what users *intended*
+ // to do when opening the app and shouldn't change much because it's based on Android system-wide
+ // conventions, so it's probably fine for our purposes.
+ private fun getStartupPathFromIntent(intent: Intent): StartupPath = when (intent.action) {
+ Intent.ACTION_MAIN -> StartupPath.MAIN
+ Intent.ACTION_VIEW -> StartupPath.VIEW
+ else -> StartupPath.UNKNOWN
+ }
+
+ /**
+ * Expected to be called when a new [Intent] is received by the [Activity]: i.e.
+ * [Activity.onCreate] and [Activity.onNewIntent].
+ */
+ fun onIntentReceived(intent: Intent?) {
+ // We want to set a path only if the intent causes the Activity to move into the STARTED state.
+ // This means we want to discard any intents that are received when the app is foregrounded.
+ // However, we can't use the Lifecycle.currentState to determine this because:
+ // - the app is briefly paused (state becomes STARTED) before receiving the Intent in
+ // the foreground so we can't say <= STARTED
+ // - onIntentReceived can be called from the CREATED or STARTED state so we can't say == CREATED
+ // So we're forced to track this state ourselves.
+ if (!wasResumedSinceStartedState && intent != null) {
+ startupPathForActivity = getStartupPathFromIntent(intent)
+ }
+ }
+
+ @VisibleForTesting(otherwise = NONE)
+ fun getTestCallbacks() = StartupPathLifecycleObserver()
+
+ @VisibleForTesting(otherwise = PRIVATE)
+ inner class StartupPathLifecycleObserver : DefaultLifecycleObserver {
+ override fun onResume(owner: LifecycleOwner) {
+ wasResumedSinceStartedState = true
+ }
+
+ override fun onStop(owner: LifecycleOwner) {
+ // Clear existing state.
+ startupPathForActivity = StartupPath.NOT_SET
+ wasResumedSinceStartedState = false
+ }
+ }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/perf/StartupStateProvider.kt b/app/src/main/java/org/mozilla/fenix/perf/StartupStateProvider.kt
new file mode 100644
index 000000000..e43223bfd
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/perf/StartupStateProvider.kt
@@ -0,0 +1,154 @@
+/* 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.perf
+
+import android.app.Activity
+import org.mozilla.fenix.perf.AppStartReasonProvider.StartReason
+import org.mozilla.fenix.perf.StartupActivityLog.LogEntry
+
+/**
+ * Identifies the "state" of start up where state can be COLD/WARM/HOT and possibly others. See
+ * the [Fenix perf glossary](https://wiki.mozilla.org/index.php?title=Performance/Fenix/Glossary)
+ * for specific definitions.
+ *
+ * This class is nuanced: **please read the kdoc carefully before using it.** Consider contacting
+ * the perf team with your use case.
+ *
+ * For this class, we use the terminology from the [StartupActivityLog] such as STARTED and STOPPED.
+ * However, we're assuming STARTED means foregrounded and STOPPED means backgrounded. If this
+ * assumption is false, the logic in this class may be incorrect.
+ */
+class StartupStateProvider(
+ private val startupLog: StartupActivityLog,
+ private val startReasonProvider: AppStartReasonProvider
+) {
+
+ /**
+ * The restoration state of the application upon this most recent start up. See the
+ * [Fenix perf glossary](https://wiki.mozilla.org/index.php?title=Performance/Fenix/Glossary)
+ * for specific definitions.
+ */
+ enum class StartupState {
+ COLD, WARM, HOT,
+
+ /**
+ * A start up state where we weren't able to bucket it into the other categories.
+ * This includes, but is not limited to:
+ * - if the activity this is called from is not currently started
+ * - if the currently started activity is not the first started activity
+ */
+ UNKNOWN;
+ }
+
+ /**
+ * Returns the [StartupState] for the currently started activity. Note: the state will be
+ * [StartupState.UNKNOWN] if the currently started activity is not the first started activity.
+ *
+ * This method must be called after the foreground Activity is STARTED.
+ */
+ fun getStartupStateForStartedActivity(activityClass: Class): StartupState = when {
+ isColdStartForStartedActivity(activityClass) -> StartupState.COLD
+ isWarmStartForStartedActivity(activityClass) -> StartupState.WARM
+ isHotStartForStartedActivity(activityClass) -> StartupState.HOT
+ else -> StartupState.UNKNOWN
+ }
+
+ /**
+ * Returns true if the current startup state is COLD and the currently started activity is the
+ * first started activity (i.e. we can use it for performance measurements).
+ *
+ * This method must be called after the foreground Activity is STARTED.
+ */
+ fun isColdStartForStartedActivity(activityClass: Class): Boolean {
+ // A cold start means:
+ // - the process was started for the first started activity (e.g. not a service)
+ // - the first started activity ever is still active
+ //
+ // Thus, for the activity log we expect:
+ // [... Activity-STARTED, App-STARTED]
+ // since if another Activity was started, it would appear after App-STARTED. This is where:
+ // - the app has not been stopped ever
+ if (startReasonProvider.reason != StartReason.ACTIVITY) {
+ return false
+ }
+
+ val isLastStartedActivityStillStarted = startupLog.log.takeLast(2) == listOf(
+ LogEntry.ActivityStarted(activityClass),
+ LogEntry.AppStarted
+ )
+ return !startupLog.log.contains(LogEntry.AppStopped) && isLastStartedActivityStillStarted
+ }
+
+ /**
+ * A short-circuit implementation of [isColdStartForStartedActivity] that will return false early
+ * so we don't have to call [isColdStartForStartedActivity].
+ *
+ * When this can be called might be tightly coupled to [ColdStartupDurationTelemetry]: use at
+ * your own risk.
+ */
+ fun shouldShortCircuitColdStart(): Boolean = startupLog.log.contains(LogEntry.AppStopped)
+
+ /**
+ * Returns true if the current startup state is WARM and the currently started activity is the
+ * first started activity for this start up (i.e. we can use it for performance measurements).
+ *
+ * This method must be called after the foreground activity is STARTED.
+ */
+ fun isWarmStartForStartedActivity(activityClass: Class): Boolean {
+ // A warm start means:
+ // - the app was backgrounded and has since been started
+ // - the first started activity since the app was started is still active.
+ // - that activity was created before being started
+ //
+ // For the activity log, we expect:
+ // [... App-STOPPED, ... Activity-CREATED, Activity-STARTED, App-STARTED]
+ // where:
+ // - App-STOPPED is the last STOPPED seen
+ // - we're assuming App-STARTED will only be last if one activity is started (as observed)
+ if (!startupLog.log.contains(LogEntry.AppStopped)) {
+ return false // if the app hasn't been stopped, it's not a warm start.
+ }
+ val afterLastStopped = startupLog.log.takeLastWhile { it != LogEntry.AppStopped }
+
+ @Suppress("MagicNumber") // we take a specific number at the end of the list to compare them.
+ val isLastActivityCreatedStillStarted = afterLastStopped.takeLast(3) == listOf(
+ LogEntry.ActivityCreated(activityClass),
+ LogEntry.ActivityStarted(activityClass),
+ LogEntry.AppStarted
+ )
+ return isLastActivityCreatedStillStarted
+ }
+
+ /**
+ * Returns true if the current startup state is HOT and the currently started activity is the
+ * first started activity for this start up (i.e. we can use it for performance measurements).
+ *
+ * This method must be called after the foreground activity is STARTED.
+ */
+ fun isHotStartForStartedActivity(activityClass: Class): Boolean {
+ // A hot start means:
+ // - the app was backgrounded and has since been started
+ // - the first started activity since the app was started is still active.
+ // - that activity was not created before being started
+ //
+ // For the activity log, we expect:
+ // [... App-STOPPED, ... Activity-STARTED, App-STARTED]
+ // where:
+ // - App-STOPPED is the last STOPPED seen
+ // - App-CREATED is NOT called for this activity
+ // - we're assuming App-STARTED will only be last if one activity is started (as observed)
+ if (!startupLog.log.contains(LogEntry.AppStopped)) {
+ return false // if the app hasn't been stopped, it's not a hot start.
+ }
+ val afterLastStopped = startupLog.log.takeLastWhile { it != LogEntry.AppStopped }
+
+ val isLastActivityStartedStillStarted = afterLastStopped.takeLast(2) == listOf(
+ LogEntry.ActivityStarted(activityClass),
+ LogEntry.AppStarted
+ )
+ return !afterLastStopped.contains(LogEntry.ActivityCreated(activityClass)) &&
+ isLastActivityStartedStillStarted
+ }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/perf/StartupTimeline.kt b/app/src/main/java/org/mozilla/fenix/perf/StartupTimeline.kt
index 2187d8e15..7fb511347 100644
--- a/app/src/main/java/org/mozilla/fenix/perf/StartupTimeline.kt
+++ b/app/src/main/java/org/mozilla/fenix/perf/StartupTimeline.kt
@@ -40,7 +40,7 @@ object StartupTimeline {
private var state: StartupState = StartupState.Cold(StartupDestination.UNKNOWN)
private val reportFullyDrawn by lazy { StartupReportFullyDrawn() }
- private val frameworkStartMeasurement by lazy { StartupFrameworkStartMeasurement() }
+ internal val frameworkStartMeasurement by lazy { StartupFrameworkStartMeasurement() }
internal val homeActivityLifecycleObserver by lazy {
StartupHomeActivityLifecycleObserver(frameworkStartMeasurement)
}
diff --git a/app/src/main/java/org/mozilla/fenix/perf/StartupTypeTelemetry.kt b/app/src/main/java/org/mozilla/fenix/perf/StartupTypeTelemetry.kt
new file mode 100644
index 000000000..07b07855a
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/perf/StartupTypeTelemetry.kt
@@ -0,0 +1,96 @@
+/* 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.perf
+
+import androidx.annotation.VisibleForTesting
+import androidx.annotation.VisibleForTesting.NONE
+import androidx.annotation.VisibleForTesting.PRIVATE
+import androidx.lifecycle.DefaultLifecycleObserver
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import mozilla.components.support.base.log.logger.Logger
+import org.mozilla.fenix.GleanMetrics.PerfStartup
+import org.mozilla.fenix.HomeActivity
+import org.mozilla.fenix.perf.StartupPathProvider.StartupPath
+import org.mozilla.fenix.perf.StartupStateProvider.StartupState
+
+private val activityClass = HomeActivity::class.java
+
+private val logger = Logger("StartupTypeTelemetry")
+
+/**
+ * Records telemetry for the number of start ups. See the
+ * [Fenix perf glossary](https://wiki.mozilla.org/index.php?title=Performance/Fenix/Glossary)
+ * for specific definitions.
+ *
+ * This should be a member variable of [HomeActivity] because its data is tied to the lifecycle of an
+ * Activity. Call [attachOnHomeActivityOnCreate] for this class to work correctly.
+ *
+ * N.B.: this class is lightly hardcoded to HomeActivity.
+ */
+class StartupTypeTelemetry(
+ private val startupStateProvider: StartupStateProvider,
+ private val startupPathProvider: StartupPathProvider
+) {
+
+ fun attachOnHomeActivityOnCreate(lifecycle: Lifecycle) {
+ lifecycle.addObserver(StartupTypeLifecycleObserver())
+ }
+
+ private fun getTelemetryLabel(startupState: StartupState, startupPath: StartupPath): String {
+ // We don't use the enum name directly to avoid unintentional changes when refactoring.
+ val stateLabel = when (startupState) {
+ StartupState.COLD -> "cold"
+ StartupState.WARM -> "warm"
+ StartupState.HOT -> "hot"
+ StartupState.UNKNOWN -> "unknown"
+ }
+
+ val pathLabel = when (startupPath) {
+ StartupPath.MAIN -> "main"
+ StartupPath.VIEW -> "view"
+
+ // To avoid combinatorial explosion in label names, we bucket NOT_SET into UNKNOWN.
+ StartupPath.NOT_SET,
+ StartupPath.UNKNOWN -> "unknown"
+ }
+
+ return "${stateLabel}_$pathLabel"
+ }
+
+ @VisibleForTesting(otherwise = NONE)
+ fun getTestCallbacks() = StartupTypeLifecycleObserver()
+
+ @VisibleForTesting(otherwise = PRIVATE)
+ fun record() {
+ val startupState = startupStateProvider.getStartupStateForStartedActivity(activityClass)
+ val startupPath = startupPathProvider.startupPathForActivity
+ val label = getTelemetryLabel(startupState, startupPath)
+
+ PerfStartup.startupType[label].add(1)
+ logger.info("Recorded start up: $label")
+ }
+
+ @VisibleForTesting(otherwise = PRIVATE)
+ inner class StartupTypeLifecycleObserver : DefaultLifecycleObserver {
+ private var shouldRecordStart = false
+
+ override fun onStart(owner: LifecycleOwner) {
+ shouldRecordStart = true
+ }
+
+ override fun onResume(owner: LifecycleOwner) {
+ // We must record in onResume because the StartupStateProvider can only be called for
+ // STARTED activities and we can't guarantee our onStart is called before its.
+ //
+ // We only record if start was called for this resume to avoid recording
+ // for onPause -> onResume states.
+ if (shouldRecordStart) {
+ record()
+ shouldRecordStart = false
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/perf/StrictModeManager.kt b/app/src/main/java/org/mozilla/fenix/perf/StrictModeManager.kt
index 845076d8c..0878a2651 100644
--- a/app/src/main/java/org/mozilla/fenix/perf/StrictModeManager.kt
+++ b/app/src/main/java/org/mozilla/fenix/perf/StrictModeManager.kt
@@ -95,21 +95,7 @@ class StrictModeManager(
* specific fragment.
*/
fun attachListenerToDisablePenaltyDeath(fragmentManager: FragmentManager) {
- fragmentManager.registerFragmentLifecycleCallbacks(object :
- FragmentManager.FragmentLifecycleCallbacks() {
- override fun onFragmentResumed(fm: FragmentManager, f: Fragment) {
- fm.unregisterFragmentLifecycleCallbacks(this)
-
- // If we don't post when using penaltyListener on P+, the violation listener is never
- // called. My best guess is that, unlike penaltyDeath, the violations are not
- // delivered instantaneously so posting gives time for the violation listeners to
- // run before they are removed here. This may be a race so we give the listeners a
- // little extra time to run too though this way we may accidentally trigger
- // violations for non-startup, which we haven't planned to do yet.
- Handler(mainLooper).postDelayed({
- enableStrictMode(setPenaltyDeath = false)
- }, DELAY_TO_REMOVE_STRICT_MODE_MILLIS)
- } }, false)
+ fragmentManager.registerFragmentLifecycleCallbacks(DisableStrictModeFragmentLifecycleCallbacks(), false)
}
/**
@@ -147,6 +133,26 @@ class StrictModeManager(
functionBlock()
}
}
+
+ // If we use anonymous classes/functions in this class, we get a class load error with a slight perf impact. #18731
+ inner class DisableStrictModeFragmentLifecycleCallbacks : FragmentManager.FragmentLifecycleCallbacks() {
+ override fun onFragmentResumed(fm: FragmentManager, f: Fragment) {
+ fm.unregisterFragmentLifecycleCallbacks(this)
+
+ // If we don't post when using penaltyListener on P+, the violation listener is never
+ // called. My best guess is that, unlike penaltyDeath, the violations are not
+ // delivered instantaneously so posting gives time for the violation listeners to
+ // run before they are removed here. This may be a race so we give the listeners a
+ // little extra time to run too though this way we may accidentally trigger
+ // violations for non-startup, which we haven't planned to do yet.
+ Handler(Looper.getMainLooper()).postDelayed(::disableStrictMode, DELAY_TO_REMOVE_STRICT_MODE_MILLIS)
+ }
+
+ // See comment on anonymous functions above.
+ private fun disableStrictMode() {
+ enableStrictMode(setPenaltyDeath = false)
+ }
+ }
}
/**
diff --git a/app/src/main/java/org/mozilla/fenix/push/FirebasePushService.kt b/app/src/main/java/org/mozilla/fenix/push/FirebasePushService.kt
index 7e03039f4..43c349491 100644
--- a/app/src/main/java/org/mozilla/fenix/push/FirebasePushService.kt
+++ b/app/src/main/java/org/mozilla/fenix/push/FirebasePushService.kt
@@ -5,64 +5,12 @@
package org.mozilla.fenix.push
import android.annotation.SuppressLint
-import com.google.firebase.messaging.RemoteMessage
-import com.google.firebase.messaging.FirebaseMessagingService
-import com.leanplum.LeanplumPushFirebaseMessagingService
-import com.leanplum.LeanplumPushService
-import mozilla.components.concept.push.PushService
-import mozilla.components.lib.push.firebase.AbstractFirebasePushService
import mozilla.components.feature.push.AutoPushFeature
-
-/**
- * A wrapper class that only exists to delegate to [FirebaseMessagingService] instances.
- *
- * Implementation notes:
- *
- * This was a doozy...
- *
- * With Firebase Cloud Messaging, we've been given some tight constraints in order to get this to
- * work:
- * - We want to have multiple FCM message receivers for AutoPush and LeanPlum (for now), however
- * there can only be one registered [FirebaseMessagingService] in the AndroidManifest.
- * - The [LeanplumPushFirebaseMessagingService] does not function as expected unless it's the
- * inherited service that receives the messages.
- * - The [AutoPushService] is not strongly tied to being the inherited service, but the
- * [AutoPushFeature] requires a reference to the push instance as a [PushService].
- *
- * We tried creating an empty [FirebaseMessagingService] that can hold a list of the services
- * for delegating, but the [LeanplumPushFirebaseMessagingService] tries to get a reference to the
- * Application Context, however,since the FCM service runs in a background process that gives a
- * nullptr. Within LeanPlum, this is something that is probably provided internally.
- *
- * We tried to pass in an instance of the [AbstractFirebasePushService] to [FirebasePushService]
- * through the constructor and delegate the implementation of a [PushService] to that, but alas,
- * the service requires you to have an empty default constructor in order for the OS to do the
- * initialization. For this reason, we created a singleton instance of the AutoPush instance since
- * that lets us easily delegate the implementation to that, as well as make invocations when FCM
- * receives new messages.
- */
-class FirebasePushService : LeanplumPushFirebaseMessagingService(),
- PushService by AutoPushService {
-
- override fun onCreate() {
- LeanplumPushService.setCustomizer(LeanplumNotificationCustomizer())
- super.onCreate()
- }
-
- override fun onNewToken(newToken: String) {
- AutoPushService.onNewToken(newToken)
- super.onNewToken(newToken)
- }
-
- override fun onMessageReceived(remoteMessage: RemoteMessage) {
- AutoPushService.onMessageReceived(remoteMessage)
- super.onMessageReceived(remoteMessage)
- }
-}
+import mozilla.components.lib.push.firebase.AbstractFirebasePushService
/**
* A singleton instance of the FirebasePushService needed for communicating between FCM and the
* [AutoPushFeature].
*/
@SuppressLint("MissingFirebaseInstanceTokenRefresh") // Implemented internally.
-object AutoPushService : AbstractFirebasePushService()
+class FirebasePushService : AbstractFirebasePushService()
diff --git a/app/src/main/java/org/mozilla/fenix/push/LeanplumNotificationCustomizer.kt b/app/src/main/java/org/mozilla/fenix/push/LeanplumNotificationCustomizer.kt
deleted file mode 100644
index 476595151..000000000
--- a/app/src/main/java/org/mozilla/fenix/push/LeanplumNotificationCustomizer.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-/* 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.push
-
-import android.app.Notification
-import android.os.Bundle
-import androidx.core.app.NotificationCompat
-import com.leanplum.LeanplumPushNotificationCustomizer
-import org.mozilla.fenix.R
-
-/**
- * Notification customizer for incoming Leanplum push messages.
- */
-class LeanplumNotificationCustomizer : LeanplumPushNotificationCustomizer {
- override fun customize(
- builder: NotificationCompat.Builder,
- notificationPayload: Bundle?
- ) {
- builder.setSmallIcon(R.drawable.ic_status_logo)
- }
-
- // Do not implement if unless we want to support 2 lines of text in the BigPicture style.
- // See: https://docs.leanplum.com/docs/customize-your-push-notifications-sample-android
- override fun customize(
- builder: Notification.Builder?,
- notificationPayload: Bundle?,
- notificationStyle: Notification.Style?
- ) = Unit // no-op
-}
diff --git a/app/src/main/java/org/mozilla/fenix/search/SearchDialogController.kt b/app/src/main/java/org/mozilla/fenix/search/SearchDialogController.kt
index 4a4fe9c4f..a3484f633 100644
--- a/app/src/main/java/org/mozilla/fenix/search/SearchDialogController.kt
+++ b/app/src/main/java/org/mozilla/fenix/search/SearchDialogController.kt
@@ -24,6 +24,7 @@ import org.mozilla.fenix.components.metrics.MetricController
import org.mozilla.fenix.components.metrics.MetricsUtils
import org.mozilla.fenix.crashes.CrashListActivity
import org.mozilla.fenix.ext.navigateSafe
+import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.settings.SupportUtils
import org.mozilla.fenix.utils.Settings
@@ -32,7 +33,7 @@ import org.mozilla.fenix.utils.Settings
*/
@Suppress("TooManyFunctions")
interface SearchController {
- fun handleUrlCommitted(url: String)
+ fun handleUrlCommitted(url: String, fromHomeScreen: Boolean = false)
fun handleEditingCancelled()
fun handleTextChanged(text: String)
fun handleUrlTapped(url: String)
@@ -60,7 +61,7 @@ class SearchDialogController(
private val clearToolbar: () -> Unit
) : SearchController {
- override fun handleUrlCommitted(url: String) {
+ override fun handleUrlCommitted(url: String, fromHomeScreen: Boolean) {
when (url) {
"about:crashes" -> {
// The list of past crashes can be accessed via "settings > about", but desktop and
@@ -73,16 +74,19 @@ class SearchDialogController(
SearchDialogFragmentDirections.actionGlobalAddonsManagementFragment()
navController.navigateSafe(R.id.searchDialogFragment, directions)
}
- "moz://a" -> openSearchOrUrl(SupportUtils.getMozillaPageUrl(SupportUtils.MozillaPage.MANIFESTO))
+ "moz://a" -> openSearchOrUrl(
+ SupportUtils.getMozillaPageUrl(SupportUtils.MozillaPage.MANIFESTO),
+ fromHomeScreen
+ )
else ->
if (url.isNotBlank()) {
- openSearchOrUrl(url)
+ openSearchOrUrl(url, fromHomeScreen)
}
}
dismissDialog()
}
- private fun openSearchOrUrl(url: String) {
+ private fun openSearchOrUrl(url: String, fromHomeScreen: Boolean) {
clearToolbarFocus()
val searchEngine = fragmentStore.state.searchEngineSource.searchEngine
@@ -91,7 +95,8 @@ class SearchDialogController(
searchTermOrURL = url,
newTab = fragmentStore.state.tabId == null,
from = BrowserDirection.FromSearchDialog,
- engine = searchEngine
+ engine = searchEngine,
+ requestDesktopMode = fromHomeScreen && activity.settings().openNextTabInDesktopMode
)
val event = if (url.isUrl() || searchEngine == null) {
diff --git a/app/src/main/java/org/mozilla/fenix/search/SearchDialogFragment.kt b/app/src/main/java/org/mozilla/fenix/search/SearchDialogFragment.kt
index 780724462..2f1296adf 100644
--- a/app/src/main/java/org/mozilla/fenix/search/SearchDialogFragment.kt
+++ b/app/src/main/java/org/mozilla/fenix/search/SearchDialogFragment.kt
@@ -168,20 +168,22 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler {
)
)
+ val fromHomeFragment =
+ findNavController().previousBackStackEntry?.destination?.id == R.id.homeFragment
+
toolbarView = ToolbarView(
requireContext(),
interactor,
historyStorageProvider(),
isPrivate,
view.toolbar,
- requireComponents.core.engine
+ requireComponents.core.engine,
+ fromHomeFragment
)
val awesomeBar = view.awesome_bar
awesomeBar.customizeForBottomToolbar = requireContext().settings().shouldUseBottomToolbar
- val fromHomeFragment =
- findNavController().previousBackStackEntry?.destination?.id == R.id.homeFragment
awesomeBarView = AwesomeBarView(
activity,
interactor,
@@ -270,6 +272,7 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler {
}
fill_link_from_clipboard.setOnClickListener {
+ requireComponents.analytics.metrics.track(Event.ClipboardSuggestionClicked)
view.hideKeyboard()
toolbarView.view.clearFocus()
(activity as HomeActivity)
@@ -431,12 +434,11 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler {
dialog.cancel()
}
setPositiveButton(R.string.qr_scanner_dialog_positive) { dialog: DialogInterface, _ ->
- (activity as HomeActivity)
- .openToBrowserAndLoad(
- searchTermOrURL = result,
- newTab = store.state.tabId == null,
- from = BrowserDirection.FromSearchDialog
- )
+ (activity as? HomeActivity)?.openToBrowserAndLoad(
+ searchTermOrURL = result,
+ newTab = store.state.tabId == null,
+ from = BrowserDirection.FromSearchDialog
+ )
dialog.dismiss()
}
create()
diff --git a/app/src/main/java/org/mozilla/fenix/search/SearchDialogInteractor.kt b/app/src/main/java/org/mozilla/fenix/search/SearchDialogInteractor.kt
index 93d1068ba..23ab51129 100644
--- a/app/src/main/java/org/mozilla/fenix/search/SearchDialogInteractor.kt
+++ b/app/src/main/java/org/mozilla/fenix/search/SearchDialogInteractor.kt
@@ -17,8 +17,8 @@ class SearchDialogInteractor(
private val searchController: SearchDialogController
) : AwesomeBarInteractor, ToolbarInteractor {
- override fun onUrlCommitted(url: String) {
- searchController.handleUrlCommitted(url)
+ override fun onUrlCommitted(url: String, fromHomeScreen: Boolean) {
+ searchController.handleUrlCommitted(url, fromHomeScreen)
}
override fun onEditingCanceled() {
diff --git a/app/src/main/java/org/mozilla/fenix/search/awesomebar/AwesomeBarView.kt b/app/src/main/java/org/mozilla/fenix/search/awesomebar/AwesomeBarView.kt
index da4cc0612..dee2033e5 100644
--- a/app/src/main/java/org/mozilla/fenix/search/awesomebar/AwesomeBarView.kt
+++ b/app/src/main/java/org/mozilla/fenix/search/awesomebar/AwesomeBarView.kt
@@ -159,7 +159,11 @@ class AwesomeBarView(
icon = searchBitmap,
showDescription = false,
engine = engineForSpeculativeConnects,
- filterExactMatch = true
+ filterExactMatch = true,
+ private = when (activity.browsingModeManager.mode) {
+ BrowsingMode.Normal -> false
+ BrowsingMode.Private -> true
+ }
)
defaultSearchActionProvider =
@@ -343,7 +347,11 @@ class AwesomeBarView(
mode = SearchSuggestionProvider.Mode.MULTIPLE_SUGGESTIONS,
icon = searchBitmap,
engine = engineForSpeculativeConnects,
- filterExactMatch = true
+ filterExactMatch = true,
+ private = when (activity.browsingModeManager.mode) {
+ BrowsingMode.Normal -> false
+ BrowsingMode.Private -> true
+ }
)
)
}
diff --git a/app/src/main/java/org/mozilla/fenix/search/toolbar/ToolbarView.kt b/app/src/main/java/org/mozilla/fenix/search/toolbar/ToolbarView.kt
index dea282f36..1462be820 100644
--- a/app/src/main/java/org/mozilla/fenix/search/toolbar/ToolbarView.kt
+++ b/app/src/main/java/org/mozilla/fenix/search/toolbar/ToolbarView.kt
@@ -31,8 +31,9 @@ interface ToolbarInteractor {
/**
* Called when a user hits the return key while [ToolbarView] has focus.
* @param url the text inside the [ToolbarView] when committed
+ * @param fromHomeScreen true if the toolbar has been opened from home screen
*/
- fun onUrlCommitted(url: String)
+ fun onUrlCommitted(url: String, fromHomeScreen: Boolean = false)
/**
* Called when a user removes focus from the [ToolbarView]
@@ -49,13 +50,15 @@ interface ToolbarInteractor {
/**
* View that contains and configures the BrowserToolbar to only be used in its editing mode.
*/
+@Suppress("LongParameterList")
class ToolbarView(
private val context: Context,
private val interactor: ToolbarInteractor,
private val historyStorage: HistoryStorage?,
private val isPrivate: Boolean,
val view: BrowserToolbar,
- engine: Engine
+ engine: Engine,
+ fromHomeFragment: Boolean
) {
@VisibleForTesting
@@ -70,7 +73,7 @@ class ToolbarView(
// from resizing in case the BrowserFragment is being displayed before the
// keyboard is gone: https://github.com/mozilla-mobile/fenix/issues/8399
hideKeyboard()
- interactor.onUrlCommitted(it)
+ interactor.onUrlCommitted(it, fromHomeFragment)
false
}
diff --git a/app/src/main/java/org/mozilla/fenix/selection/SelectionHolder.kt b/app/src/main/java/org/mozilla/fenix/selection/SelectionHolder.kt
new file mode 100644
index 000000000..fe2b40d73
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/selection/SelectionHolder.kt
@@ -0,0 +1,12 @@
+/* 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.selection
+
+/**
+ * Contains the selection of items added or removed using the [SelectionInteractor].
+ */
+interface SelectionHolder {
+ val selectedItems: Set
+}
diff --git a/app/src/main/java/org/mozilla/fenix/selection/SelectionInteractor.kt b/app/src/main/java/org/mozilla/fenix/selection/SelectionInteractor.kt
new file mode 100644
index 000000000..320bf1e04
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/selection/SelectionInteractor.kt
@@ -0,0 +1,29 @@
+/* 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.selection
+
+/**
+ * Interactor for items that can be selected on the bookmarks and history screens.
+ */
+interface SelectionInteractor {
+ /**
+ * Called when an item is tapped to open it.
+ * @param item the tapped item to open.
+ */
+ fun open(item: T)
+
+ /**
+ * Called when an item is long pressed and selection mode is started,
+ * or when selection mode has already started an an item is tapped.
+ * @param item the item to select.
+ */
+ fun select(item: T)
+
+ /**
+ * Called when a selected item is tapped in selection mode and should no longer be selected.
+ * @param item the item to deselect.
+ */
+ fun deselect(item: T)
+}
diff --git a/app/src/main/java/org/mozilla/fenix/settings/DataChoicesFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/DataChoicesFragment.kt
index ca0229a2a..eb0c11332 100644
--- a/app/src/main/java/org/mozilla/fenix/settings/DataChoicesFragment.kt
+++ b/app/src/main/java/org/mozilla/fenix/settings/DataChoicesFragment.kt
@@ -67,13 +67,6 @@ class DataChoicesFragment : PreferenceFragmentCompat() {
requirePreference(R.string.pref_key_marketing_telemetry).apply {
isChecked = context.settings().isMarketingTelemetryEnabled
-
- val appName = context.getString(R.string.app_name)
- summary = String.format(
- context.getString(R.string.preferences_marketing_data_description),
- appName
- )
-
onPreferenceChangeListener = SharedPreferenceUpdater()
}
diff --git a/app/src/main/java/org/mozilla/fenix/settings/PairFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/PairFragment.kt
index cc398781d..e07083985 100644
--- a/app/src/main/java/org/mozilla/fenix/settings/PairFragment.kt
+++ b/app/src/main/java/org/mozilla/fenix/settings/PairFragment.kt
@@ -13,13 +13,13 @@ import android.view.View
import androidx.core.content.ContextCompat
import androidx.core.content.getSystemService
import androidx.fragment.app.Fragment
-import androidx.navigation.fragment.NavHostFragment.findNavController
import androidx.navigation.fragment.findNavController
import mozilla.components.feature.qr.QrFeature
import mozilla.components.support.base.feature.UserInteractionHandler
import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.requireComponents
+import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.ext.showToolbar
class PairFragment : Fragment(R.layout.fragment_pair), UserInteractionHandler {
@@ -67,7 +67,11 @@ class PairFragment : Fragment(R.layout.fragment_pair), UserInteractionHandler {
false
)
},
- scanMessage = R.string.pair_instructions_2
+ scanMessage =
+ if (requireContext().settings().allowDomesticChinaFxaServer &&
+ org.mozilla.fenix.Config.channel.isMozillaOnline)
+ R.string.pair_instructions_2_cn
+ else R.string.pair_instructions_2
),
owner = this,
view = view
diff --git a/app/src/main/java/org/mozilla/fenix/settings/PhoneFeature.kt b/app/src/main/java/org/mozilla/fenix/settings/PhoneFeature.kt
index e46b6ae52..dc1579f74 100644
--- a/app/src/main/java/org/mozilla/fenix/settings/PhoneFeature.kt
+++ b/app/src/main/java/org/mozilla/fenix/settings/PhoneFeature.kt
@@ -48,7 +48,7 @@ enum class PhoneFeature(val androidPermissionsList: Array) : Parcelable
@StringRes val stringRes = if (isAndroidPermissionGranted(context)) {
when (this) {
AUTOPLAY_AUDIBLE ->
- when (settings?.getAutoplayUserSetting(default = AUTOPLAY_BLOCK_ALL) ?: AUTOPLAY_BLOCK_ALL) {
+ when (settings?.getAutoplayUserSetting() ?: AUTOPLAY_BLOCK_ALL) {
AUTOPLAY_ALLOW_ALL -> R.string.preference_option_autoplay_allowed2
AUTOPLAY_ALLOW_ON_WIFI -> R.string.preference_option_autoplay_allowed_wifi_only2
AUTOPLAY_BLOCK_AUDIBLE -> R.string.preference_option_autoplay_block_audio2
diff --git a/app/src/main/java/org/mozilla/fenix/settings/SecretDebugSettingsFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/SecretDebugSettingsFragment.kt
index bedc939e6..9ac930728 100644
--- a/app/src/main/java/org/mozilla/fenix/settings/SecretDebugSettingsFragment.kt
+++ b/app/src/main/java/org/mozilla/fenix/settings/SecretDebugSettingsFragment.kt
@@ -7,7 +7,6 @@ package org.mozilla.fenix.settings
import android.os.Bundle
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
-import com.leanplum.Leanplum
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.showToolbar
@@ -24,26 +23,6 @@ class SecretDebugSettingsFragment : PreferenceFragmentCompat() {
val store = requireComponents.core.store
- requirePreference(R.string.pref_key_leanplum_user_id).apply {
- summary = Leanplum.getUserId().let {
- if (it.isNullOrEmpty()) {
- "No User Id"
- } else {
- it
- }
- }
- }
-
- requirePreference(R.string.pref_key_leanplum_device_id).apply {
- summary = Leanplum.getDeviceId().let {
- if (it.isNullOrEmpty()) {
- "No Device Id"
- } else {
- it
- }
- }
- }
-
requirePreference(R.string.pref_key_search_region_home).apply {
summary = store.state.search.region?.home ?: "Unknown"
}
diff --git a/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt
index 161e45295..518bc3f4f 100644
--- a/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt
+++ b/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt
@@ -6,7 +6,6 @@ package org.mozilla.fenix.settings
import android.annotation.SuppressLint
import android.app.Activity
-import android.app.role.RoleManager
import android.content.ActivityNotFoundException
import android.content.DialogInterface
import android.content.Intent
@@ -25,6 +24,7 @@ import androidx.navigation.findNavController
import androidx.navigation.fragment.navArgs
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
+import androidx.preference.SwitchPreference
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.amo_collection_override_dialog.view.*
import kotlinx.coroutines.CoroutineScope
@@ -34,23 +34,28 @@ import mozilla.components.concept.sync.AccountObserver
import mozilla.components.concept.sync.AuthType
import mozilla.components.concept.sync.OAuthAccount
import mozilla.components.concept.sync.Profile
-import mozilla.components.support.ktx.android.content.getColorFromAttr
import mozilla.components.support.ktx.android.view.showKeyboard
import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.Config
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
-import org.mozilla.fenix.FeatureFlags
import org.mozilla.fenix.components.metrics.Event
+import org.mozilla.fenix.experiments.ExperimentBranch
+import org.mozilla.fenix.experiments.Experiments
import org.mozilla.fenix.ext.application
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.getPreferenceKey
+import org.mozilla.fenix.ext.navigateBlockingForAsyncNavGraph
import org.mozilla.fenix.ext.metrics
import org.mozilla.fenix.ext.navigateToNotificationsSettings
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.settings
+import org.mozilla.fenix.ext.REQUEST_CODE_BROWSER_ROLE
+import org.mozilla.fenix.ext.openSetDefaultBrowserOption
import org.mozilla.fenix.ext.showToolbar
+import org.mozilla.fenix.ext.withExperiment
import org.mozilla.fenix.settings.account.AccountUiView
+import org.mozilla.fenix.utils.BrowsersCache
import org.mozilla.fenix.utils.Settings
import kotlin.system.exitProcess
@@ -91,7 +96,8 @@ class SettingsFragment : PreferenceFragmentCompat() {
scope = lifecycleScope,
accountManager = requireComponents.backgroundServices.accountManager,
httpClient = requireComponents.core.client,
- updateFxASyncOverrideMenu = ::updateFxASyncOverrideMenu
+ updateFxASyncOverrideMenu = ::updateFxASyncOverrideMenu,
+ updateFxAAllowDomesticChinaServerMenu = :: updateFxAAllowDomesticChinaServerMenu
)
// Observe account changes to keep the UI up-to-date.
@@ -133,15 +139,23 @@ class SettingsFragment : PreferenceFragmentCompat() {
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
- val preferencesId = if (FeatureFlags.newIconSet) {
- R.xml.preferences_without_icons
- } else {
- R.xml.preferences
- }
+ val preferencesId = getPreferenceLayoutId()
+
setPreferencesFromResource(preferencesId, rootKey)
updateMakeDefaultBrowserPreference()
}
+ /**
+ * @return The preference layout to be used depending on flags and existing experiment branches.
+ * Note: Changing Settings screen before experiment is over requires changing all layouts.
+ */
+ private fun getPreferenceLayoutId() =
+ if (isDefaultBrowserExperimentBranch() && !isFirefoxDefaultBrowser()) {
+ R.xml.preferences_default_browser_experiment
+ } else {
+ R.xml.preferences
+ }
+
@SuppressLint("RestrictedApi")
override fun onResume() {
super.onResume()
@@ -276,6 +290,9 @@ class SettingsFragment : PreferenceFragmentCompat() {
resources.getString(R.string.pref_key_passwords) -> {
SettingsFragmentDirections.actionSettingsFragmentToSavedLoginsAuthFragment()
}
+ resources.getString(R.string.pref_key_credit_cards) -> {
+ SettingsFragmentDirections.actionSettingsFragmentToCreditCardsSettingFragment()
+ }
resources.getString(R.string.pref_key_about) -> {
SettingsFragmentDirections.actionSettingsFragmentToAboutFragment()
}
@@ -321,6 +338,9 @@ class SettingsFragment : PreferenceFragmentCompat() {
resources.getString(R.string.pref_key_secret_debug_info) -> {
SettingsFragmentDirections.actionSettingsFragmentToSecretInfoSettingsFragment()
}
+ resources.getString(R.string.pref_key_nimbus_experiments) -> {
+ SettingsFragmentDirections.actionSettingsFragmentToNimbusExperimentsFragment()
+ }
resources.getString(R.string.pref_key_override_amo_collection) -> {
val context = requireContext()
val dialogView = LayoutInflater.from(context).inflate(R.layout.amo_collection_override_dialog, null)
@@ -365,8 +385,6 @@ class SettingsFragment : PreferenceFragmentCompat() {
private fun setupPreferences() {
val leakKey = getPreferenceKey(R.string.pref_key_leakcanary)
val debuggingKey = getPreferenceKey(R.string.pref_key_remote_debugging)
- val preferencePrivateBrowsing =
- requirePreference(R.string.pref_key_private_browsing)
val preferenceLeakCanary = findPreference(leakKey)
val preferenceRemoteDebugging = findPreference(debuggingKey)
val preferenceMakeDefaultBrowser =
@@ -374,12 +392,6 @@ class SettingsFragment : PreferenceFragmentCompat() {
val preferenceOpenLinksInExternalApp =
findPreference(getPreferenceKey(R.string.pref_key_open_links_in_external_app))
- if (!FeatureFlags.newIconSet) {
- preferencePrivateBrowsing.icon.mutate().apply {
- setTint(requireContext().getColorFromAttr(R.attr.primaryText))
- }
- }
-
if (!Config.channel.isReleased) {
preferenceLeakCanary?.setOnPreferenceChangeListener { _, newValue ->
val isEnabled = newValue == true
@@ -423,14 +435,24 @@ class SettingsFragment : PreferenceFragmentCompat() {
}
preferenceFxAOverride?.onPreferenceChangeListener = syncFxAOverrideUpdater
preferenceSyncOverride?.onPreferenceChangeListener = syncFxAOverrideUpdater
- findPreference(
- getPreferenceKey(R.string.pref_key_debug_settings)
- )?.isVisible = requireContext().settings().showSecretDebugMenuThisSession
- findPreference(
- getPreferenceKey(R.string.pref_key_secret_debug_info)
- )?.isVisible = requireContext().settings().showSecretDebugMenuThisSession
+
+ with(requireContext().settings()) {
+ findPreference(
+ getPreferenceKey(R.string.pref_key_credit_cards)
+ )?.isVisible = creditCardsFeature
+ findPreference(
+ getPreferenceKey(R.string.pref_key_nimbus_experiments)
+ )?.isVisible = showSecretDebugMenuThisSession
+ findPreference(
+ getPreferenceKey(R.string.pref_key_debug_settings)
+ )?.isVisible = showSecretDebugMenuThisSession
+ findPreference(
+ getPreferenceKey(R.string.pref_key_secret_debug_info)
+ )?.isVisible = showSecretDebugMenuThisSession
+ }
setupAmoCollectionOverridePreference(requireContext().settings())
+ setupAllowDomesticChinaFxaServerPreference()
}
/**
@@ -439,44 +461,12 @@ class SettingsFragment : PreferenceFragmentCompat() {
* For Open sumo page to show user how to change default app.
*/
private fun getClickListenerForMakeDefaultBrowser(): Preference.OnPreferenceClickListener {
- return when {
- Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q -> {
- Preference.OnPreferenceClickListener {
- requireContext().getSystemService(RoleManager::class.java).also {
- if (it.isRoleAvailable(RoleManager.ROLE_BROWSER) && !it.isRoleHeld(
- RoleManager.ROLE_BROWSER
- )
- ) {
- startActivityForResult(
- it.createRequestRoleIntent(RoleManager.ROLE_BROWSER),
- REQUEST_CODE_BROWSER_ROLE
- )
- } else {
- navigateUserToDefaultAppsSettings()
- }
- }
- true
- }
- }
- Build.VERSION.SDK_INT >= Build.VERSION_CODES.N -> {
- Preference.OnPreferenceClickListener {
- navigateUserToDefaultAppsSettings()
- true
- }
- }
- else -> {
- Preference.OnPreferenceClickListener {
- (activity as HomeActivity).openToBrowserAndLoad(
- searchTermOrURL = SupportUtils.getSumoURLForTopic(
- requireContext(),
- SupportUtils.SumoTopic.SET_AS_DEFAULT_BROWSER
- ),
- newTab = true,
- from = BrowserDirection.FromSettings
- )
- true
- }
+ return Preference.OnPreferenceClickListener {
+ if (isDefaultBrowserExperimentBranch() && !isFirefoxDefaultBrowser()) {
+ requireContext().metrics.track(Event.SetDefaultBrowserSettingsScreenClicked)
}
+ activity?.openSetDefaultBrowserOption()
+ true
}
}
@@ -489,21 +479,16 @@ class SettingsFragment : PreferenceFragmentCompat() {
}
}
- private fun navigateUserToDefaultAppsSettings() {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
- val intent = Intent(android.provider.Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS)
- startActivity(intent)
- }
- }
-
private fun updateMakeDefaultBrowserPreference() {
- requirePreference(R.string.pref_key_make_default_browser).updateSwitch()
+ if (!isDefaultBrowserExperimentBranch()) {
+ requirePreference(R.string.pref_key_make_default_browser).updateSwitch()
+ }
}
private fun navigateFromSettings(directions: NavDirections) {
view?.findNavController()?.let { navController ->
if (navController.currentDestination?.id == R.id.settingsFragment) {
- navController.navigate(directions)
+ navController.navigateBlockingForAsyncNavGraph(directions)
}
}
}
@@ -520,6 +505,22 @@ class SettingsFragment : PreferenceFragmentCompat() {
}
}
+ private fun updateFxAAllowDomesticChinaServerMenu() {
+ val settings = requireContext().settings()
+ val preferenceAllowDomesticChinaServer =
+ findPreference(getPreferenceKey(R.string.pref_key_allow_domestic_china_fxa_server))
+ // Only enable changes to these prefs when the user isn't connected to an account.
+ val enabled =
+ requireComponents.backgroundServices.accountManager.authenticatedAccount() == null
+ val checked = settings.allowDomesticChinaFxaServer
+ val visible = Config.channel.isMozillaOnline
+ preferenceAllowDomesticChinaServer?.apply {
+ isEnabled = enabled
+ isChecked = checked
+ isVisible = visible
+ }
+ }
+
private fun updateFxASyncOverrideMenu() {
val preferenceFxAOverride =
findPreference(getPreferenceKey(R.string.pref_key_override_fxa_server))
@@ -558,8 +559,46 @@ class SettingsFragment : PreferenceFragmentCompat() {
}
}
+ private fun setupAllowDomesticChinaFxaServerPreference() {
+ val allowDomesticChinaFxAServer = getPreferenceKey(R.string.pref_key_allow_domestic_china_fxa_server)
+ val preferenceAllowDomesticChinaFxAServer = findPreference(allowDomesticChinaFxAServer)
+ val visible = Config.channel.isMozillaOnline
+
+ preferenceAllowDomesticChinaFxAServer?.apply {
+ isVisible = visible
+ }
+
+ if (visible) {
+ preferenceAllowDomesticChinaFxAServer?.onPreferenceChangeListener =
+ Preference.OnPreferenceChangeListener { preference, newValue ->
+ preference.context.settings().preferences.edit()
+ .putBoolean(preference.key, newValue as Boolean).apply()
+ updateFxAAllowDomesticChinaServerMenu()
+ Toast.makeText(
+ context,
+ getString(R.string.toast_override_fxa_sync_server_done),
+ Toast.LENGTH_LONG
+ ).show()
+ Handler(Looper.getMainLooper()).postDelayed({
+ exitProcess(0)
+ }, FXA_SYNC_OVERRIDE_EXIT_DELAY)
+ }
+ }
+ }
+
+ private fun isDefaultBrowserExperimentBranch(): Boolean {
+ val experiments = context?.components?.analytics?.experiments
+ return experiments?.withExperiment(Experiments.DEFAULT_BROWSER) { experimentBranch ->
+ (experimentBranch == ExperimentBranch.DEFAULT_BROWSER_SETTINGS_MENU)
+ } == true
+ }
+
+ private fun isFirefoxDefaultBrowser(): Boolean {
+ val browsers = BrowsersCache.all(requireContext())
+ return browsers.isFirefoxDefaultBrowser
+ }
+
companion object {
- private const val REQUEST_CODE_BROWSER_ROLE = 1
private const val SCROLL_INDICATOR_DELAY = 10L
private const val FXA_SYNC_OVERRIDE_EXIT_DELAY = 2000L
private const val AMO_COLLECTION_OVERRIDE_EXIT_DELAY = 3000L
diff --git a/app/src/main/java/org/mozilla/fenix/settings/SupportUtils.kt b/app/src/main/java/org/mozilla/fenix/settings/SupportUtils.kt
index 0c356cc84..bcded9406 100644
--- a/app/src/main/java/org/mozilla/fenix/settings/SupportUtils.kt
+++ b/app/src/main/java/org/mozilla/fenix/settings/SupportUtils.kt
@@ -33,6 +33,8 @@ object SupportUtils {
"?e=&p=AyIGZRprFDJWWA1FBCVbV0IUWVALHFRBEwQAQB1AWQkFVUVXfFkAF14lRFRbJXstVWR3WQ1rJ08AZnhS" +
"HDJBYh4LZR9eEAMUBlccWCUBEQZRGFoXCxc3ZRteJUl8BmUZWhQ" +
"AEwdRGF0cMhIAVB5ZFAETBVAaXRwyFQdcKydLSUpaCEtYFAIXN2UrWCUyIgdVK1slXVZaCCtZFAMWDg%3D%3D"
+ const val PDD_URL = "https://mobile.yangkeduo.com/duo_cms_mall.html?pid=13289095_194240604&" +
+ "cpsSign=CM_210309_13289095_194240604_8bcfd56d5db3c43d983014d2658ec26e&duoduo_type=2"
const val GOOGLE_US_URL = "https://www.google.com/webhp?client=firefox-b-1-m&channel=ts"
const val GOOGLE_XX_URL = "https://www.google.com/webhp?client=firefox-b-m&channel=ts"
diff --git a/app/src/main/java/org/mozilla/fenix/settings/TrackingProtectionFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/TrackingProtectionFragment.kt
index 66bb347cc..92be8a332 100644
--- a/app/src/main/java/org/mozilla/fenix/settings/TrackingProtectionFragment.kt
+++ b/app/src/main/java/org/mozilla/fenix/settings/TrackingProtectionFragment.kt
@@ -16,6 +16,7 @@ import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.ext.components
+import org.mozilla.fenix.ext.navigateBlockingForAsyncNavGraph
import org.mozilla.fenix.ext.nav
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.settings
@@ -32,7 +33,7 @@ class TrackingProtectionFragment : PreferenceFragmentCompat() {
private val exceptionsClickListener = Preference.OnPreferenceClickListener {
val directions =
TrackingProtectionFragmentDirections.actionTrackingProtectionFragmentToExceptionsFragment()
- requireView().findNavController().navigate(directions)
+ requireView().findNavController().navigateBlockingForAsyncNavGraph(directions)
true
}
private lateinit var customCookies: CheckBoxPreference
diff --git a/app/src/main/java/org/mozilla/fenix/settings/about/AboutFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/about/AboutFragment.kt
index 9f80cf5e6..ba1d7a886 100644
--- a/app/src/main/java/org/mozilla/fenix/settings/about/AboutFragment.kt
+++ b/app/src/main/java/org/mozilla/fenix/settings/about/AboutFragment.kt
@@ -22,6 +22,7 @@ import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.crashes.CrashListActivity
+import org.mozilla.fenix.ext.navigateBlockingForAsyncNavGraph
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.settings.SupportUtils
@@ -178,7 +179,7 @@ class AboutFragment : Fragment(), AboutPageListener {
private fun openLibrariesPage() {
val navController = findNavController()
- navController.navigate(R.id.action_aboutFragment_to_aboutLibrariesFragment)
+ navController.navigateBlockingForAsyncNavGraph(R.id.action_aboutFragment_to_aboutLibrariesFragment)
}
override fun onAboutItemClicked(item: AboutItem) {
diff --git a/app/src/main/java/org/mozilla/fenix/settings/account/AccountUiView.kt b/app/src/main/java/org/mozilla/fenix/settings/account/AccountUiView.kt
index 965ff46cf..94464b035 100644
--- a/app/src/main/java/org/mozilla/fenix/settings/account/AccountUiView.kt
+++ b/app/src/main/java/org/mozilla/fenix/settings/account/AccountUiView.kt
@@ -26,7 +26,8 @@ class AccountUiView(
private val scope: CoroutineScope,
private val accountManager: FxaAccountManager,
private val httpClient: Client,
- private val updateFxASyncOverrideMenu: () -> Unit
+ private val updateFxASyncOverrideMenu: () -> Unit,
+ private val updateFxAAllowDomesticChinaServerMenu: () -> Unit
) {
private val preferenceSignIn =
@@ -48,6 +49,7 @@ class AccountUiView(
val account = accountManager.authenticatedAccount()
updateFxASyncOverrideMenu()
+ updateFxAAllowDomesticChinaServerMenu()
// Signed-in, no problems.
if (account != null && !accountManager.accountNeedsReauth()) {
diff --git a/app/src/main/java/org/mozilla/fenix/settings/account/SignOutFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/account/SignOutFragment.kt
index fe0b30c2a..e5f960008 100644
--- a/app/src/main/java/org/mozilla/fenix/settings/account/SignOutFragment.kt
+++ b/app/src/main/java/org/mozilla/fenix/settings/account/SignOutFragment.kt
@@ -66,9 +66,10 @@ class SignOutFragment : AppCompatDialogFragment() {
accountManager.logout()
}.invokeOnCompletion {
runIfFragmentIsAttached {
- if (!findNavController().popBackStack(R.id.settingsFragment, false)) {
+ if (this.isVisible) {
dismiss()
}
+ findNavController().popBackStack()
}
}
}
diff --git a/app/src/main/java/org/mozilla/fenix/settings/account/TurnOnSyncFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/account/TurnOnSyncFragment.kt
index 27734d945..2433bb5b2 100644
--- a/app/src/main/java/org/mozilla/fenix/settings/account/TurnOnSyncFragment.kt
+++ b/app/src/main/java/org/mozilla/fenix/settings/account/TurnOnSyncFragment.kt
@@ -21,10 +21,12 @@ import mozilla.components.concept.sync.OAuthAccount
import mozilla.components.support.ktx.android.content.hasCamera
import mozilla.components.support.ktx.android.content.isPermissionGranted
import mozilla.components.support.ktx.android.view.hideKeyboard
+import org.mozilla.fenix.Config
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.components.FenixSnackbar
import org.mozilla.fenix.components.metrics.Event
+import org.mozilla.fenix.ext.navigateBlockingForAsyncNavGraph
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.ext.showToolbar
@@ -58,7 +60,7 @@ class TurnOnSyncFragment : Fragment(), AccountObserver {
private fun navigateToPairFragment() {
val directions = TurnOnSyncFragmentDirections.actionTurnOnSyncFragmentToPairFragment()
- requireView().findNavController().navigate(directions)
+ requireView().findNavController().navigateBlockingForAsyncNavGraph(directions)
requireComponents.analytics.metrics.track(Event.SyncAuthScanPairing)
}
@@ -114,7 +116,9 @@ class TurnOnSyncFragment : Fragment(), AccountObserver {
view.signInScanButton.setOnClickListener(paringClickListener)
view.signInEmailButton.setOnClickListener(signInClickListener)
view.signInInstructions.text = HtmlCompat.fromHtml(
- getString(R.string.sign_in_instructions),
+ if (requireContext().settings().allowDomesticChinaFxaServer && Config.channel.isMozillaOnline)
+ getString(R.string.sign_in_instructions_cn)
+ else getString(R.string.sign_in_instructions),
HtmlCompat.FROM_HTML_MODE_LEGACY
)
diff --git a/app/src/main/java/org/mozilla/fenix/settings/advanced/LocaleViewHolders.kt b/app/src/main/java/org/mozilla/fenix/settings/advanced/LocaleViewHolders.kt
index 2421d45d9..3163e9fe3 100644
--- a/app/src/main/java/org/mozilla/fenix/settings/advanced/LocaleViewHolders.kt
+++ b/app/src/main/java/org/mozilla/fenix/settings/advanced/LocaleViewHolders.kt
@@ -23,10 +23,14 @@ class LocaleViewHolder(
if (locale.toString().equals("vec", ignoreCase = true)) {
locale.toString()
}
- // Capitalisation is done using the rules of the appropriate locale (endonym and exonym).
- locale_title_text.text = getDisplayName(locale)
- // Show the given locale using the device locale for the subtitle.
- locale_subtitle_text.text = locale.getProperDisplayName()
+ if (locale.language == "zh") {
+ bindChineseLocale(locale)
+ } else {
+ // Capitalisation is done using the rules of the appropriate locale (endonym and exonym).
+ locale_title_text.text = getDisplayName(locale)
+ // Show the given locale using the device locale for the subtitle.
+ locale_subtitle_text.text = locale.getProperDisplayName()
+ }
locale_selected_icon.isVisible = isCurrentLocaleSelected(locale, isDefault = false)
itemView.setOnClickListener {
@@ -34,6 +38,20 @@ class LocaleViewHolder(
}
}
+ private fun bindChineseLocale(locale: Locale) {
+ if (locale.country == "CN") {
+ locale_title_text.text =
+ Locale.forLanguageTag("zh-Hans").getDisplayName(locale).capitalize(locale)
+ locale_subtitle_text.text =
+ Locale.forLanguageTag("zh-Hans").displayName.capitalize(Locale.getDefault())
+ } else if (locale.country == "TW") {
+ locale_title_text.text =
+ Locale.forLanguageTag("zh-Hant").getDisplayName(locale).capitalize(locale)
+ locale_subtitle_text.text =
+ Locale.forLanguageTag("zh-Hant").displayName.capitalize(Locale.getDefault())
+ }
+ }
+
private fun getDisplayName(locale: Locale): String {
val displayName = locale.getDisplayName(locale).capitalize(locale)
if (displayName.equals(locale.toString(), ignoreCase = true)) {
@@ -255,8 +273,16 @@ class SystemLocaleViewHolder(
override fun bind(locale: Locale) {
locale_title_text.text = itemView.context.getString(R.string.default_locale_text)
- // Use the device locale for the system locale subtitle.
- locale_subtitle_text.text = locale.getDisplayName(locale).capitalize(locale)
+ if (locale.script == "Hant") {
+ locale_subtitle_text.text =
+ Locale.forLanguageTag("zh-Hant").displayName.capitalize(Locale.getDefault())
+ } else if (locale.script == "Hans") {
+ locale_subtitle_text.text =
+ Locale.forLanguageTag("zh-Hans").displayName.capitalize(Locale.getDefault())
+ } else {
+ // Use the device locale for the system locale subtitle.
+ locale_subtitle_text.text = locale.getDisplayName(locale).capitalize(locale)
+ }
locale_selected_icon.isVisible = isCurrentLocaleSelected(locale, isDefault = true)
itemView.setOnClickListener {
interactor.onDefaultLocaleSelected()
diff --git a/app/src/main/java/org/mozilla/fenix/settings/creditcards/CreditCardEditorFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/creditcards/CreditCardEditorFragment.kt
new file mode 100644
index 000000000..91b9ac8ce
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/settings/creditcards/CreditCardEditorFragment.kt
@@ -0,0 +1,108 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.fenix.settings.creditcards
+
+import android.os.Bundle
+import android.view.Menu
+import android.view.MenuInflater
+import android.view.MenuItem
+import android.view.View
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.lifecycleScope
+import androidx.navigation.fragment.findNavController
+import androidx.navigation.fragment.navArgs
+import kotlinx.android.synthetic.main.fragment_credit_card_editor.*
+import mozilla.components.concept.storage.UpdatableCreditCardFields
+import mozilla.components.support.ktx.android.view.hideKeyboard
+import org.mozilla.fenix.R
+import org.mozilla.fenix.ext.components
+import org.mozilla.fenix.ext.showToolbar
+import org.mozilla.fenix.settings.creditcards.controller.DefaultCreditCardEditorController
+import org.mozilla.fenix.settings.creditcards.interactor.CreditCardEditorInteractor
+import org.mozilla.fenix.settings.creditcards.interactor.DefaultCreditCardEditorInteractor
+import org.mozilla.fenix.settings.creditcards.view.CreditCardEditorView
+
+/**
+ * Display a credit card editor for adding and editing a credit card.
+ */
+class CreditCardEditorFragment : Fragment(R.layout.fragment_credit_card_editor) {
+
+ private val args by navArgs()
+
+ /**
+ * Returns true if a credit card is being edited, and false otherwise.
+ */
+ private val isEditing: Boolean
+ get() = args.creditCard != null
+
+ private lateinit var interactor: CreditCardEditorInteractor
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ setHasOptionsMenu(true)
+
+ if (!isEditing) {
+ showToolbar(getString(R.string.credit_cards_add_card))
+ } else {
+ showToolbar(getString(R.string.credit_cards_edit_card))
+ }
+
+ interactor = DefaultCreditCardEditorInteractor(
+ controller = DefaultCreditCardEditorController(
+ storage = requireContext().components.core.autofillStorage,
+ lifecycleScope = lifecycleScope,
+ navController = findNavController()
+ )
+ )
+
+ val creditCardEditorState =
+ args.creditCard?.toCreditCardEditorState() ?: getInitialCreditCardEditorState()
+ CreditCardEditorView(view, interactor).bind(creditCardEditorState)
+ }
+
+ override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
+ inflater.inflate(R.menu.credit_card_editor, menu)
+
+ menu.findItem(R.id.delete_credit_card_button).isVisible = isEditing
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) {
+ R.id.delete_credit_card_button -> {
+ args.creditCard?.let { interactor.onDeleteCardButtonClicked(it.guid) }
+ true
+ }
+ R.id.save_credit_card_button -> {
+ view?.hideKeyboard()
+
+ val creditCard = args.creditCard
+ val creditCardFields = UpdatableCreditCardFields(
+ billingName = name_on_card_input.text.toString(),
+ cardNumber = card_number_input.text.toString(),
+ expiryMonth = (expiry_month_drop_down.selectedItemPosition + 1).toLong(),
+ expiryYear = expiry_year_drop_down.selectedItem.toString().toLong(),
+ cardType = CARD_TYPE_PLACEHOLDER
+ )
+
+ if (creditCard != null) {
+ interactor.onUpdateCreditCard(creditCard.guid, creditCardFields)
+ } else {
+ interactor.onSaveCreditCard(creditCardFields)
+ }
+
+ true
+ }
+ else -> false
+ }
+
+ companion object {
+ // Number of years to show in the expiry year dropdown.
+ const val NUMBER_OF_YEARS_TO_SHOW = 10
+
+ // Placeholder for the card type. This will be replaced when we can identify the card type.
+ // This is dependent on https://github.com/mozilla-mobile/android-components/issues/9813.
+ const val CARD_TYPE_PLACEHOLDER = ""
+ }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/settings/creditcards/CreditCardEditorState.kt b/app/src/main/java/org/mozilla/fenix/settings/creditcards/CreditCardEditorState.kt
new file mode 100644
index 000000000..ed9c62747
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/settings/creditcards/CreditCardEditorState.kt
@@ -0,0 +1,61 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.fenix.settings.creditcards
+
+import mozilla.components.concept.storage.CreditCard
+import org.mozilla.fenix.settings.creditcards.CreditCardEditorFragment.Companion.NUMBER_OF_YEARS_TO_SHOW
+import java.util.Calendar
+
+/**
+ * The state for the [CreditCardEditorFragment].
+ *
+ * @property guid The unique identifier for the edited credit card.
+ * @property billingName The credit card billing name to display.
+ * @property cardNumber The credit card number to display.
+ * @property expiryMonth The selected credit card expiry month.
+ * @property expiryYears The range of expiry years to display.
+ * @property isEditing Whether or not the credit card is being edited.
+ */
+data class CreditCardEditorState(
+ val guid: String = "",
+ val billingName: String = "",
+ val cardNumber: String = "",
+ val expiryMonth: Int = 1,
+ val expiryYears: Pair,
+ val isEditing: Boolean = false
+)
+
+/**
+ * Returns a [CreditCardEditorState] from the given [CreditCard].
+ */
+fun CreditCard.toCreditCardEditorState(): CreditCardEditorState {
+ val startYear = expiryYear.toInt()
+ val endYear = startYear + NUMBER_OF_YEARS_TO_SHOW
+
+ return CreditCardEditorState(
+ guid = guid,
+ billingName = billingName,
+ cardNumber = cardNumber,
+ expiryMonth = expiryMonth.toInt(),
+ expiryYears = Pair(startYear, endYear),
+ isEditing = true
+ )
+}
+
+/**
+ * Returns the initial credit editor state if no credit card is provided.
+ *
+ * @return an empty [CreditCardEditorState] with a range of expiry years based on the latest
+ * 10 years.
+ */
+fun getInitialCreditCardEditorState(): CreditCardEditorState {
+ val calendar = Calendar.getInstance()
+ val startYear = calendar.get(Calendar.YEAR)
+ val endYear = startYear + NUMBER_OF_YEARS_TO_SHOW
+
+ return CreditCardEditorState(
+ expiryYears = Pair(startYear, endYear)
+ )
+}
diff --git a/app/src/main/java/org/mozilla/fenix/settings/creditcards/CreditCardsFragmentStore.kt b/app/src/main/java/org/mozilla/fenix/settings/creditcards/CreditCardsFragmentStore.kt
new file mode 100644
index 000000000..043607625
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/settings/creditcards/CreditCardsFragmentStore.kt
@@ -0,0 +1,64 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.fenix.settings.creditcards
+
+import mozilla.components.concept.storage.CreditCard
+import mozilla.components.lib.state.Action
+import mozilla.components.lib.state.State
+import mozilla.components.lib.state.Store
+
+/**
+ * The [Store] for holding the [CreditCardsListState] and applying [CreditCardsAction]s.
+ */
+class CreditCardsFragmentStore(initialState: CreditCardsListState) :
+ Store(
+ initialState, ::creditCardsFragmentStateReducer
+ )
+
+/**
+ * The state for [CreditCardsManagementFragment].
+ *
+ * @property creditCards The list of [CreditCard]s to display in the credit card list.
+ * @property isLoading True if the credit cards are still being loaded from storage,
+ * otherwise false.
+ */
+data class CreditCardsListState(
+ val creditCards: List,
+ val isLoading: Boolean = true
+) : State
+
+/**
+ * Actions to dispatch through the [CreditCardsFragmentStore] to modify the [CreditCardsListState]
+ * through the [creditCardsFragmentStateReducer].
+ */
+sealed class CreditCardsAction : Action {
+ /**
+ * Updates the list of credit cards with the provided [creditCards].
+ *
+ * @param creditCards The list of [CreditCard]s to display in the credit card list.
+ */
+ data class UpdateCreditCards(val creditCards: List) : CreditCardsAction()
+}
+
+/**
+ * Reduces the credit cards state from the current state with the provided [action] to be performed.
+ *
+ * @param state The current credit cards state.
+ * @param action The action to be performed on the state.
+ * @return the new [CreditCardsListState] with the [action] executed.
+ */
+private fun creditCardsFragmentStateReducer(
+ state: CreditCardsListState,
+ action: CreditCardsAction
+): CreditCardsListState {
+ return when (action) {
+ is CreditCardsAction.UpdateCreditCards -> {
+ state.copy(
+ creditCards = action.creditCards,
+ isLoading = false
+ )
+ }
+ }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/settings/creditcards/CreditCardsManagementFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/creditcards/CreditCardsManagementFragment.kt
new file mode 100644
index 000000000..be0ceaffa
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/settings/creditcards/CreditCardsManagementFragment.kt
@@ -0,0 +1,86 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.fenix.settings.creditcards
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.lifecycleScope
+import androidx.navigation.fragment.findNavController
+import kotlinx.android.synthetic.main.fragment_saved_cards.view.*
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+import mozilla.components.lib.state.ext.consumeFrom
+import org.mozilla.fenix.R
+import org.mozilla.fenix.components.StoreProvider
+import org.mozilla.fenix.ext.components
+import org.mozilla.fenix.ext.showToolbar
+import org.mozilla.fenix.settings.creditcards.controller.DefaultCreditCardsManagementController
+import org.mozilla.fenix.settings.creditcards.interactor.CreditCardsManagementInteractor
+import org.mozilla.fenix.settings.creditcards.interactor.DefaultCreditCardsManagementInteractor
+import org.mozilla.fenix.settings.creditcards.view.CreditCardsManagementView
+
+/**
+ * Displays a list of saved credit cards.
+ */
+class CreditCardsManagementFragment : Fragment() {
+
+ private lateinit var creditCardsStore: CreditCardsFragmentStore
+ private lateinit var interactor: CreditCardsManagementInteractor
+ private lateinit var creditCardsView: CreditCardsManagementView
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ val view = inflater.inflate(R.layout.fragment_saved_cards, container, false)
+
+ creditCardsStore = StoreProvider.get(this) {
+ CreditCardsFragmentStore(CreditCardsListState(creditCards = emptyList()))
+ }
+
+ interactor = DefaultCreditCardsManagementInteractor(
+ controller = DefaultCreditCardsManagementController(
+ navController = findNavController()
+ )
+ )
+
+ creditCardsView = CreditCardsManagementView(view.saved_cards_layout, interactor)
+
+ loadCreditCards()
+
+ return view
+ }
+
+ @ExperimentalCoroutinesApi
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ consumeFrom(creditCardsStore) { state ->
+ creditCardsView.update(state)
+ }
+ }
+
+ override fun onResume() {
+ super.onResume()
+ showToolbar(getString(R.string.credit_cards_saved_cards))
+ }
+
+ /**
+ * Fetches all the credit cards from the autofill storage and updates the
+ * [CreditCardsFragmentStore] with the list of credit cards.
+ */
+ private fun loadCreditCards() {
+ lifecycleScope.launch(Dispatchers.IO) {
+ val creditCards = requireContext().components.core.autofillStorage.getAllCreditCards()
+
+ lifecycleScope.launch(Dispatchers.Main) {
+ creditCardsStore.dispatch(CreditCardsAction.UpdateCreditCards(creditCards))
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/settings/creditcards/CreditCardsSettingFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/creditcards/CreditCardsSettingFragment.kt
new file mode 100644
index 000000000..bcb197137
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/settings/creditcards/CreditCardsSettingFragment.kt
@@ -0,0 +1,77 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.fenix.settings.creditcards
+
+import android.os.Bundle
+import androidx.navigation.fragment.findNavController
+import androidx.preference.Preference
+import androidx.preference.PreferenceFragmentCompat
+import mozilla.components.service.fxa.SyncEngine
+import org.mozilla.fenix.R
+import org.mozilla.fenix.ext.getPreferenceKey
+import org.mozilla.fenix.ext.navigateBlockingForAsyncNavGraph
+import org.mozilla.fenix.ext.requireComponents
+import org.mozilla.fenix.ext.showToolbar
+import org.mozilla.fenix.settings.SyncPreferenceView
+import org.mozilla.fenix.settings.requirePreference
+
+/**
+ * "Credit cards" settings fragment displays a list of settings related to autofilling, adding and
+ * syncing credit cards.
+ */
+class CreditCardsSettingFragment : PreferenceFragmentCompat() {
+
+ override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
+ setPreferencesFromResource(R.xml.credit_cards_preferences, rootKey)
+ }
+
+ override fun onResume() {
+ super.onResume()
+
+ showToolbar(getString(R.string.preferences_credit_cards))
+
+ SyncPreferenceView(
+ syncPreference = requirePreference(R.string.pref_key_credit_cards_sync_cards_across_devices),
+ lifecycleOwner = viewLifecycleOwner,
+ accountManager = requireComponents.backgroundServices.accountManager,
+ syncEngine = SyncEngine.Passwords,
+ onSignInToSyncClicked = {
+ val directions =
+ CreditCardsSettingFragmentDirections.actionCreditCardsSettingFragmentToTurnOnSyncFragment()
+ findNavController().navigateBlockingForAsyncNavGraph(directions)
+ },
+ onSyncStatusClicked = {
+ val directions =
+ CreditCardsSettingFragmentDirections.actionGlobalAccountSettingsFragment()
+ findNavController().navigateBlockingForAsyncNavGraph(directions)
+ },
+ onReconnectClicked = {
+ val directions =
+ CreditCardsSettingFragmentDirections.actionGlobalAccountProblemFragment()
+ findNavController().navigateBlockingForAsyncNavGraph(directions)
+ }
+ )
+ }
+
+ @Suppress("MaxLineLength")
+ override fun onPreferenceTreeClick(preference: Preference): Boolean {
+ when (preference.key) {
+ getPreferenceKey(R.string.pref_key_credit_cards_add_credit_card) -> {
+ val directions =
+ CreditCardsSettingFragmentDirections
+ .actionCreditCardsSettingFragmentToCreditCardEditorFragment()
+ findNavController().navigateBlockingForAsyncNavGraph(directions)
+ }
+ getPreferenceKey(R.string.pref_key_credit_cards_manage_saved_cards) -> {
+ val directions =
+ CreditCardsSettingFragmentDirections
+ .actionCreditCardsSettingFragmentToCreditCardsManagementFragment()
+ findNavController().navigateBlockingForAsyncNavGraph(directions)
+ }
+ }
+
+ return super.onPreferenceTreeClick(preference)
+ }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/settings/creditcards/controller/CreditCardEditorController.kt b/app/src/main/java/org/mozilla/fenix/settings/creditcards/controller/CreditCardEditorController.kt
new file mode 100644
index 000000000..bfa6ca66b
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/settings/creditcards/controller/CreditCardEditorController.kt
@@ -0,0 +1,93 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.fenix.settings.creditcards.controller
+
+import androidx.navigation.NavController
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import mozilla.components.concept.storage.UpdatableCreditCardFields
+import mozilla.components.service.sync.autofill.AutofillCreditCardsAddressesStorage
+import org.mozilla.fenix.settings.creditcards.CreditCardEditorFragment
+import org.mozilla.fenix.settings.creditcards.interactor.CreditCardEditorInteractor
+
+/**
+ * [CreditCardEditorFragment] controller. An interface that handles the view manipulation of the
+ * credit card editor.
+ */
+interface CreditCardEditorController {
+
+ /**
+ * @see [CreditCardEditorInteractor.onCancelButtonClicked]
+ */
+ fun handleCancelButtonClicked()
+
+ /**
+ * @see [CreditCardEditorInteractor.onDeleteCardButtonClicked]
+ */
+ fun handleDeleteCreditCard(guid: String)
+
+ /**
+ * @see [CreditCardEditorInteractor.onSaveCreditCard]
+ */
+ fun handleSaveCreditCard(creditCardFields: UpdatableCreditCardFields)
+
+ /**
+ * @see [CreditCardEditorInteractor.onUpdateCreditCard]
+ */
+ fun handleUpdateCreditCard(guid: String, creditCardFields: UpdatableCreditCardFields)
+}
+
+/**
+ * The default implementation of [CreditCardEditorController].
+ *
+ * @param storage An instance of the [AutofillCreditCardsAddressesStorage] for adding and retrieving
+ * credit cards.
+ * @param lifecycleScope [CoroutineScope] scope to launch coroutines.
+ * @param navController [NavController] used for navigation.
+ * @param ioDispatcher [CoroutineDispatcher] used for executing async tasks. Defaults to [Dispatchers.IO].
+ */
+class DefaultCreditCardEditorController(
+ private val storage: AutofillCreditCardsAddressesStorage,
+ private val lifecycleScope: CoroutineScope,
+ private val navController: NavController,
+ private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
+) : CreditCardEditorController {
+
+ override fun handleCancelButtonClicked() {
+ navController.popBackStack()
+ }
+
+ override fun handleDeleteCreditCard(guid: String) {
+ lifecycleScope.launch(ioDispatcher) {
+ storage.deleteCreditCard(guid)
+
+ lifecycleScope.launch(Dispatchers.Main) {
+ navController.popBackStack()
+ }
+ }
+ }
+
+ override fun handleSaveCreditCard(creditCardFields: UpdatableCreditCardFields) {
+ lifecycleScope.launch(ioDispatcher) {
+ storage.addCreditCard(creditCardFields)
+
+ lifecycleScope.launch(Dispatchers.Main) {
+ navController.popBackStack()
+ }
+ }
+ }
+
+ override fun handleUpdateCreditCard(guid: String, creditCardFields: UpdatableCreditCardFields) {
+ lifecycleScope.launch(ioDispatcher) {
+ storage.updateCreditCard(guid, creditCardFields)
+
+ lifecycleScope.launch(Dispatchers.Main) {
+ navController.popBackStack()
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/settings/creditcards/controller/CreditCardsManagementController.kt b/app/src/main/java/org/mozilla/fenix/settings/creditcards/controller/CreditCardsManagementController.kt
new file mode 100644
index 000000000..bcc0906a0
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/settings/creditcards/controller/CreditCardsManagementController.kt
@@ -0,0 +1,41 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.fenix.settings.creditcards.controller
+
+import androidx.navigation.NavController
+import mozilla.components.concept.storage.CreditCard
+import org.mozilla.fenix.ext.navigateBlockingForAsyncNavGraph
+import org.mozilla.fenix.settings.creditcards.CreditCardsManagementFragment
+import org.mozilla.fenix.settings.creditcards.CreditCardsManagementFragmentDirections
+import org.mozilla.fenix.settings.creditcards.interactor.CreditCardsManagementInteractor
+
+/**
+ * [CreditCardsManagementFragment] controller. An interface that handles the view manipulation of
+ * the credit cards manager triggered by the Interactor.
+ */
+interface CreditCardsManagementController {
+
+ /**
+ * @see [CreditCardsManagementInteractor.onSelectCreditCard]
+ */
+ fun handleCreditCardClicked(creditCard: CreditCard)
+}
+
+/**
+ * The default implementation of [CreditCardsManagementController].
+ */
+class DefaultCreditCardsManagementController(
+ private val navController: NavController
+) : CreditCardsManagementController {
+
+ override fun handleCreditCardClicked(creditCard: CreditCard) {
+ navController.navigateBlockingForAsyncNavGraph(
+ CreditCardsManagementFragmentDirections
+ .actionCreditCardsManagementFragmentToCreditCardEditorFragment(
+ creditCard = creditCard
+ )
+ )
+ }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/settings/creditcards/interactor/CreditCardEditorInteractor.kt b/app/src/main/java/org/mozilla/fenix/settings/creditcards/interactor/CreditCardEditorInteractor.kt
new file mode 100644
index 000000000..2633c57fe
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/settings/creditcards/interactor/CreditCardEditorInteractor.kt
@@ -0,0 +1,72 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.fenix.settings.creditcards.interactor
+
+import mozilla.components.concept.storage.UpdatableCreditCardFields
+import org.mozilla.fenix.settings.creditcards.controller.CreditCardEditorController
+
+/**
+ * Interface for the credit card editor Interactor.
+ */
+interface CreditCardEditorInteractor {
+
+ /**
+ * Navigates back to the credit card preference settings. Called when a user taps on the
+ * "Cancel" button.
+ */
+ fun onCancelButtonClicked()
+
+ /**
+ * Deletes the provided credit card in the credit card storage. Called when a user
+ * taps on the delete menu item or "Delete card" button.
+ *
+ * @param guid Unique identifier for the credit card to be deleted.
+ */
+ fun onDeleteCardButtonClicked(guid: String)
+
+ /**
+ * Saves the provided credit card field into the credit card storage. Called when a user
+ * taps on the save menu item or "Save" button.
+ *
+ * @param creditCardFields A [UpdatableCreditCardFields] record to add.
+ */
+ fun onSaveCreditCard(creditCardFields: UpdatableCreditCardFields)
+
+ /**
+ * Updates the provided credit card with the new credit card fields. Called when a user
+ * taps on the save menu item or "Save" button when editing an existing credit card.
+ *
+ * @param guid Unique identifier for the desired credit card.
+ * @param creditCardFields The credit card fields to update.
+ */
+ fun onUpdateCreditCard(guid: String, creditCardFields: UpdatableCreditCardFields)
+}
+
+/**
+ * The default implementation of [CreditCardEditorInteractor].
+ *
+ * @param controller An instance of [CreditCardEditorController] which will be delegated for all
+ * user interactions.
+ */
+class DefaultCreditCardEditorInteractor(
+ private val controller: CreditCardEditorController
+) : CreditCardEditorInteractor {
+
+ override fun onCancelButtonClicked() {
+ controller.handleCancelButtonClicked()
+ }
+
+ override fun onDeleteCardButtonClicked(guid: String) {
+ controller.handleDeleteCreditCard(guid)
+ }
+
+ override fun onSaveCreditCard(creditCardFields: UpdatableCreditCardFields) {
+ controller.handleSaveCreditCard(creditCardFields)
+ }
+
+ override fun onUpdateCreditCard(guid: String, creditCardFields: UpdatableCreditCardFields) {
+ controller.handleUpdateCreditCard(guid, creditCardFields)
+ }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/settings/creditcards/interactor/CreditCardsManagementInteractor.kt b/app/src/main/java/org/mozilla/fenix/settings/creditcards/interactor/CreditCardsManagementInteractor.kt
new file mode 100644
index 000000000..251d9de8c
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/settings/creditcards/interactor/CreditCardsManagementInteractor.kt
@@ -0,0 +1,37 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.fenix.settings.creditcards.interactor
+
+import mozilla.components.concept.storage.CreditCard
+import org.mozilla.fenix.settings.creditcards.controller.CreditCardsManagementController
+
+/**
+ * Interface for the credit cards management Interactor.
+ */
+interface CreditCardsManagementInteractor {
+
+ /**
+ * Navigates to the credit card editor to edit the selected credit card. Called when a user
+ * taps on a credit card item.
+ *
+ * @param creditCard The selected [CreditCard] to edit.
+ */
+ fun onSelectCreditCard(creditCard: CreditCard)
+}
+
+/**
+ * The default implementation of [CreditCardsManagementInteractor].
+ *
+ * @param controller An instance of [CreditCardsManagementController] which will be delegated for
+ * all user interactions.
+ */
+class DefaultCreditCardsManagementInteractor(
+ private val controller: CreditCardsManagementController
+) : CreditCardsManagementInteractor {
+
+ override fun onSelectCreditCard(creditCard: CreditCard) {
+ controller.handleCreditCardClicked(creditCard)
+ }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/settings/creditcards/view/CreditCardEditorView.kt b/app/src/main/java/org/mozilla/fenix/settings/creditcards/view/CreditCardEditorView.kt
new file mode 100644
index 000000000..adf19410c
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/settings/creditcards/view/CreditCardEditorView.kt
@@ -0,0 +1,118 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.fenix.settings.creditcards.view
+
+import android.R
+import android.view.View
+import android.widget.ArrayAdapter
+import kotlinx.android.extensions.LayoutContainer
+import kotlinx.android.synthetic.main.fragment_credit_card_editor.*
+import mozilla.components.concept.storage.UpdatableCreditCardFields
+import mozilla.components.support.ktx.android.view.hideKeyboard
+import org.mozilla.fenix.ext.toEditable
+import org.mozilla.fenix.settings.creditcards.CreditCardEditorFragment.Companion.CARD_TYPE_PLACEHOLDER
+import org.mozilla.fenix.settings.creditcards.CreditCardEditorState
+import org.mozilla.fenix.settings.creditcards.interactor.CreditCardEditorInteractor
+import java.text.SimpleDateFormat
+import java.util.Calendar
+import java.util.Locale
+
+/**
+ * Shows a credit card editor for adding or updating a credit card.
+ */
+class CreditCardEditorView(
+ override val containerView: View,
+ private val interactor: CreditCardEditorInteractor
+) : LayoutContainer {
+
+ /**
+ * Binds the given [CreditCardEditorState] in the [CreditCardEditorFragment].
+ */
+ fun bind(state: CreditCardEditorState) {
+ if (state.isEditing) {
+ delete_button.apply {
+ visibility = View.VISIBLE
+
+ setOnClickListener {
+ interactor.onDeleteCardButtonClicked(state.guid)
+ }
+ }
+ }
+
+ cancel_button.setOnClickListener {
+ interactor.onCancelButtonClicked()
+ }
+
+ save_button.setOnClickListener {
+ containerView.hideKeyboard()
+
+ val creditCardFields = UpdatableCreditCardFields(
+ billingName = name_on_card_input.text.toString(),
+ cardNumber = card_number_input.text.toString(),
+ expiryMonth = (expiry_month_drop_down.selectedItemPosition + 1).toLong(),
+ expiryYear = expiry_year_drop_down.selectedItem.toString().toLong(),
+ cardType = CARD_TYPE_PLACEHOLDER
+ )
+
+ if (state.isEditing) {
+ interactor.onUpdateCreditCard(state.guid, creditCardFields)
+ } else {
+ interactor.onSaveCreditCard(creditCardFields)
+ }
+ }
+
+ card_number_input.text = state.cardNumber.toEditable()
+ name_on_card_input.text = state.billingName.toEditable()
+
+ bindExpiryMonthDropDown(state.expiryMonth)
+ bindExpiryYearDropDown(state.expiryYears)
+ }
+
+ /**
+ * Setup the expiry month dropdown by formatting and populating it with the months in a calendar
+ * year, and set the selection to the provided expiry month.
+ *
+ * @param expiryMonth The selected credit card expiry month to display.
+ */
+ private fun bindExpiryMonthDropDown(expiryMonth: Int) {
+ val adapter =
+ ArrayAdapter(containerView.context, R.layout.simple_spinner_dropdown_item)
+ val dateFormat = SimpleDateFormat("MMMM (MM)", Locale.getDefault())
+
+ val calendar = Calendar.getInstance()
+ calendar.set(Calendar.DAY_OF_MONTH, 1)
+
+ for (month in 0..NUMBER_OF_MONTHS) {
+ calendar.set(Calendar.MONTH, month)
+ adapter.add(dateFormat.format(calendar.time))
+ }
+
+ expiry_month_drop_down.adapter = adapter
+ expiry_month_drop_down.setSelection(expiryMonth - 1)
+ }
+
+ /**
+ * Setup the expiry year dropdown with the range specified by the provided expiryYears
+ *
+ * @param expiryYears A range specifying the start and end year to display in the expiry year
+ * dropdown.
+ */
+ private fun bindExpiryYearDropDown(expiryYears: Pair) {
+ val adapter =
+ ArrayAdapter(containerView.context, R.layout.simple_spinner_dropdown_item)
+ val (startYear, endYear) = expiryYears
+
+ for (year in startYear until endYear) {
+ adapter.add(year.toString())
+ }
+
+ expiry_year_drop_down.adapter = adapter
+ }
+
+ companion object {
+ // Number of months in a year (0-indexed).
+ const val NUMBER_OF_MONTHS = 11
+ }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/settings/creditcards/view/CreditCardItemViewHolder.kt b/app/src/main/java/org/mozilla/fenix/settings/creditcards/view/CreditCardItemViewHolder.kt
new file mode 100644
index 000000000..b5d18ebe0
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/settings/creditcards/view/CreditCardItemViewHolder.kt
@@ -0,0 +1,56 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.fenix.settings.creditcards.view
+
+import android.view.View
+import kotlinx.android.synthetic.main.credit_card_list_item.*
+import mozilla.components.concept.storage.CreditCard
+import org.mozilla.fenix.R
+import org.mozilla.fenix.settings.creditcards.interactor.CreditCardsManagementInteractor
+import org.mozilla.fenix.utils.view.ViewHolder
+import java.text.SimpleDateFormat
+import java.util.Calendar
+import java.util.Locale
+
+/**
+ * View holder for a credit card list item.
+ */
+class CreditCardItemViewHolder(
+ view: View,
+ private val interactor: CreditCardsManagementInteractor
+) : ViewHolder(view) {
+
+ fun bind(creditCard: CreditCard) {
+ credit_card_number.text = creditCard.cardNumber
+
+ bindCreditCardExpiryDate(creditCard)
+
+ itemView.setOnClickListener {
+ interactor.onSelectCreditCard(creditCard)
+ }
+ }
+
+ /**
+ * Set the credit card expiry date formatted according to the locale.
+ */
+ private fun bindCreditCardExpiryDate(creditCard: CreditCard) {
+ val dateFormat = SimpleDateFormat(DATE_PATTERN, Locale.getDefault())
+
+ val calendar = Calendar.getInstance()
+ calendar.set(Calendar.DAY_OF_MONTH, 1)
+ // Subtract 1 from the expiry month since Calendar.Month is based on a 0-indexed.
+ calendar.set(Calendar.MONTH, creditCard.expiryMonth.toInt() - 1)
+ calendar.set(Calendar.YEAR, creditCard.expiryYear.toInt())
+
+ expiry_date.text = dateFormat.format(calendar.time)
+ }
+
+ companion object {
+ const val LAYOUT_ID = R.layout.credit_card_list_item
+
+ // Date format pattern for the credit card expiry date.
+ private const val DATE_PATTERN = "MM/yyyy"
+ }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/settings/creditcards/view/CreditCardsAdapter.kt b/app/src/main/java/org/mozilla/fenix/settings/creditcards/view/CreditCardsAdapter.kt
new file mode 100644
index 000000000..55d5c0328
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/settings/creditcards/view/CreditCardsAdapter.kt
@@ -0,0 +1,38 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.fenix.settings.creditcards.view
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.ListAdapter
+import mozilla.components.concept.storage.CreditCard
+import org.mozilla.fenix.settings.creditcards.interactor.CreditCardsManagementInteractor
+
+/**
+ * Adapter for a list of credit cards to be displayed.
+ */
+class CreditCardsAdapter(
+ private val interactor: CreditCardsManagementInteractor
+) : ListAdapter(DiffCallback) {
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CreditCardItemViewHolder {
+ val view = LayoutInflater.from(parent.context)
+ .inflate(CreditCardItemViewHolder.LAYOUT_ID, parent, false)
+ return CreditCardItemViewHolder(view, interactor)
+ }
+
+ override fun onBindViewHolder(holder: CreditCardItemViewHolder, position: Int) {
+ holder.bind(getItem(position))
+ }
+
+ internal object DiffCallback : DiffUtil.ItemCallback() {
+ override fun areItemsTheSame(oldItem: CreditCard, newItem: CreditCard) =
+ oldItem.guid == newItem.guid
+
+ override fun areContentsTheSame(oldItem: CreditCard, newItem: CreditCard) =
+ oldItem == newItem
+ }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/settings/creditcards/view/CreditCardsManagementView.kt b/app/src/main/java/org/mozilla/fenix/settings/creditcards/view/CreditCardsManagementView.kt
new file mode 100644
index 000000000..8bc175076
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/settings/creditcards/view/CreditCardsManagementView.kt
@@ -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.settings.creditcards.view
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.core.view.isVisible
+import androidx.recyclerview.widget.LinearLayoutManager
+import kotlinx.android.extensions.LayoutContainer
+import kotlinx.android.synthetic.main.component_credit_cards.*
+import org.mozilla.fenix.R
+import org.mozilla.fenix.settings.creditcards.CreditCardsListState
+import org.mozilla.fenix.settings.creditcards.interactor.CreditCardsManagementInteractor
+
+/**
+ * Shows a list of credit cards.
+ */
+class CreditCardsManagementView(
+ override val containerView: ViewGroup,
+ val interactor: CreditCardsManagementInteractor
+) : LayoutContainer {
+
+ private val creditCardsAdapter = CreditCardsAdapter(interactor)
+
+ init {
+ LayoutInflater.from(containerView.context).inflate(LAYOUT_ID, containerView, true)
+
+ credit_cards_list.apply {
+ adapter = creditCardsAdapter
+ layoutManager = LinearLayoutManager(containerView.context)
+ }
+ }
+
+ /**
+ * Updates the display of the credit cards based on the given [CreditCardsListState].
+ */
+ fun update(state: CreditCardsListState) {
+ progress_bar.isVisible = state.isLoading
+ credit_cards_list.isVisible = state.creditCards.isNotEmpty()
+
+ creditCardsAdapter.submitList(state.creditCards)
+ }
+
+ companion object {
+ const val LAYOUT_ID = R.layout.component_credit_cards
+ }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/settings/deletebrowsingdata/DeleteAndQuit.kt b/app/src/main/java/org/mozilla/fenix/settings/deletebrowsingdata/DeleteAndQuit.kt
index e5fe0ac42..d7f8a6d6e 100644
--- a/app/src/main/java/org/mozilla/fenix/settings/deletebrowsingdata/DeleteAndQuit.kt
+++ b/app/src/main/java/org/mozilla/fenix/settings/deletebrowsingdata/DeleteAndQuit.kt
@@ -49,7 +49,7 @@ fun deleteAndQuit(activity: Activity, coroutineScope: CoroutineScope, snackbar:
snackbar?.dismiss()
- activity.finish()
+ activity.finishAndRemoveTask()
}
}
diff --git a/app/src/main/java/org/mozilla/fenix/settings/deletebrowsingdata/DeleteBrowsingDataFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/deletebrowsingdata/DeleteBrowsingDataFragment.kt
index 4bb7dcd1c..a1d0fd7c0 100644
--- a/app/src/main/java/org/mozilla/fenix/settings/deletebrowsingdata/DeleteBrowsingDataFragment.kt
+++ b/app/src/main/java/org/mozilla/fenix/settings/deletebrowsingdata/DeleteBrowsingDataFragment.kt
@@ -28,6 +28,7 @@ import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged
import org.mozilla.fenix.R
import org.mozilla.fenix.components.FenixSnackbar
import org.mozilla.fenix.components.metrics.Event
+import org.mozilla.fenix.ext.navigateBlockingForAsyncNavGraph
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.ext.showToolbar
@@ -199,7 +200,7 @@ class DeleteBrowsingDataFragment : Fragment(R.layout.fragment_delete_browsing_da
// If the user deletes all open tabs we need to make sure we remove
// the BrowserFragment from the backstack.
popBackStack(R.id.homeFragment, false)
- navigate(DeleteBrowsingDataFragmentDirections.actionGlobalSettingsFragment())
+ navigateBlockingForAsyncNavGraph(DeleteBrowsingDataFragmentDirections.actionGlobalSettingsFragment())
}
}
}
diff --git a/app/src/main/java/org/mozilla/fenix/settings/logins/controller/LoginsListController.kt b/app/src/main/java/org/mozilla/fenix/settings/logins/controller/LoginsListController.kt
index 8d4c5629f..2f662ecca 100644
--- a/app/src/main/java/org/mozilla/fenix/settings/logins/controller/LoginsListController.kt
+++ b/app/src/main/java/org/mozilla/fenix/settings/logins/controller/LoginsListController.kt
@@ -8,6 +8,7 @@ import androidx.navigation.NavController
import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.components.metrics.MetricController
+import org.mozilla.fenix.ext.navigateBlockingForAsyncNavGraph
import org.mozilla.fenix.settings.SupportUtils
import org.mozilla.fenix.settings.logins.LoginsAction
import org.mozilla.fenix.settings.logins.LoginsFragmentStore
@@ -40,7 +41,7 @@ class LoginsListController(
fun handleItemClicked(item: SavedLogin) {
loginsFragmentStore.dispatch(LoginsAction.LoginSelected(item))
metrics.track(Event.OpenOneLogin)
- navController.navigate(
+ navController.navigateBlockingForAsyncNavGraph(
SavedLoginsFragmentDirections.actionSavedLoginsFragmentToLoginDetailFragment(item.guid)
)
}
diff --git a/app/src/main/java/org/mozilla/fenix/settings/logins/controller/SavedLoginsStorageController.kt b/app/src/main/java/org/mozilla/fenix/settings/logins/controller/SavedLoginsStorageController.kt
index 5257c73f8..885a2ce90 100644
--- a/app/src/main/java/org/mozilla/fenix/settings/logins/controller/SavedLoginsStorageController.kt
+++ b/app/src/main/java/org/mozilla/fenix/settings/logins/controller/SavedLoginsStorageController.kt
@@ -20,6 +20,7 @@ import mozilla.components.service.sync.logins.LoginsStorageException
import mozilla.components.service.sync.logins.NoSuchRecordException
import mozilla.components.service.sync.logins.SyncableLoginsStorage
import org.mozilla.fenix.R
+import org.mozilla.fenix.ext.navigateBlockingForAsyncNavGraph
import org.mozilla.fenix.settings.logins.LoginsAction
import org.mozilla.fenix.settings.logins.LoginsFragmentStore
import org.mozilla.fenix.settings.logins.fragment.EditLoginFragmentDirections
@@ -83,7 +84,7 @@ open class SavedLoginsStorageController(
EditLoginFragmentDirections.actionEditLoginFragmentToLoginDetailFragment(
loginId
)
- navController.navigate(directions)
+ navController.navigateBlockingForAsyncNavGraph(directions)
}
}
saveLoginJob?.invokeOnCompletion {
diff --git a/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/EditLoginFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/EditLoginFragment.kt
index 9adcee40f..36b5844db 100644
--- a/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/EditLoginFragment.kt
+++ b/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/EditLoginFragment.kt
@@ -30,6 +30,7 @@ import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.redirectToReAuth
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.settings
+import org.mozilla.fenix.ext.toEditable
import org.mozilla.fenix.settings.logins.LoginsAction
import org.mozilla.fenix.settings.logins.LoginsFragmentStore
import org.mozilla.fenix.settings.logins.SavedLogin
@@ -45,8 +46,6 @@ import org.mozilla.fenix.settings.logins.togglePasswordReveal
@Suppress("TooManyFunctions", "NestedBlockDepth", "ForbiddenComment")
class EditLoginFragment : Fragment(R.layout.fragment_edit_login) {
- private fun String.toEditable(): Editable = Editable.Factory.getInstance().newEditable(this)
-
private val args by navArgs()
private lateinit var loginsFragmentStore: LoginsFragmentStore
private lateinit var interactor: EditLoginInteractor
diff --git a/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/LoginDetailFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/LoginDetailFragment.kt
index 10b031877..3826179c0 100644
--- a/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/LoginDetailFragment.kt
+++ b/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/LoginDetailFragment.kt
@@ -33,6 +33,7 @@ import org.mozilla.fenix.components.StoreProvider
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.increaseTapArea
+import org.mozilla.fenix.ext.navigateBlockingForAsyncNavGraph
import org.mozilla.fenix.ext.redirectToReAuth
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.settings
@@ -199,7 +200,7 @@ class LoginDetailFragment : Fragment(R.layout.fragment_login_detail) {
LoginDetailFragmentDirections.actionLoginDetailFragmentToEditLoginFragment(
login!!
)
- findNavController().navigate(directions)
+ findNavController().navigateBlockingForAsyncNavGraph(directions)
}
private fun displayDeleteLoginDialog() {
diff --git a/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/SavedLoginsAuthFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/SavedLoginsAuthFragment.kt
index 021ef626d..f92d070d4 100644
--- a/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/SavedLoginsAuthFragment.kt
+++ b/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/SavedLoginsAuthFragment.kt
@@ -27,6 +27,7 @@ import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
import org.mozilla.fenix.R
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.ext.components
+import org.mozilla.fenix.ext.navigateBlockingForAsyncNavGraph
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.runIfFragmentIsAttached
import org.mozilla.fenix.ext.secure
@@ -130,17 +131,17 @@ class SavedLoginsAuthFragment : PreferenceFragmentCompat() {
onSignInToSyncClicked = {
val directions =
SavedLoginsAuthFragmentDirections.actionSavedLoginsAuthFragmentToTurnOnSyncFragment()
- findNavController().navigate(directions)
+ findNavController().navigateBlockingForAsyncNavGraph(directions)
},
onSyncStatusClicked = {
val directions =
SavedLoginsAuthFragmentDirections.actionGlobalAccountSettingsFragment()
- findNavController().navigate(directions)
+ findNavController().navigateBlockingForAsyncNavGraph(directions)
},
onReconnectClicked = {
val directions =
SavedLoginsAuthFragmentDirections.actionGlobalAccountProblemFragment()
- findNavController().navigate(directions)
+ findNavController().navigateBlockingForAsyncNavGraph(directions)
}
)
@@ -213,19 +214,19 @@ class SavedLoginsAuthFragment : PreferenceFragmentCompat() {
context?.components?.analytics?.metrics?.track(Event.OpenLogins)
val directions =
SavedLoginsAuthFragmentDirections.actionSavedLoginsAuthFragmentToLoginsListFragment()
- findNavController().navigate(directions)
+ findNavController().navigateBlockingForAsyncNavGraph(directions)
}
private fun navigateToSaveLoginSettingFragment() {
val directions =
SavedLoginsAuthFragmentDirections.actionSavedLoginsAuthFragmentToSavedLoginsSettingFragment()
- findNavController().navigate(directions)
+ findNavController().navigateBlockingForAsyncNavGraph(directions)
}
private fun navigateToLoginExceptionFragment() {
val directions =
SavedLoginsAuthFragmentDirections.actionSavedLoginsAuthFragmentToLoginExceptionsFragment()
- findNavController().navigate(directions)
+ findNavController().navigateBlockingForAsyncNavGraph(directions)
}
companion object {
diff --git a/app/src/main/java/org/mozilla/fenix/settings/quicksettings/QuickSettingsController.kt b/app/src/main/java/org/mozilla/fenix/settings/quicksettings/QuickSettingsController.kt
index f93b9c69f..c8dbe8ac4 100644
--- a/app/src/main/java/org/mozilla/fenix/settings/quicksettings/QuickSettingsController.kt
+++ b/app/src/main/java/org/mozilla/fenix/settings/quicksettings/QuickSettingsController.kt
@@ -18,6 +18,7 @@ import mozilla.components.feature.sitepermissions.SitePermissions
import mozilla.components.feature.tabs.TabsUseCases.AddNewTabUseCase
import mozilla.components.support.base.feature.OnNeedToRequestPermissions
import org.mozilla.fenix.components.PermissionStorage
+import org.mozilla.fenix.ext.navigateBlockingForAsyncNavGraph
import org.mozilla.fenix.settings.PhoneFeature
import org.mozilla.fenix.settings.quicksettings.ext.shouldBeEnabled
import org.mozilla.fenix.settings.toggle
@@ -199,6 +200,6 @@ class DefaultQuickSettingsController(
private fun navigateToManagePhoneFeature(phoneFeature: PhoneFeature) {
val directions = QuickSettingsSheetDialogFragmentDirections
.actionGlobalSitePermissionsManagePhoneFeature(phoneFeature)
- navController.navigate(directions)
+ navController.navigateBlockingForAsyncNavGraph(directions)
}
}
diff --git a/app/src/main/java/org/mozilla/fenix/settings/search/RadioSearchEngineListPreference.kt b/app/src/main/java/org/mozilla/fenix/settings/search/RadioSearchEngineListPreference.kt
index caea80564..fb79c79cf 100644
--- a/app/src/main/java/org/mozilla/fenix/settings/search/RadioSearchEngineListPreference.kt
+++ b/app/src/main/java/org/mozilla/fenix/settings/search/RadioSearchEngineListPreference.kt
@@ -38,6 +38,7 @@ import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.getRootView
+import org.mozilla.fenix.ext.navigateBlockingForAsyncNavGraph
import org.mozilla.fenix.utils.allowUndo
class RadioSearchEngineListPreference @JvmOverloads constructor(
@@ -146,7 +147,7 @@ class RadioSearchEngineListPreference @JvmOverloads constructor(
val directions = SearchEngineFragmentDirections
.actionSearchEngineFragmentToEditCustomSearchEngineFragment(engine.id)
- Navigation.findNavController(view).navigate(directions)
+ Navigation.findNavController(view).navigateBlockingForAsyncNavGraph(directions)
}
private fun deleteSearchEngine(
diff --git a/app/src/main/java/org/mozilla/fenix/settings/search/SearchEngineFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/search/SearchEngineFragment.kt
index c5971e7f9..41d648b57 100644
--- a/app/src/main/java/org/mozilla/fenix/settings/search/SearchEngineFragment.kt
+++ b/app/src/main/java/org/mozilla/fenix/settings/search/SearchEngineFragment.kt
@@ -13,6 +13,7 @@ import androidx.preference.SwitchPreference
import mozilla.components.support.ktx.android.view.hideKeyboard
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.getPreferenceKey
+import org.mozilla.fenix.ext.navigateBlockingForAsyncNavGraph
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.ext.showToolbar
import org.mozilla.fenix.settings.SharedPreferenceUpdater
@@ -99,7 +100,7 @@ class SearchEngineFragment : PreferenceFragmentCompat() {
getPreferenceKey(R.string.pref_key_add_search_engine) -> {
val directions = SearchEngineFragmentDirections
.actionSearchEngineFragmentToAddSearchEngineFragment()
- findNavController().navigate(directions)
+ findNavController().navigateBlockingForAsyncNavGraph(directions)
}
}
diff --git a/app/src/main/java/org/mozilla/fenix/settings/sitepermissions/SitePermissionsDetailsExceptionsFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/sitepermissions/SitePermissionsDetailsExceptionsFragment.kt
index 399d46ca8..49b7cdb43 100644
--- a/app/src/main/java/org/mozilla/fenix/settings/sitepermissions/SitePermissionsDetailsExceptionsFragment.kt
+++ b/app/src/main/java/org/mozilla/fenix/settings/sitepermissions/SitePermissionsDetailsExceptionsFragment.kt
@@ -20,6 +20,7 @@ import kotlinx.coroutines.withContext
import mozilla.components.feature.sitepermissions.SitePermissions
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.components
+import org.mozilla.fenix.ext.navigateBlockingForAsyncNavGraph
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.ext.showToolbar
@@ -161,6 +162,6 @@ class SitePermissionsDetailsExceptionsFragment : PreferenceFragmentCompat() {
phoneFeature = phoneFeature,
sitePermissions = sitePermissions
)
- requireView().findNavController().navigate(directions)
+ requireView().findNavController().navigateBlockingForAsyncNavGraph(directions)
}
}
diff --git a/app/src/main/java/org/mozilla/fenix/settings/sitepermissions/SitePermissionsFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/sitepermissions/SitePermissionsFragment.kt
index a87f1041e..688766782 100644
--- a/app/src/main/java/org/mozilla/fenix/settings/sitepermissions/SitePermissionsFragment.kt
+++ b/app/src/main/java/org/mozilla/fenix/settings/sitepermissions/SitePermissionsFragment.kt
@@ -12,6 +12,7 @@ import androidx.preference.PreferenceFragmentCompat
import org.mozilla.fenix.R
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.ext.getPreferenceKey
+import org.mozilla.fenix.ext.navigateBlockingForAsyncNavGraph
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.ext.showToolbar
@@ -42,7 +43,7 @@ class SitePermissionsFragment : PreferenceFragmentCompat() {
exceptionsCategory.onPreferenceClickListener = OnPreferenceClickListener {
val directions = SitePermissionsFragmentDirections.actionSitePermissionsToExceptions()
- Navigation.findNavController(requireView()).navigate(directions)
+ Navigation.findNavController(requireView()).navigateBlockingForAsyncNavGraph(directions)
true
}
}
@@ -59,17 +60,8 @@ class SitePermissionsFragment : PreferenceFragmentCompat() {
val context = requireContext()
val settings = context.settings()
- val summary = phoneFeature.getActionLabel(context, settings = settings)
- // Remove autoplaySummary after https://bugzilla.mozilla.org/show_bug.cgi?id=1621825 is fixed
- val autoplaySummary =
- if (summary == context.getString(R.string.preference_option_autoplay_allowed2)) {
- context.getString(R.string.preference_option_autoplay_allowed_wifi_only2)
- } else {
- null
- }
-
val cameraPhoneFeatures = requirePreference(phoneFeature.getPreferenceId())
- cameraPhoneFeatures.summary = autoplaySummary ?: summary
+ cameraPhoneFeatures.summary = phoneFeature.getActionLabel(context, settings = settings)
cameraPhoneFeatures.onPreferenceClickListener = OnPreferenceClickListener {
navigateToPhoneFeature(phoneFeature)
@@ -85,6 +77,6 @@ class SitePermissionsFragment : PreferenceFragmentCompat() {
requireComponents.analytics.metrics.track(Event.AutoPlaySettingVisited)
}
- Navigation.findNavController(requireView()).navigate(directions)
+ Navigation.findNavController(requireView()).navigateBlockingForAsyncNavGraph(directions)
}
}
diff --git a/app/src/main/java/org/mozilla/fenix/settings/sitepermissions/SitePermissionsManagePhoneFeatureFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/sitepermissions/SitePermissionsManagePhoneFeatureFragment.kt
index 2644e8ac9..a5f1bd0a6 100644
--- a/app/src/main/java/org/mozilla/fenix/settings/sitepermissions/SitePermissionsManagePhoneFeatureFragment.kt
+++ b/app/src/main/java/org/mozilla/fenix/settings/sitepermissions/SitePermissionsManagePhoneFeatureFragment.kt
@@ -79,14 +79,12 @@ class SitePermissionsManagePhoneFeatureFragment : Fragment() {
private fun initFirstRadio(rootView: View) {
with(rootView.ask_to_allow_radio) {
if (args.phoneFeature == AUTOPLAY_AUDIBLE) {
- // Disabled because GV does not allow this setting. TODO Reenable after
- // https://bugzilla.mozilla.org/show_bug.cgi?id=1621825 is fixed
-// text = getString(R.string.preference_option_autoplay_allowed2)
-// setOnClickListener {
-// saveActionInSettings(it.context, AUTOPLAY_ALLOW_ALL)
-// }
-// restoreState(AUTOPLAY_ALLOW_ALL)
- visibility = View.GONE
+ text = getString(R.string.preference_option_autoplay_allowed2)
+ setOnClickListener {
+ saveActionInSettings(AUTOPLAY_ALLOW_ALL)
+ }
+ restoreState(AUTOPLAY_ALLOW_ALL)
+ visibility = View.VISIBLE
} else {
text = getCombinedLabel(
getString(R.string.preference_option_phone_feature_ask_to_allow),
@@ -109,10 +107,7 @@ class SitePermissionsManagePhoneFeatureFragment : Fragment() {
getString(R.string.preference_option_autoplay_allowed_wifi_subtext)
)
setOnClickListener {
- // TODO replace with AUTOPLAY_ALLOW_ON_WIFI when
- // https://bugzilla.mozilla.org/show_bug.cgi?id=1621825 is fixed. This GV bug
- // makes ALLOW_ALL behave as ALLOW_ON_WIFI
- saveActionInSettings(AUTOPLAY_ALLOW_ALL)
+ saveActionInSettings(AUTOPLAY_ALLOW_ON_WIFI)
}
restoreState(AUTOPLAY_ALLOW_ON_WIFI)
} else {
@@ -129,7 +124,10 @@ class SitePermissionsManagePhoneFeatureFragment : Fragment() {
with(rootView.third_radio) {
if (args.phoneFeature == AUTOPLAY_AUDIBLE) {
visibility = View.VISIBLE
- text = getString(R.string.preference_option_autoplay_block_audio2)
+ text = getCombinedLabel(
+ getString(R.string.preference_option_autoplay_block_audio2),
+ getString(R.string.phone_feature_recommended)
+ )
setOnClickListener {
saveActionInSettings(AUTOPLAY_BLOCK_AUDIBLE)
}
@@ -151,10 +149,8 @@ class SitePermissionsManagePhoneFeatureFragment : Fragment() {
with(rootView.fourth_radio) {
if (args.phoneFeature == AUTOPLAY_AUDIBLE) {
visibility = View.VISIBLE
- text = getCombinedLabel(
- getString(R.string.preference_option_autoplay_blocked3),
- getString(R.string.phone_feature_recommended)
- )
+ text = getString(R.string.preference_option_autoplay_blocked3)
+
setOnClickListener {
saveActionInSettings(AUTOPLAY_BLOCK_ALL)
}
@@ -173,7 +169,7 @@ class SitePermissionsManagePhoneFeatureFragment : Fragment() {
}
private fun RadioButton.restoreState(buttonAutoplaySetting: Int) {
- if (settings.getAutoplayUserSetting(AUTOPLAY_BLOCK_ALL) == buttonAutoplaySetting) {
+ if (settings.getAutoplayUserSetting() == buttonAutoplaySetting) {
this.isChecked = true
this.setStartCheckedIndicator()
}
@@ -194,9 +190,11 @@ class SitePermissionsManagePhoneFeatureFragment : Fragment() {
val setting: Event.AutoPlaySettingChanged.AutoplaySetting
val (audible, inaudible) = when (autoplaySetting) {
- AUTOPLAY_ALLOW_ALL,
+ AUTOPLAY_ALLOW_ALL -> {
+ setting = Event.AutoPlaySettingChanged.AutoplaySetting.ALLOW_ALL
+ ALLOWED to ALLOWED
+ }
AUTOPLAY_ALLOW_ON_WIFI -> {
- settings.setAutoplayUserSetting(AUTOPLAY_ALLOW_ON_WIFI)
setting = Event.AutoPlaySettingChanged.AutoplaySetting.BLOCK_CELLULAR
BLOCKED to BLOCKED
}
diff --git a/app/src/main/java/org/mozilla/fenix/share/ShareController.kt b/app/src/main/java/org/mozilla/fenix/share/ShareController.kt
index bb7c72643..5607edf4d 100644
--- a/app/src/main/java/org/mozilla/fenix/share/ShareController.kt
+++ b/app/src/main/java/org/mozilla/fenix/share/ShareController.kt
@@ -31,6 +31,7 @@ import mozilla.components.support.ktx.kotlin.isExtensionUrl
import org.mozilla.fenix.R
import org.mozilla.fenix.components.FenixSnackbar
import org.mozilla.fenix.components.metrics.Event
+import org.mozilla.fenix.ext.navigateBlockingForAsyncNavGraph
import org.mozilla.fenix.ext.metrics
import org.mozilla.fenix.ext.nav
import org.mozilla.fenix.share.listadapters.AppShareOption
@@ -121,7 +122,7 @@ class DefaultShareController(
override fun handleAddNewDevice() {
val directions = ShareFragmentDirections.actionShareFragmentToAddNewDeviceFragment()
- navController.navigate(directions)
+ navController.navigateBlockingForAsyncNavGraph(directions)
}
override fun handleShareToDevice(device: Device) {
diff --git a/app/src/main/java/org/mozilla/fenix/sync/SyncedTabsViewHolder.kt b/app/src/main/java/org/mozilla/fenix/sync/SyncedTabsViewHolder.kt
index a48d6d264..375cec1e6 100644
--- a/app/src/main/java/org/mozilla/fenix/sync/SyncedTabsViewHolder.kt
+++ b/app/src/main/java/org/mozilla/fenix/sync/SyncedTabsViewHolder.kt
@@ -19,6 +19,7 @@ import mozilla.components.concept.sync.DeviceType
import mozilla.components.feature.syncedtabs.view.SyncedTabsView
import org.mozilla.fenix.NavGraphDirections
import org.mozilla.fenix.R
+import org.mozilla.fenix.ext.navigateBlockingForAsyncNavGraph
import org.mozilla.fenix.sync.SyncedTabsAdapter.AdapterItem
/**
@@ -63,7 +64,7 @@ sealed class SyncedTabsViewHolder(itemView: View) : RecyclerView.ViewHolder(item
errorItem.navController?.let { navController ->
itemView.sync_tabs_error_cta_button.visibility = VISIBLE
itemView.sync_tabs_error_cta_button.setOnClickListener {
- navController.navigate(NavGraphDirections.actionGlobalTurnOnSync())
+ navController.navigateBlockingForAsyncNavGraph(NavGraphDirections.actionGlobalTurnOnSync())
}
}
}
diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/CloseOnLastTabBinding.kt b/app/src/main/java/org/mozilla/fenix/tabstray/CloseOnLastTabBinding.kt
new file mode 100644
index 000000000..d19958375
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/tabstray/CloseOnLastTabBinding.kt
@@ -0,0 +1,50 @@
+/* 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.tabstray
+
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.drop
+import kotlinx.coroutines.flow.map
+import mozilla.components.browser.state.selector.normalTabs
+import mozilla.components.browser.state.selector.privateTabs
+import mozilla.components.browser.state.state.BrowserState
+import mozilla.components.browser.state.store.BrowserStore
+import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged
+import org.mozilla.fenix.components.AbstractBinding
+
+/**
+ * A binding that closes the tabs tray when the last tab is closed.
+ */
+class CloseOnLastTabBinding(
+ browserStore: BrowserStore,
+ private val tabsTrayStore: TabsTrayStore,
+ private val navigationInteractor: NavigationInteractor
+) : AbstractBinding(browserStore) {
+ override suspend fun onState(flow: Flow) {
+ flow.map { it }
+ // Ignore the initial state; we don't want to close immediately.
+ .drop(1)
+ .ifChanged { it.tabs }
+ .collect { state ->
+ val selectedPage = tabsTrayStore.state.selectedPage
+ val tabs = when (selectedPage) {
+ Page.NormalTabs -> {
+ state.normalTabs
+ }
+ Page.PrivateTabs -> {
+ state.privateTabs
+ }
+ else -> {
+ // Do nothing if we're on any other non-browser page.
+ null
+ }
+ }
+ if (tabs?.isEmpty() == true) {
+ navigationInteractor.onCloseAllTabsClicked(selectedPage == Page.PrivateTabs)
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/FloatingActionButtonBinding.kt b/app/src/main/java/org/mozilla/fenix/tabstray/FloatingActionButtonBinding.kt
new file mode 100644
index 000000000..42d925368
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/tabstray/FloatingActionButtonBinding.kt
@@ -0,0 +1,90 @@
+/* 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.tabstray
+
+import androidx.appcompat.content.res.AppCompatResources
+import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.map
+import mozilla.components.lib.state.ext.flowScoped
+import mozilla.components.support.base.feature.LifecycleAwareFeature
+import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifAnyChanged
+import org.mozilla.fenix.R
+import org.mozilla.fenix.tabstray.browser.BrowserTrayInteractor
+
+class FloatingActionButtonBinding(
+ private val store: TabsTrayStore,
+ private val actionButton: ExtendedFloatingActionButton,
+ private val browserTrayInteractor: BrowserTrayInteractor
+) : LifecycleAwareFeature {
+
+ private var scope: CoroutineScope? = null
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ override fun start() {
+ setFab(store.state.selectedPage, store.state.syncing)
+ scope = store.flowScoped { flow ->
+ flow.map { it }
+ .ifAnyChanged { state ->
+ arrayOf(
+ state.selectedPage,
+ state.syncing
+ )
+ }
+ .collect { state ->
+ setFab(state.selectedPage, state.syncing)
+ }
+ }
+ }
+
+ override fun stop() {
+ scope?.cancel()
+ }
+
+ private fun setFab(selectedPage: Page, syncing: Boolean) {
+ when (selectedPage) {
+ Page.NormalTabs -> {
+ actionButton.apply {
+ shrink()
+ show()
+ icon = AppCompatResources.getDrawable(context, R.drawable.ic_new)
+ setOnClickListener {
+ browserTrayInteractor.onFabClicked(false)
+ }
+ }
+ }
+ Page.PrivateTabs -> {
+ actionButton.apply {
+ text = context.getText(R.string.tab_drawer_fab_content)
+ extend()
+ show()
+ icon = AppCompatResources.getDrawable(context, R.drawable.ic_new)
+ setOnClickListener {
+ browserTrayInteractor.onFabClicked(true)
+ }
+ }
+ }
+ Page.SyncedTabs -> {
+ actionButton.apply {
+ text = if (syncing) context.getText(R.string.sync_syncing_in_progress)
+ else context.getText(R.string.tab_drawer_fab_sync)
+ extend()
+ show()
+ icon = AppCompatResources.getDrawable(context, R.drawable.ic_fab_sync)
+ setOnClickListener {
+ // Notify the store observers (one of which is the SyncedTabsFeature), that
+ // a sync was requested.
+ if (!syncing) {
+ store.dispatch(TabsTrayAction.SyncNow)
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/MenuIntegration.kt b/app/src/main/java/org/mozilla/fenix/tabstray/MenuIntegration.kt
new file mode 100644
index 000000000..2a1630204
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/tabstray/MenuIntegration.kt
@@ -0,0 +1,59 @@
+/* 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.tabstray
+
+import android.content.Context
+import androidx.annotation.VisibleForTesting
+import com.google.android.material.tabs.TabLayout
+import mozilla.components.browser.menu.BrowserMenuBuilder
+import mozilla.components.browser.state.store.BrowserStore
+import org.mozilla.fenix.utils.Do
+
+/**
+ * A wrapper class that building the tabs tray menu that handles item clicks.
+ */
+class MenuIntegration(
+ private val context: Context,
+ private val browserStore: BrowserStore,
+ private val tabsTrayStore: TabsTrayStore,
+ private val tabLayout: TabLayout,
+ private val navigationInteractor: NavigationInteractor
+) {
+ private val tabsTrayItemMenu by lazy {
+ TabsTrayMenu(
+ context = context,
+ browserStore = browserStore,
+ tabLayout = tabLayout,
+ onItemTapped = ::handleMenuClicked
+ )
+ }
+
+ private val isPrivateMode: Boolean
+ get() = tabsTrayStore.state.selectedPage == Page.PrivateTabs
+
+ /**
+ * Builds the internal menu items list. See [BrowserMenuBuilder.build].
+ */
+ fun build() = tabsTrayItemMenu.menuBuilder.build(context)
+
+ @VisibleForTesting
+ internal fun handleMenuClicked(item: TabsTrayMenu.Item) {
+ Do exhaustive when (item) {
+ is TabsTrayMenu.Item.ShareAllTabs ->
+ navigationInteractor.onShareTabsOfTypeClicked(isPrivateMode)
+ is TabsTrayMenu.Item.OpenAccountSettings ->
+ navigationInteractor.onAccountSettingsClicked()
+ is TabsTrayMenu.Item.OpenTabSettings ->
+ navigationInteractor.onTabSettingsClicked()
+ is TabsTrayMenu.Item.CloseAllTabs ->
+ navigationInteractor.onCloseAllTabsClicked(isPrivateMode)
+ is TabsTrayMenu.Item.OpenRecentlyClosed ->
+ navigationInteractor.onOpenRecentlyClosedClicked()
+ is TabsTrayMenu.Item.SelectTabs -> {
+ tabsTrayStore.dispatch(TabsTrayAction.EnterSelectMode)
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/NavigationInteractor.kt b/app/src/main/java/org/mozilla/fenix/tabstray/NavigationInteractor.kt
new file mode 100644
index 000000000..c88c42d94
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/tabstray/NavigationInteractor.kt
@@ -0,0 +1,214 @@
+/* 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.tabstray
+
+import android.content.Context
+import androidx.navigation.NavController
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+import mozilla.components.browser.state.selector.getNormalOrPrivateTabs
+import mozilla.components.browser.state.selector.normalTabs
+import mozilla.components.browser.state.store.BrowserStore
+import mozilla.components.browser.storage.sync.Tab as SyncTab
+import mozilla.components.concept.engine.prompt.ShareData
+import mozilla.components.concept.tabstray.Tab
+import mozilla.components.service.fxa.manager.FxaAccountManager
+import org.mozilla.fenix.BrowserDirection
+import org.mozilla.fenix.HomeActivity
+import org.mozilla.fenix.collections.CollectionsDialog
+import org.mozilla.fenix.collections.show
+import org.mozilla.fenix.components.TabCollectionStorage
+import org.mozilla.fenix.components.bookmarks.BookmarksUseCase
+import org.mozilla.fenix.components.metrics.Event
+import org.mozilla.fenix.components.metrics.MetricController
+import org.mozilla.fenix.ext.navigateBlockingForAsyncNavGraph
+import org.mozilla.fenix.home.HomeFragment
+import org.mozilla.fenix.tabstray.ext.getTabSessionState
+
+/**
+ * An interactor that helps with navigating to different parts of the app from the tabs tray.
+ */
+interface NavigationInteractor {
+
+ /**
+ * Called when tab tray should be dismissed.
+ */
+ fun onTabTrayDismissed()
+
+ /**
+ * Called when clicking the account settings button.
+ */
+ fun onAccountSettingsClicked()
+
+ /**
+ * Called when sharing a list of [Tab]s.
+ */
+ fun onShareTabs(tabs: Collection)
+
+ /**
+ * Called when clicking the share tabs button.
+ */
+ fun onShareTabsOfTypeClicked(private: Boolean)
+
+ /**
+ * Called when clicking the tab settings button.
+ */
+ fun onTabSettingsClicked()
+
+ /**
+ * Called when clicking the close all tabs button.
+ */
+ fun onCloseAllTabsClicked(private: Boolean)
+
+ /**
+ * Called when opening the recently closed tabs menu button.
+ */
+ fun onOpenRecentlyClosedClicked()
+
+ /**
+ * Used when opening the add-to-collections user flow.
+ */
+ fun onSaveToCollections(tabs: Collection)
+
+ /**
+ * Used when adding [Tab]s as bookmarks.
+ */
+ fun onSaveToBookmarks(tabs: Collection)
+
+ /**
+ * Called when clicking on a SyncedTab item.
+ */
+ fun onSyncedTabClicked(tab: SyncTab)
+}
+
+/**
+ * A default implementation of [NavigationInteractor].
+ */
+@Suppress("LongParameterList")
+class DefaultNavigationInteractor(
+ private val context: Context,
+ private val activity: HomeActivity,
+ private val browserStore: BrowserStore,
+ private val navController: NavController,
+ private val metrics: MetricController,
+ private val dismissTabTray: () -> Unit,
+ private val dismissTabTrayAndNavigateHome: (String) -> Unit,
+ private val bookmarksUseCase: BookmarksUseCase,
+ private val tabsTrayStore: TabsTrayStore,
+ private val collectionStorage: TabCollectionStorage,
+ private val accountManager: FxaAccountManager
+) : NavigationInteractor {
+
+ override fun onTabTrayDismissed() {
+ dismissTabTray()
+ }
+
+ override fun onAccountSettingsClicked() {
+ val isSignedIn = accountManager.authenticatedAccount() != null
+
+ val direction = if (isSignedIn) {
+ TabsTrayFragmentDirections.actionGlobalAccountSettingsFragment()
+ } else {
+ TabsTrayFragmentDirections.actionGlobalTurnOnSync()
+ }
+ navController.navigateBlockingForAsyncNavGraph(direction)
+ }
+
+ override fun onTabSettingsClicked() {
+ navController.navigateBlockingForAsyncNavGraph(
+ TabsTrayFragmentDirections.actionGlobalTabSettingsFragment()
+ )
+ }
+
+ override fun onOpenRecentlyClosedClicked() {
+ navController.navigateBlockingForAsyncNavGraph(
+ TabsTrayFragmentDirections.actionGlobalRecentlyClosed()
+ )
+ metrics.track(Event.RecentlyClosedTabsOpened)
+ }
+
+ override fun onShareTabs(tabs: Collection) {
+ val data = tabs.map {
+ ShareData(url = it.url, title = it.title)
+ }
+ val directions = TabsTrayFragmentDirections.actionGlobalShareFragment(
+ data = data.toTypedArray()
+ )
+ navController.navigateBlockingForAsyncNavGraph(directions)
+ }
+
+ override fun onShareTabsOfTypeClicked(private: Boolean) {
+ val tabs = browserStore.state.getNormalOrPrivateTabs(private)
+ val data = tabs.map {
+ ShareData(url = it.content.url, title = it.content.title)
+ }
+ val directions = TabsTrayFragmentDirections.actionGlobalShareFragment(
+ data = data.toTypedArray()
+ )
+ navController.navigateBlockingForAsyncNavGraph(directions)
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ override fun onCloseAllTabsClicked(private: Boolean) {
+ val sessionsToClose = if (private) {
+ HomeFragment.ALL_PRIVATE_TABS
+ } else {
+ HomeFragment.ALL_NORMAL_TABS
+ }
+
+ dismissTabTrayAndNavigateHome(sessionsToClose)
+ }
+
+ override fun onSaveToCollections(tabs: Collection) {
+ metrics.track(Event.TabsTraySaveToCollectionPressed)
+
+ CollectionsDialog(
+ storage = collectionStorage,
+ onPositiveButtonClick = { existingCollection ->
+ tabsTrayStore.dispatch(TabsTrayAction.ExitSelectMode)
+
+ // If collection is null, a new one was created.
+ val event = if (existingCollection == null) {
+ Event.CollectionSaved(browserStore.state.normalTabs.size, tabs.size)
+ } else {
+ Event.CollectionTabsAdded(browserStore.state.normalTabs.size, tabs.size)
+ }
+ metrics.track(event)
+
+ browserStore.getTabSessionState(tabs)
+ },
+ onNegativeButtonClick = {
+ tabsTrayStore.dispatch(TabsTrayAction.ExitSelectMode)
+ }
+ ).show(context)
+ }
+
+ override fun onSaveToBookmarks(tabs: Collection) {
+ tabs.forEach { tab ->
+ // We don't combine the context with lifecycleScope so that our jobs are not cancelled
+ // if we leave the fragment, i.e. we still want the bookmarks to be added.
+ CoroutineScope(Dispatchers.IO).launch {
+ bookmarksUseCase.addBookmark(tab.url, tab.title)
+ }
+ }
+
+ tabsTrayStore.dispatch(TabsTrayAction.ExitSelectMode)
+
+ // TODO show successful snackbar here (regardless of operation success).
+ }
+
+ override fun onSyncedTabClicked(tab: SyncTab) {
+ metrics.track(Event.SyncedTabOpened)
+
+ dismissTabTray()
+ activity.openToBrowserAndLoad(
+ searchTermOrURL = tab.active().url,
+ newTab = true,
+ from = BrowserDirection.FromTabTray
+ )
+ }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/TabCounterBinding.kt b/app/src/main/java/org/mozilla/fenix/tabstray/TabCounterBinding.kt
new file mode 100644
index 000000000..e6b833c7d
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/tabstray/TabCounterBinding.kt
@@ -0,0 +1,44 @@
+/* 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.tabstray
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.map
+import mozilla.components.browser.state.selector.normalTabs
+import mozilla.components.browser.state.state.BrowserState
+import mozilla.components.browser.state.store.BrowserStore
+import mozilla.components.lib.state.ext.flowScoped
+import mozilla.components.support.base.feature.LifecycleAwareFeature
+import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged
+import mozilla.components.ui.tabcounter.TabCounter
+
+/**
+ * Updates the tab counter to the size of [BrowserState.normalTabs].
+ */
+class TabCounterBinding(
+ private val store: BrowserStore,
+ private val counter: TabCounter
+) : LifecycleAwareFeature {
+
+ private var scope: CoroutineScope? = null
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ override fun start() {
+ scope = store.flowScoped { flow ->
+ flow.map { it.normalTabs }
+ .ifChanged()
+ .collect {
+ counter.setCount(it.size)
+ }
+ }
+ }
+
+ override fun stop() {
+ scope?.cancel()
+ }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/TabLayoutMediator.kt b/app/src/main/java/org/mozilla/fenix/tabstray/TabLayoutMediator.kt
new file mode 100644
index 000000000..8e27666f7
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/tabstray/TabLayoutMediator.kt
@@ -0,0 +1,92 @@
+/* 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.tabstray
+
+import androidx.annotation.VisibleForTesting
+import com.google.android.material.tabs.TabLayout
+import mozilla.components.browser.state.store.BrowserStore
+import mozilla.components.support.base.feature.LifecycleAwareFeature
+import org.mozilla.fenix.browser.browsingmode.BrowsingModeManager
+import org.mozilla.fenix.components.metrics.Event
+import org.mozilla.fenix.components.metrics.MetricController
+import org.mozilla.fenix.tabstray.TrayPagerAdapter.Companion.POSITION_NORMAL_TABS
+import org.mozilla.fenix.tabstray.TrayPagerAdapter.Companion.POSITION_PRIVATE_TABS
+import org.mozilla.fenix.utils.Do
+
+/**
+ * Selected the selected pager depending on the [BrowserStore] state and synchronizes user actions
+ * with the pager position.
+ */
+class TabLayoutMediator(
+ private val tabLayout: TabLayout,
+ interactor: TabsTrayInteractor,
+ private val browsingModeManager: BrowsingModeManager,
+ private val tabsTrayStore: TabsTrayStore,
+ metrics: MetricController
+) : LifecycleAwareFeature {
+
+ private val observer = TabLayoutObserver(interactor, metrics)
+
+ /**
+ * Start observing the [TabLayout] and select the current tab for initial state.
+ */
+ override fun start() {
+ tabLayout.addOnTabSelectedListener(observer)
+
+ selectActivePage()
+ }
+
+ override fun stop() {
+ tabLayout.removeOnTabSelectedListener(observer)
+ }
+
+ @VisibleForTesting
+ internal fun selectActivePage() {
+ val selectedPagerPosition =
+ when (browsingModeManager.mode.isPrivate) {
+ true -> POSITION_PRIVATE_TABS
+ false -> POSITION_NORMAL_TABS
+ }
+
+ selectTabAtPosition(selectedPagerPosition)
+ }
+
+ fun selectTabAtPosition(position: Int) {
+ tabLayout.getTabAt(position)?.select()
+ tabsTrayStore.dispatch(TabsTrayAction.PageSelected(Page.positionToPage(position)))
+ }
+}
+
+/**
+ * An observer for the [TabLayout] used for the Tabs Tray.
+ */
+internal class TabLayoutObserver(
+ private val interactor: TabsTrayInteractor,
+ private val metrics: MetricController
+) : TabLayout.OnTabSelectedListener {
+
+ private var initialScroll = true
+
+ override fun onTabSelected(tab: TabLayout.Tab) {
+ // Do not animate the initial scroll when opening the tabs tray.
+ val animate = if (initialScroll) {
+ initialScroll = false
+ false
+ } else {
+ true
+ }
+
+ interactor.setCurrentTrayPosition(tab.position, animate)
+
+ Do exhaustive when (Page.positionToPage(tab.position)) {
+ Page.NormalTabs -> metrics.track(Event.TabsTrayNormalModeTapped)
+ Page.PrivateTabs -> metrics.track(Event.TabsTrayPrivateModeTapped)
+ Page.SyncedTabs -> metrics.track(Event.TabsTraySyncedModeTapped)
+ }
+ }
+
+ override fun onTabUnselected(tab: TabLayout.Tab) = Unit
+ override fun onTabReselected(tab: TabLayout.Tab) = Unit
+}
diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayController.kt b/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayController.kt
new file mode 100644
index 000000000..0999ad47e
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayController.kt
@@ -0,0 +1,59 @@
+/* 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.tabstray
+
+import androidx.navigation.NavController
+import kotlinx.coroutines.CoroutineScope
+import mozilla.components.concept.base.profiler.Profiler
+import mozilla.components.service.fxa.manager.FxaAccountManager
+import org.mozilla.fenix.browser.browsingmode.BrowsingMode
+import org.mozilla.fenix.browser.browsingmode.BrowsingModeManager
+import org.mozilla.fenix.components.metrics.Event
+import org.mozilla.fenix.components.metrics.MetricController
+import org.mozilla.fenix.ext.navigateBlockingForAsyncNavGraph
+import org.mozilla.fenix.tabtray.TabTrayDialogFragmentDirections
+
+interface TabsTrayController {
+
+ /**
+ * Called when user clicks the new tab button.
+ */
+ fun onNewTabTapped(isPrivate: Boolean)
+}
+
+class DefaultTabsTrayController(
+ private val store: TabsTrayStore,
+ private val browsingModeManager: BrowsingModeManager,
+ private val navController: NavController,
+ private val profiler: Profiler?,
+ private val navigationInteractor: NavigationInteractor,
+ private val metrics: MetricController,
+ private val ioScope: CoroutineScope,
+ private val accountManager: FxaAccountManager
+) : TabsTrayController {
+
+ override fun onNewTabTapped(isPrivate: Boolean) {
+ val startTime = profiler?.getProfilerTime()
+ browsingModeManager.mode = BrowsingMode.fromBoolean(isPrivate)
+ navController.navigateBlockingForAsyncNavGraph(
+ TabTrayDialogFragmentDirections.actionGlobalHome(focusOnAddressBar = true))
+ navigationInteractor.onTabTrayDismissed()
+ profiler?.addMarker(
+ "DefaultTabTrayController.onNewTabTapped",
+ startTime
+ )
+ sendNewTabEvent(isPrivate)
+ }
+
+ private fun sendNewTabEvent(isPrivateModeSelected: Boolean) {
+ val eventToSend = if (isPrivateModeSelected) {
+ Event.NewPrivateTabTapped
+ } else {
+ Event.NewTabTapped
+ }
+
+ metrics.track(eventToSend)
+ }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayDialog.kt b/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayDialog.kt
new file mode 100644
index 000000000..18e9af8f3
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayDialog.kt
@@ -0,0 +1,26 @@
+/* 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.tabstray
+
+import android.app.Dialog
+import android.content.Context
+import org.mozilla.fenix.tabstray.browser.BrowserTrayInteractor
+
+/**
+ * Default tabs tray dialog implementation for overriding the default on back pressed.
+ */
+class TabsTrayDialog(
+ context: Context,
+ theme: Int,
+ private val interactor: () -> BrowserTrayInteractor
+) : Dialog(context, theme) {
+ override fun onBackPressed() {
+ if (interactor.invoke().onBackPressed()) {
+ return
+ }
+
+ dismiss()
+ }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayFragment.kt b/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayFragment.kt
index b0a0fc12a..bae954a3b 100644
--- a/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayFragment.kt
+++ b/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayFragment.kt
@@ -5,28 +5,80 @@
package org.mozilla.fenix.tabstray
import android.content.Context
+import android.content.res.Configuration
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatDialogFragment
import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.fragment.app.activityViewModels
+import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import com.google.android.material.bottomsheet.BottomSheetBehavior
-import com.google.android.material.tabs.TabLayout
+import kotlinx.android.synthetic.main.component_tabstray.view.*
import kotlinx.android.synthetic.main.component_tabstray2.*
import kotlinx.android.synthetic.main.component_tabstray2.view.*
+import kotlinx.android.synthetic.main.component_tabstray2.view.tab_tray_overflow
+import kotlinx.android.synthetic.main.component_tabstray2.view.tab_wrapper
+import kotlinx.android.synthetic.main.component_tabstray_fab.*
+import kotlinx.android.synthetic.main.fragment_tab_tray_dialog.*
+import kotlinx.android.synthetic.main.tabs_tray_tab_counter2.*
+import kotlinx.android.synthetic.main.tabstray_multiselect_items.*
+import kotlinx.android.synthetic.main.tabstray_multiselect_items.view.*
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.plus
+import mozilla.components.browser.state.selector.findTab
+import mozilla.components.browser.state.selector.getNormalOrPrivateTabs
+import mozilla.components.browser.state.selector.normalTabs
+import mozilla.components.browser.state.selector.privateTabs
+import mozilla.components.browser.state.state.TabSessionState
+import mozilla.components.concept.tabstray.Tab
+import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
+import org.mozilla.fenix.HomeActivity
+import org.mozilla.fenix.NavGraphDirections
import org.mozilla.fenix.R
+import org.mozilla.fenix.components.StoreProvider
+import org.mozilla.fenix.components.metrics.Event
+import org.mozilla.fenix.ext.components
+import org.mozilla.fenix.ext.navigateBlockingForAsyncNavGraph
+import org.mozilla.fenix.ext.requireComponents
+import org.mozilla.fenix.ext.settings
+import org.mozilla.fenix.home.HomeScreenViewModel
+import org.mozilla.fenix.tabstray.browser.BrowserTrayInteractor
+import org.mozilla.fenix.tabstray.browser.DefaultBrowserTrayInteractor
+import org.mozilla.fenix.tabstray.browser.SelectionHandleBinding
+import org.mozilla.fenix.tabstray.browser.SelectionBannerBinding
+import org.mozilla.fenix.tabstray.browser.SelectionBannerBinding.VisibilityModifier
+import org.mozilla.fenix.tabstray.ext.getTrayPosition
+import org.mozilla.fenix.tabstray.ext.showWithTheme
+import org.mozilla.fenix.utils.allowUndo
+import kotlin.math.max
+@Suppress("TooManyFunctions", "LargeClass")
class TabsTrayFragment : AppCompatDialogFragment(), TabsTrayInteractor {
- lateinit var behavior: BottomSheetBehavior
+ private var fabView: View? = null
+ private lateinit var tabsTrayStore: TabsTrayStore
+ private lateinit var browserTrayInteractor: BrowserTrayInteractor
+ private lateinit var tabsTrayController: DefaultTabsTrayController
+ private lateinit var behavior: BottomSheetBehavior
+
+ private val tabLayoutMediator = ViewBoundFeatureWrapper()
+ private val tabCounterBinding = ViewBoundFeatureWrapper()
+ private val floatingActionButtonBinding = ViewBoundFeatureWrapper()
+ private val selectionBannerBinding = ViewBoundFeatureWrapper()
+ private val selectionHandleBinding = ViewBoundFeatureWrapper()
+ private val tabsTrayCtaBinding = ViewBoundFeatureWrapper()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NO_TITLE, R.style.TabTrayDialogStyle)
}
+ override fun onCreateDialog(savedInstanceState: Bundle?) =
+ TabsTrayDialog(requireContext(), theme) { browserTrayInteractor }
+
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@@ -38,21 +90,171 @@ class TabsTrayFragment : AppCompatDialogFragment(), TabsTrayInteractor {
behavior = BottomSheetBehavior.from(view.tab_wrapper)
+ tabsTrayStore = StoreProvider.get(this) { TabsTrayStore() }
+
+ fabView = LayoutInflater.from(containerView.context)
+ .inflate(R.layout.component_tabstray_fab, containerView, true)
+
return containerView
}
+ @Suppress("LongMethod")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
+ val activity = activity as HomeActivity
+
+ requireComponents.analytics.metrics.track(Event.TabsTrayOpened)
+
+ val navigationInteractor =
+ DefaultNavigationInteractor(
+ context = requireContext(),
+ activity = activity,
+ tabsTrayStore = tabsTrayStore,
+ browserStore = requireComponents.core.store,
+ navController = findNavController(),
+ metrics = requireComponents.analytics.metrics,
+ dismissTabTray = ::dismissTabsTray,
+ dismissTabTrayAndNavigateHome = ::dismissTabsTrayAndNavigateHome,
+ bookmarksUseCase = requireComponents.useCases.bookmarksUseCases,
+ collectionStorage = requireComponents.core.tabCollectionStorage,
+ accountManager = requireComponents.backgroundServices.accountManager
+ )
+
+ tabsTrayController = DefaultTabsTrayController(
+ store = tabsTrayStore,
+ browsingModeManager = activity.browsingModeManager,
+ navController = findNavController(),
+ navigationInteractor = navigationInteractor,
+ profiler = requireComponents.core.engine.profiler,
+ accountManager = requireComponents.backgroundServices.accountManager,
+ metrics = requireComponents.analytics.metrics,
+ ioScope = lifecycleScope + Dispatchers.IO
+ )
+
+ browserTrayInteractor = DefaultBrowserTrayInteractor(
+ tabsTrayStore,
+ this@TabsTrayFragment,
+ tabsTrayController,
+ requireComponents.useCases.tabsUseCases.selectTab,
+ requireComponents.settings,
+ requireComponents.analytics.metrics
+ )
+
+ setupMenu(view, navigationInteractor)
+ setupPager(
+ view.context,
+ tabsTrayStore,
+ this,
+ browserTrayInteractor,
+ navigationInteractor
+ )
+
+ setupBackgroundDismissalListener {
+ requireComponents.analytics.metrics.track(Event.TabsTrayClosed)
+ dismissAllowingStateLoss()
+ }
- setupPager(view.context, this)
+ behavior.setUpTrayBehavior(
+ isLandscape = requireContext().resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE,
+ maxNumberOfTabs = max(
+ requireContext().components.core.store.state.normalTabs.size,
+ requireContext().components.core.store.state.privateTabs.size
+ ),
+ numberForExpandingTray = if (requireContext().settings().gridTabView) {
+ EXPAND_AT_GRID_SIZE
+ } else {
+ EXPAND_AT_LIST_SIZE
+ },
+ navigationInteractor = navigationInteractor
+ )
+
+ tabsTrayCtaBinding.set(
+ feature = TabsTrayInfoBannerBinding(
+ context = view.context,
+ store = requireComponents.core.store,
+ infoBannerView = view.info_banner,
+ settings = requireComponents.settings,
+ navigationInteractor = navigationInteractor,
+ metrics = requireComponents.analytics.metrics
+ ),
+ owner = this,
+ view = view
+ )
+
+ tabLayoutMediator.set(
+ feature = TabLayoutMediator(
+ tabLayout = tab_layout,
+ interactor = this,
+ browsingModeManager = activity.browsingModeManager,
+ tabsTrayStore = tabsTrayStore,
+ metrics = requireComponents.analytics.metrics
+ ), owner = this,
+ view = view
+ )
+
+ tabCounterBinding.set(
+ feature = TabCounterBinding(
+ store = requireComponents.core.store,
+ counter = tab_counter
+ ),
+ owner = this,
+ view = view
+ )
+
+ floatingActionButtonBinding.set(
+ feature = FloatingActionButtonBinding(
+ store = tabsTrayStore,
+ actionButton = new_tab_button,
+ browserTrayInteractor = browserTrayInteractor
+ ),
+ owner = this,
+ view = view
+ )
+
+ selectionBannerBinding.set(
+ feature = SelectionBannerBinding(
+ context = requireContext(),
+ store = tabsTrayStore,
+ navInteractor = navigationInteractor,
+ tabsTrayInteractor = this,
+ containerView = view,
+ backgroundView = topBar,
+ showOnSelectViews = VisibilityModifier(
+ collect_multi_select,
+ share_multi_select,
+ menu_multi_select,
+ multiselect_title,
+ exit_multi_select
+ ),
+ showOnNormalViews = VisibilityModifier(
+ tab_layout,
+ tab_tray_overflow,
+ new_tab_button
+ )
+ ),
+ owner = this,
+ view = view
+ )
+
+ selectionHandleBinding.set(
+ feature = SelectionHandleBinding(
+ store = tabsTrayStore,
+ handle = handle,
+ containerLayout = tab_wrapper
+ ),
+ owner = this,
+ view = view
+ )
}
- override fun setCurrentTrayPosition(position: Int) {
- tabsTray.currentItem = position
+ override fun setCurrentTrayPosition(position: Int, smoothScroll: Boolean) {
+ tabsTray.setCurrentItem(position, smoothScroll)
+ tab_layout.getTabAt(position)?.select()
+ tabsTrayStore.dispatch(TabsTrayAction.PageSelected(Page.positionToPage(position)))
}
override fun navigateToBrowser() {
- dismissAllowingStateLoss()
+ dismissTabsTray()
val navController = findNavController()
@@ -61,36 +263,115 @@ class TabsTrayFragment : AppCompatDialogFragment(), TabsTrayInteractor {
}
if (!navController.popBackStack(R.id.browserFragment, false)) {
- navController.navigate(R.id.browserFragment)
+ navController.navigateBlockingForAsyncNavGraph(R.id.browserFragment)
}
}
- override fun tabRemoved(sessionId: String) {
- // TODO re-implement these methods
- // showUndoSnackbarForTab(sessionId)
- // removeIfNotLastTab(sessionId)
+ override fun onDeleteTab(tabId: String) {
+ val browserStore = requireComponents.core.store
+ val tab = browserStore.state.findTab(tabId)
+
+ tab?.let {
+ if (browserStore.state.getNormalOrPrivateTabs(it.content.private).size != 1) {
+ requireComponents.useCases.tabsUseCases.removeTab(tabId)
+ showUndoSnackbarForTab(it)
+ } else {
+ dismissTabsTrayAndNavigateHome(tabId)
+ }
+ }
}
- private fun setupPager(context: Context, interactor: TabsTrayInteractor) {
+ override fun onDeleteTabs(tabs: Collection) {
+ tabs.forEach {
+ onDeleteTab(it.id)
+ }
+ }
+
+ private fun showUndoSnackbarForTab(removedTab: TabSessionState) {
+ val snackbarMessage =
+ when (removedTab.content.private) {
+ true -> getString(R.string.snackbar_private_tab_closed)
+ false -> getString(R.string.snackbar_tab_closed)
+ }
+
+ lifecycleScope.allowUndo(
+ requireView(),
+ snackbarMessage,
+ getString(R.string.snackbar_deleted_undo),
+ {
+ requireComponents.useCases.tabsUseCases.undo.invoke()
+ tabLayoutMediator.withFeature { it.selectTabAtPosition(removedTab.getTrayPosition()) }
+ },
+ operation = { },
+ elevation = ELEVATION,
+ anchorView = new_tab_button
+ )
+ }
+
+ private fun setupPager(
+ context: Context,
+ store: TabsTrayStore,
+ trayInteractor: TabsTrayInteractor,
+ browserInteractor: BrowserTrayInteractor,
+ navigationInteractor: NavigationInteractor
+ ) {
tabsTray.apply {
- adapter = TrayPagerAdapter(context, interactor)
+ adapter = TrayPagerAdapter(
+ context,
+ store,
+ browserInteractor,
+ navigationInteractor,
+ trayInteractor,
+ requireComponents.core.store
+ )
isUserInputEnabled = false
}
+ }
+
+ private fun setupMenu(view: View, navigationInteractor: NavigationInteractor) {
+ view.tab_tray_overflow.setOnClickListener { anchor ->
+
+ requireComponents.analytics.metrics.track(Event.TabsTrayMenuOpened)
+
+ val menu = MenuIntegration(
+ context = requireContext(),
+ browserStore = requireComponents.core.store,
+ tabsTrayStore = tabsTrayStore,
+ tabLayout = tab_layout,
+ navigationInteractor = navigationInteractor
+ ).build()
+
+ menu.showWithTheme(anchor)
+ }
+ }
- tab_layout.addOnTabSelectedListener(TabLayoutObserver(interactor))
+ private fun setupBackgroundDismissalListener(block: (View) -> Unit) {
+ tabLayout.setOnClickListener(block)
+ handle.setOnClickListener(block)
+ }
+
+ private val homeViewModel: HomeScreenViewModel by activityViewModels()
+
+ private fun dismissTabsTrayAndNavigateHome(sessionId: String) {
+ homeViewModel.sessionToDelete = sessionId
+ val directions = NavGraphDirections.actionGlobalHome()
+ findNavController().navigateBlockingForAsyncNavGraph(directions)
+ dismissTabsTray()
}
-}
-/**
- * An observer for the [TabLayout] used for the Tabs Tray.
- */
-internal class TabLayoutObserver(
- private val interactor: TabsTrayInteractor
-) : TabLayout.OnTabSelectedListener {
- override fun onTabSelected(tab: TabLayout.Tab) {
- interactor.setCurrentTrayPosition(tab.position)
+ private fun dismissTabsTray() {
+ dismissAllowingStateLoss()
+ requireComponents.analytics.metrics.track(Event.TabsTrayClosed)
}
- override fun onTabUnselected(tab: TabLayout.Tab) = Unit
- override fun onTabReselected(tab: TabLayout.Tab) = Unit
+ companion object {
+ // Minimum number of list items for which to show the tabs tray as expanded.
+ const val EXPAND_AT_LIST_SIZE = 4
+
+ // Minimum number of grid items for which to show the tabs tray as expanded.
+ private const val EXPAND_AT_GRID_SIZE = 3
+
+ // Elevation for undo toasts
+ private const val ELEVATION = 80f
+ }
}
diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayInfoBannerBinding.kt b/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayInfoBannerBinding.kt
new file mode 100644
index 000000000..a1251f37e
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayInfoBannerBinding.kt
@@ -0,0 +1,126 @@
+/* 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.tabstray
+
+import android.content.Context
+import android.view.View.VISIBLE
+import android.view.ViewGroup
+import androidx.annotation.VisibleForTesting
+import kotlin.math.max
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.map
+import mozilla.components.browser.state.selector.normalTabs
+import mozilla.components.browser.state.selector.privateTabs
+import mozilla.components.browser.state.store.BrowserStore
+import mozilla.components.lib.state.ext.flowScoped
+import mozilla.components.support.base.feature.LifecycleAwareFeature
+import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged
+import org.mozilla.fenix.R
+import org.mozilla.fenix.browser.infobanner.InfoBanner
+import org.mozilla.fenix.components.metrics.Event
+import org.mozilla.fenix.components.metrics.MetricController
+import org.mozilla.fenix.utils.Settings
+
+class TabsTrayInfoBannerBinding(
+ private val context: Context,
+ private val store: BrowserStore,
+ private val infoBannerView: ViewGroup,
+ private val settings: Settings,
+ private val navigationInteractor: NavigationInteractor,
+ private val metrics: MetricController?
+) : LifecycleAwareFeature {
+ private var scope: CoroutineScope? = null
+
+ @VisibleForTesting
+ internal var banner: InfoBanner? = null
+
+ @ExperimentalCoroutinesApi
+ override fun start() {
+ scope = store.flowScoped { flow ->
+ flow.map { state -> max(state.normalTabs.size, state.privateTabs.size) }
+ .ifChanged()
+ .collect { tabCount ->
+ if (tabCount >= TAB_COUNT_SHOW_CFR) {
+ displayInfoBannerIfNeeded(settings)
+ }
+ }
+ }
+ }
+
+ override fun stop() {
+ scope?.cancel()
+ }
+
+ private fun displayInfoBannerIfNeeded(settings: Settings) {
+ banner = displayGridViewBannerIfNeeded(settings)
+ ?: displayAutoCloseTabsBannerIfNeeded(settings)
+
+ banner?.apply {
+ infoBannerView.visibility = VISIBLE
+ showBanner()
+ }
+ }
+
+ private fun displayGridViewBannerIfNeeded(settings: Settings): InfoBanner? {
+ return if (
+ settings.shouldShowGridViewBanner &&
+ settings.canShowCfr &&
+ settings.listTabView
+ ) {
+ InfoBanner(
+ context = context,
+ message = context.getString(R.string.tab_tray_grid_view_banner_message),
+ dismissText = context.getString(R.string.tab_tray_grid_view_banner_negative_button_text),
+ actionText = context.getString(R.string.tab_tray_grid_view_banner_positive_button_text),
+ container = infoBannerView,
+ dismissByHiding = true,
+ dismissAction = {
+ metrics?.track(Event.TabsTrayCfrDismissed)
+ settings.shouldShowGridViewBanner = false
+ }
+ ) {
+ navigationInteractor.onTabSettingsClicked()
+ metrics?.track(Event.TabsTrayCfrTapped)
+ settings.shouldShowGridViewBanner = false
+ }
+ } else {
+ null
+ }
+ }
+
+ private fun displayAutoCloseTabsBannerIfNeeded(settings: Settings): InfoBanner? {
+ return if (
+ settings.shouldShowAutoCloseTabsBanner &&
+ settings.canShowCfr
+ ) {
+ InfoBanner(
+ context = context,
+ message = context.getString(R.string.tab_tray_close_tabs_banner_message),
+ dismissText = context.getString(R.string.tab_tray_close_tabs_banner_negative_button_text),
+ actionText = context.getString(R.string.tab_tray_close_tabs_banner_positive_button_text),
+ container = infoBannerView,
+ dismissByHiding = true,
+ dismissAction = {
+ metrics?.track(Event.TabsTrayCfrDismissed)
+ settings.shouldShowAutoCloseTabsBanner = false
+ }
+ ) {
+ navigationInteractor.onTabSettingsClicked()
+ metrics?.track(Event.TabsTrayCfrTapped)
+ settings.shouldShowAutoCloseTabsBanner = false
+ }
+ } else {
+ null
+ }
+ }
+
+ companion object {
+ @VisibleForTesting
+ internal const val TAB_COUNT_SHOW_CFR = 6
+ }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayInteractor.kt b/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayInteractor.kt
index 6c479aad3..5c3a7371a 100644
--- a/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayInteractor.kt
+++ b/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayInteractor.kt
@@ -4,11 +4,16 @@
package org.mozilla.fenix.tabstray
+import mozilla.components.concept.tabstray.Tab
+
interface TabsTrayInteractor {
/**
* Set the current tray item to the clamped [position].
+ *
+ * @param position The position on the tray to focus.
+ * @param smoothScroll If true, animate the scrolling from the current tab to [position].
*/
- fun setCurrentTrayPosition(position: Int)
+ fun setCurrentTrayPosition(position: Int, smoothScroll: Boolean)
/**
* Dismisses the tabs tray and navigates to the browser.
@@ -16,7 +21,12 @@ interface TabsTrayInteractor {
fun navigateToBrowser()
/**
- * Invoked when a tab is removed from the tabs tray with the given [sessionId].
+ * Invoked when a tab is removed from the tabs tray with the given [tabId].
+ */
+ fun onDeleteTab(tabId: String)
+
+ /**
+ * Invoked when [Tab]s need to be deleted.
*/
- fun tabRemoved(sessionId: String)
+ fun onDeleteTabs(tabs: Collection)
}
diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayMenu.kt b/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayMenu.kt
new file mode 100644
index 000000000..0db845931
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayMenu.kt
@@ -0,0 +1,100 @@
+/* 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.tabstray
+
+import android.content.Context
+import com.google.android.material.tabs.TabLayout
+import mozilla.components.browser.menu.BrowserMenuBuilder
+import mozilla.components.browser.menu.item.SimpleBrowserMenuItem
+import mozilla.components.browser.state.selector.normalTabs
+import mozilla.components.browser.state.selector.privateTabs
+import mozilla.components.browser.state.store.BrowserStore
+import org.mozilla.fenix.R
+import org.mozilla.fenix.components.metrics.Event
+import org.mozilla.fenix.ext.components
+import org.mozilla.fenix.tabstray.ext.isNormalModeSelected
+import org.mozilla.fenix.tabstray.ext.isPrivateModeSelected
+import org.mozilla.fenix.tabstray.ext.isSyncedModeSelected
+
+class TabsTrayMenu(
+ private val context: Context,
+ browserStore: BrowserStore,
+ private val tabLayout: TabLayout,
+ private val onItemTapped: (Item) -> Unit = {}
+) {
+
+ private val checkOpenTabs =
+ when {
+ tabLayout.isNormalModeSelected() ->
+ browserStore.state.normalTabs.isNotEmpty()
+ tabLayout.isPrivateModeSelected() ->
+ browserStore.state.privateTabs.isNotEmpty()
+ else ->
+ false
+ }
+
+ private val shouldShowSelectOrShare = { tabLayout.isNormalModeSelected() && checkOpenTabs }
+ private val shouldShowTabSetting = { !tabLayout.isSyncedModeSelected() }
+ private val shouldShowAccountSetting = { tabLayout.isSyncedModeSelected() }
+
+ sealed class Item {
+ object ShareAllTabs : Item()
+ object OpenAccountSettings : Item()
+ object OpenTabSettings : Item()
+ object SelectTabs : Item()
+ object CloseAllTabs : Item()
+ object OpenRecentlyClosed : Item()
+ }
+
+ val menuBuilder by lazy { BrowserMenuBuilder(menuItems) }
+
+ private val menuItems by lazy {
+ listOf(
+ SimpleBrowserMenuItem(
+ context.getString(R.string.tabs_tray_select_tabs),
+ textColorResource = R.color.primary_text_normal_theme
+ ) {
+ onItemTapped.invoke(Item.SelectTabs)
+ }.apply { visible = shouldShowSelectOrShare },
+
+ SimpleBrowserMenuItem(
+ context.getString(R.string.tab_tray_menu_item_share),
+ textColorResource = R.color.primary_text_normal_theme
+ ) {
+ context.components.analytics.metrics.track(Event.TabsTrayShareAllTabsPressed)
+ onItemTapped.invoke(Item.ShareAllTabs)
+ }.apply { visible = shouldShowSelectOrShare },
+
+ SimpleBrowserMenuItem(
+ context.getString(R.string.tab_tray_menu_account_settings),
+ textColorResource = R.color.primary_text_normal_theme
+ ) {
+ onItemTapped.invoke(Item.OpenAccountSettings)
+ }.apply { visible = shouldShowAccountSetting },
+
+ SimpleBrowserMenuItem(
+ context.getString(R.string.tab_tray_menu_tab_settings),
+ textColorResource = R.color.primary_text_normal_theme
+ ) {
+ onItemTapped.invoke(Item.OpenTabSettings)
+ }.apply { visible = shouldShowTabSetting },
+
+ SimpleBrowserMenuItem(
+ context.getString(R.string.tab_tray_menu_recently_closed),
+ textColorResource = R.color.primary_text_normal_theme
+ ) {
+ onItemTapped.invoke(Item.OpenRecentlyClosed)
+ },
+
+ SimpleBrowserMenuItem(
+ context.getString(R.string.tab_tray_menu_item_close),
+ textColorResource = R.color.primary_text_normal_theme
+ ) {
+ context.components.analytics.metrics.track(Event.TabsTrayCloseAllTabsPressed)
+ onItemTapped.invoke(Item.CloseAllTabs)
+ }.apply { visible = { checkOpenTabs } }
+ )
+ }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayStore.kt b/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayStore.kt
new file mode 100644
index 000000000..28c9da2f2
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayStore.kt
@@ -0,0 +1,167 @@
+/* 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.tabstray
+
+import mozilla.components.concept.tabstray.Tab
+import mozilla.components.lib.state.Action
+import mozilla.components.lib.state.Middleware
+import mozilla.components.lib.state.State
+import mozilla.components.lib.state.Store
+
+/**
+ * Value type that represents the state of the tabs tray.
+ *
+ * @property selectedPage The current page in the tray can be on.
+ * @property mode Whether the browser tab list is in multi-select mode or not with the set of
+ * currently selected tabs.
+ * @property syncing Whether the Synced Tabs feature should fetch the latest tabs from paired
+ * devices.
+ */
+data class TabsTrayState(
+ val selectedPage: Page = Page.NormalTabs,
+ val mode: Mode = Mode.Normal,
+ val syncing: Boolean = false
+) : State {
+
+ /**
+ * The current mode that the tabs list is in.
+ */
+ sealed class Mode {
+
+ /**
+ * A set of selected tabs which we would want to perform an action on.
+ */
+ open val selectedTabs = emptySet()
+
+ /**
+ * The default mode the tabs list is in.
+ */
+ object Normal : Mode()
+
+ /**
+ * The multi-select mode that the tabs list is in containing the set of currently
+ * selected tabs.
+ */
+ data class Select(override val selectedTabs: Set) : Mode()
+ }
+}
+
+/**
+ * The different pagers in the tray that we can switch between in the [TrayPagerAdapter].
+ */
+enum class Page {
+
+ /**
+ * The pager position that displays normal tabs.
+ */
+ NormalTabs,
+
+ /**
+ * The pager position that displays private tabs.
+ */
+ PrivateTabs,
+
+ /**
+ * The pager position that displays Synced Tabs.
+ */
+ SyncedTabs;
+
+ companion object {
+ fun positionToPage(position: Int): Page {
+ return when (position) {
+ 0 -> NormalTabs
+ 1 -> PrivateTabs
+ else -> SyncedTabs
+ }
+ }
+ }
+}
+
+/**
+ * [Action] implementation related to [TabsTrayStore].
+ */
+sealed class TabsTrayAction : Action {
+
+ /**
+ * Entered multi-select mode.
+ */
+ object EnterSelectMode : TabsTrayAction()
+
+ /**
+ * Exited multi-select mode.
+ */
+ object ExitSelectMode : TabsTrayAction()
+
+ /**
+ * Added a new [Tab] to the selection set.
+ */
+ data class AddSelectTab(val tab: Tab) : TabsTrayAction()
+
+ /**
+ * Removed a [Tab] from the selection set.
+ */
+ data class RemoveSelectTab(val tab: Tab) : TabsTrayAction()
+
+ /**
+ * The active page in the tray that is now in focus.
+ */
+ data class PageSelected(val page: Page) : TabsTrayAction()
+
+ /**
+ * A request to perform a "sync" action.
+ */
+ object SyncNow : TabsTrayAction()
+
+ /**
+ * When a "sync" action has completed; this can be triggered immediately after [SyncNow] if
+ * no sync action was able to be performed.
+ */
+ object SyncCompleted : TabsTrayAction()
+}
+
+/**
+ * Reducer for [TabsTrayStore].
+ */
+internal object TabsTrayReducer {
+ fun reduce(state: TabsTrayState, action: TabsTrayAction): TabsTrayState {
+ return when (action) {
+ is TabsTrayAction.EnterSelectMode ->
+ state.copy(mode = TabsTrayState.Mode.Select(emptySet()))
+ is TabsTrayAction.ExitSelectMode ->
+ state.copy(mode = TabsTrayState.Mode.Normal)
+ is TabsTrayAction.AddSelectTab ->
+ state.copy(mode = TabsTrayState.Mode.Select(state.mode.selectedTabs + action.tab))
+ is TabsTrayAction.RemoveSelectTab -> {
+ val selected = state.mode.selectedTabs - action.tab
+ state.copy(
+ mode = if (selected.isEmpty()) {
+ TabsTrayState.Mode.Normal
+ } else {
+ TabsTrayState.Mode.Select(selected)
+ }
+ )
+ }
+ is TabsTrayAction.PageSelected ->
+ state.copy(selectedPage = action.page)
+ is TabsTrayAction.SyncNow ->
+ state.copy(syncing = true)
+ is TabsTrayAction.SyncCompleted ->
+ state.copy(syncing = false)
+ }
+ }
+}
+
+/**
+ * A [Store] that holds the [TabsTrayState] for the tabs tray and reduces [TabsTrayAction]s
+ * dispatched to the store.
+ */
+class TabsTrayStore(
+ initialState: TabsTrayState = TabsTrayState(),
+ middlewares: List> = emptyList()
+) : Store(
+ initialState,
+ TabsTrayReducer::reduce,
+ middlewares
+)
diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayViewHolder.kt b/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayViewHolder.kt
new file mode 100644
index 000000000..e0c443526
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayViewHolder.kt
@@ -0,0 +1,231 @@
+/* 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.tabstray
+
+import android.view.View
+import android.widget.ImageButton
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.annotation.VisibleForTesting
+import androidx.appcompat.content.res.AppCompatResources
+import androidx.appcompat.widget.AppCompatImageButton
+import androidx.core.view.isInvisible
+import androidx.core.view.isVisible
+import kotlinx.android.synthetic.main.checkbox_item.view.*
+import mozilla.components.browser.state.selector.findTabOrCustomTab
+import mozilla.components.browser.state.store.BrowserStore
+import mozilla.components.browser.tabstray.TabViewHolder
+import mozilla.components.browser.tabstray.TabsTrayStyling
+import mozilla.components.browser.tabstray.thumbnail.TabThumbnailView
+import mozilla.components.browser.toolbar.MAX_URI_LENGTH
+import mozilla.components.concept.base.images.ImageLoadRequest
+import mozilla.components.concept.base.images.ImageLoader
+import mozilla.components.concept.engine.mediasession.MediaSession
+import mozilla.components.concept.tabstray.Tab
+import mozilla.components.concept.tabstray.TabsTray
+import mozilla.components.support.base.observer.Observable
+import org.mozilla.fenix.R
+import org.mozilla.fenix.components.metrics.Event
+import org.mozilla.fenix.components.metrics.MetricController
+import org.mozilla.fenix.ext.components
+import org.mozilla.fenix.ext.increaseTapArea
+import org.mozilla.fenix.ext.removeAndDisable
+import org.mozilla.fenix.ext.removeTouchDelegate
+import org.mozilla.fenix.ext.showAndEnable
+import org.mozilla.fenix.ext.toShortUrl
+import org.mozilla.fenix.selection.SelectionHolder
+import org.mozilla.fenix.selection.SelectionInteractor
+import org.mozilla.fenix.tabstray.browser.BrowserTrayInteractor
+import org.mozilla.fenix.tabstray.ext.isSelect
+
+/**
+ * A RecyclerView ViewHolder implementation for "tab" items.
+ */
+abstract class TabsTrayViewHolder(
+ itemView: View,
+ private val imageLoader: ImageLoader,
+ private val trayStore: TabsTrayStore,
+ private val selectionHolder: SelectionHolder?,
+ private val store: BrowserStore = itemView.context.components.core.store,
+ private val metrics: MetricController = itemView.context.components.analytics.metrics
+) : TabViewHolder(itemView) {
+
+ private val faviconView: ImageView? =
+ itemView.findViewById(R.id.mozac_browser_tabstray_favicon_icon)
+ private val titleView: TextView = itemView.findViewById(R.id.mozac_browser_tabstray_title)
+ private val closeView: AppCompatImageButton =
+ itemView.findViewById(R.id.mozac_browser_tabstray_close)
+ private val thumbnailView: TabThumbnailView =
+ itemView.findViewById(R.id.mozac_browser_tabstray_thumbnail)
+
+ @VisibleForTesting
+ internal val urlView: TextView? = itemView.findViewById(R.id.mozac_browser_tabstray_url)
+ private val playPauseButtonView: ImageButton = itemView.findViewById(R.id.play_pause_button)
+
+ abstract val browserTrayInteractor: BrowserTrayInteractor
+ abstract val thumbnailSize: Int
+
+ override var tab: Tab? = null
+
+ /**
+ * Displays the data of the given session and notifies the given observable about events.
+ */
+ @Suppress("ComplexMethod", "LongMethod")
+ override fun bind(
+ tab: Tab,
+ isSelected: Boolean,
+ styling: TabsTrayStyling,
+ observable: Observable
+ ) {
+ this.tab = tab
+
+ updateTitle(tab)
+ updateUrl(tab)
+ updateFavicon(tab)
+ updateCloseButtonDescription(tab.title)
+ updateSelectedTabIndicator(isSelected)
+ updateMediaState(tab)
+
+ if (selectionHolder != null) {
+ setSelectionInteractor(tab, selectionHolder, browserTrayInteractor)
+ } else {
+ itemView.setOnClickListener { browserTrayInteractor.open(tab) }
+ }
+
+ if (tab.thumbnail != null) {
+ thumbnailView.setImageBitmap(tab.thumbnail)
+ } else {
+ loadIntoThumbnailView(thumbnailView, tab.id)
+ }
+ }
+
+ fun showTabIsMultiSelectEnabled(isSelected: Boolean) {
+ itemView.selected_mask.isVisible = isSelected
+ closeView.isInvisible = trayStore.state.mode is TabsTrayState.Mode.Select
+ }
+
+ private fun updateFavicon(tab: Tab) {
+ if (tab.icon != null) {
+ faviconView?.visibility = View.VISIBLE
+ faviconView?.setImageBitmap(tab.icon)
+ } else {
+ faviconView?.visibility = View.GONE
+ }
+ }
+
+ private fun updateTitle(tab: Tab) {
+ val title = if (tab.title.isNotEmpty()) {
+ tab.title
+ } else {
+ tab.url
+ }
+ titleView.text = title
+ }
+
+ private fun updateUrl(tab: Tab) {
+ // Truncate to MAX_URI_LENGTH to prevent the UI from locking up for
+ // extremely large URLs such as data URIs or bookmarklets. The same
+ // is done in the toolbar and awesomebar:
+ // https://github.com/mozilla-mobile/fenix/issues/1824
+ // https://github.com/mozilla-mobile/android-components/issues/6985
+ urlView?.text = tab.url
+ .toShortUrl(itemView.context.components.publicSuffixList)
+ .take(MAX_URI_LENGTH)
+ }
+
+ private fun updateCloseButtonDescription(title: String) {
+ closeView.contentDescription =
+ closeView.context.getString(R.string.close_tab_title, title)
+ }
+
+ /**
+ * NB: Why do we query for the media state from the store, when we have [Tab.playbackState] and
+ * [Tab.controller] already mapped?
+ */
+ private fun updateMediaState(tab: Tab) {
+ // Media state
+ playPauseButtonView.increaseTapArea(PLAY_PAUSE_BUTTON_EXTRA_DPS)
+
+ with(playPauseButtonView) {
+ invalidate()
+ val sessionState = store.state.findTabOrCustomTab(tab.id)
+ when (sessionState?.mediaSessionState?.playbackState) {
+ MediaSession.PlaybackState.PAUSED -> {
+ showAndEnable()
+ contentDescription =
+ context.getString(R.string.mozac_feature_media_notification_action_play)
+ setImageDrawable(
+ AppCompatResources.getDrawable(context, R.drawable.media_state_play)
+ )
+ }
+
+ MediaSession.PlaybackState.PLAYING -> {
+ showAndEnable()
+ contentDescription =
+ context.getString(R.string.mozac_feature_media_notification_action_pause)
+ setImageDrawable(
+ AppCompatResources.getDrawable(context, R.drawable.media_state_pause)
+ )
+ }
+
+ else -> {
+ removeTouchDelegate()
+ removeAndDisable()
+ }
+ }
+
+ setOnClickListener {
+ when (sessionState?.mediaSessionState?.playbackState) {
+ MediaSession.PlaybackState.PLAYING -> {
+ metrics.track(Event.TabMediaPause)
+ sessionState.mediaSessionState?.controller?.pause()
+ }
+
+ MediaSession.PlaybackState.PAUSED -> {
+ metrics.track(Event.TabMediaPlay)
+ sessionState.mediaSessionState?.controller?.play()
+ }
+ else -> throw AssertionError(
+ "Play/Pause button clicked without play/pause state."
+ )
+ }
+ }
+ }
+ }
+
+ private fun loadIntoThumbnailView(thumbnailView: ImageView, id: String) {
+ imageLoader.loadIntoView(thumbnailView, ImageLoadRequest(id, thumbnailSize))
+ }
+
+ private fun setSelectionInteractor(
+ item: Tab,
+ holder: SelectionHolder,
+ interactor: SelectionInteractor
+ ) {
+ itemView.setOnClickListener {
+ val selected = holder.selectedItems
+ when {
+ selected.isEmpty() && trayStore.state.mode.isSelect().not() -> interactor.open(item)
+ item in selected -> interactor.deselect(item)
+ else -> interactor.select(item)
+ }
+ }
+
+ itemView.setOnLongClickListener {
+ if (holder.selectedItems.isEmpty()) {
+ metrics.track(Event.CollectionTabLongPressed)
+ interactor.select(item)
+ true
+ } else {
+ false
+ }
+ }
+ }
+
+ companion object {
+ internal const val PLAY_PAUSE_BUTTON_EXTRA_DPS = 24
+ internal const val GRID_ITEM_CLOSE_BUTTON_EXTRA_DPS = 24
+ }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/TrayPagerAdapter.kt b/app/src/main/java/org/mozilla/fenix/tabstray/TrayPagerAdapter.kt
index c4ea545ae..7b72b8144 100644
--- a/app/src/main/java/org/mozilla/fenix/tabstray/TrayPagerAdapter.kt
+++ b/app/src/main/java/org/mozilla/fenix/tabstray/TrayPagerAdapter.kt
@@ -8,42 +8,79 @@ import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
-import org.mozilla.fenix.tabstray.BrowserTabViewHolder.Companion.LAYOUT_ID_NORMAL_TAB
-import org.mozilla.fenix.tabstray.BrowserTabViewHolder.Companion.LAYOUT_ID_PRIVATE_TAB
-import org.mozilla.fenix.tabtray.FenixTabsAdapter
+import mozilla.components.browser.state.selector.normalTabs
+import mozilla.components.browser.state.selector.privateTabs
+import mozilla.components.browser.state.selector.selectedTab
+import mozilla.components.browser.state.store.BrowserStore
+import org.mozilla.fenix.sync.SyncedTabsAdapter
+import org.mozilla.fenix.tabstray.browser.BrowserTabsAdapter
+import org.mozilla.fenix.tabstray.browser.BrowserTrayInteractor
+import org.mozilla.fenix.tabstray.syncedtabs.TabClickDelegate
+import org.mozilla.fenix.tabstray.viewholders.AbstractTrayViewHolder
+import org.mozilla.fenix.tabstray.viewholders.NormalBrowserTabViewHolder
+import org.mozilla.fenix.tabstray.viewholders.PrivateBrowserTabViewHolder
+import org.mozilla.fenix.tabstray.viewholders.SyncedTabViewHolder
class TrayPagerAdapter(
- context: Context,
- val interactor: TabsTrayInteractor
-) : RecyclerView.Adapter() {
+ private val context: Context,
+ private val store: TabsTrayStore,
+ private val browserInteractor: BrowserTrayInteractor,
+ private val navInteractor: NavigationInteractor,
+ private val interactor: TabsTrayInteractor,
+ private val browserStore: BrowserStore
+) : RecyclerView.Adapter() {
- private val normalAdapter by lazy { FenixTabsAdapter(context) }
- private val privateAdapter by lazy { FenixTabsAdapter(context) }
+ private val normalAdapter by lazy { BrowserTabsAdapter(context, browserInteractor, store) }
+ private val privateAdapter by lazy { BrowserTabsAdapter(context, browserInteractor, store) }
+ private val syncedTabsAdapter by lazy { SyncedTabsAdapter(TabClickDelegate(navInteractor)) }
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TrayViewHolder {
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AbstractTrayViewHolder {
val itemView = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
+ val selectedTab = browserStore.state.selectedTab
+
return when (viewType) {
- LAYOUT_ID_NORMAL_TAB -> BrowserTabViewHolder(itemView, interactor)
- LAYOUT_ID_PRIVATE_TAB -> BrowserTabViewHolder(itemView, interactor)
+ NormalBrowserTabViewHolder.LAYOUT_ID -> {
+ NormalBrowserTabViewHolder(
+ itemView,
+ store,
+ interactor,
+ browserStore.state.normalTabs.indexOf(selectedTab)
+ )
+ }
+ PrivateBrowserTabViewHolder.LAYOUT_ID -> {
+ PrivateBrowserTabViewHolder(
+ itemView,
+ store,
+ interactor,
+ browserStore.state.privateTabs.indexOf(selectedTab)
+ )
+ }
+ SyncedTabViewHolder.LAYOUT_ID -> {
+ SyncedTabViewHolder(
+ itemView,
+ store
+ )
+ }
else -> throw IllegalStateException("Unknown viewType.")
}
}
- override fun onBindViewHolder(viewHolder: TrayViewHolder, position: Int) {
+ override fun onBindViewHolder(viewHolder: AbstractTrayViewHolder, position: Int) {
val adapter = when (position) {
POSITION_NORMAL_TABS -> normalAdapter
POSITION_PRIVATE_TABS -> privateAdapter
+ POSITION_SYNCED_TABS -> syncedTabsAdapter
else -> throw IllegalStateException("View type does not exist.")
}
-
- viewHolder.bind(adapter)
+ viewHolder.bind(adapter, browserInteractor.getLayoutManagerForPosition(context, position))
}
override fun getItemViewType(position: Int): Int {
return when (position) {
- POSITION_NORMAL_TABS -> LAYOUT_ID_NORMAL_TAB
- POSITION_PRIVATE_TABS -> LAYOUT_ID_PRIVATE_TAB
+ POSITION_NORMAL_TABS -> NormalBrowserTabViewHolder.LAYOUT_ID
+ POSITION_PRIVATE_TABS -> PrivateBrowserTabViewHolder.LAYOUT_ID
+ POSITION_SYNCED_TABS -> SyncedTabViewHolder.LAYOUT_ID
else -> throw IllegalStateException("Unknown position.")
}
}
@@ -51,9 +88,10 @@ class TrayPagerAdapter(
override fun getItemCount(): Int = TRAY_TABS_COUNT
companion object {
- const val TRAY_TABS_COUNT = 2
+ const val TRAY_TABS_COUNT = 3
- const val POSITION_NORMAL_TABS = 0
- const val POSITION_PRIVATE_TABS = 1
+ val POSITION_NORMAL_TABS = Page.NormalTabs.ordinal
+ val POSITION_PRIVATE_TABS = Page.PrivateTabs.ordinal
+ val POSITION_SYNCED_TABS = Page.SyncedTabs.ordinal
}
}
diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/TraySheetBehaviorCallback.kt b/app/src/main/java/org/mozilla/fenix/tabstray/TraySheetBehaviorCallback.kt
new file mode 100644
index 000000000..5c92df303
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/tabstray/TraySheetBehaviorCallback.kt
@@ -0,0 +1,44 @@
+/* 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.tabstray
+
+import android.view.View
+import androidx.constraintlayout.widget.ConstraintLayout
+import com.google.android.material.bottomsheet.BottomSheetBehavior
+import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_HIDDEN
+
+class TraySheetBehaviorCallback(
+ private val behavior: BottomSheetBehavior,
+ private val trayInteractor: NavigationInteractor
+) : BottomSheetBehavior.BottomSheetCallback() {
+
+ override fun onStateChanged(bottomSheet: View, newState: Int) {
+ if (newState == STATE_HIDDEN) {
+ trayInteractor.onTabTrayDismissed()
+ } else if (newState == BottomSheetBehavior.STATE_HALF_EXPANDED) {
+ // We only support expanded and collapsed states.
+ // But why??
+ behavior.state = STATE_HIDDEN
+ }
+ }
+
+ override fun onSlide(bottomSheet: View, slideOffset: Float) = Unit
+}
+
+fun BottomSheetBehavior.setUpTrayBehavior(
+ isLandscape: Boolean,
+ maxNumberOfTabs: Int,
+ numberForExpandingTray: Int,
+ navigationInteractor: DefaultNavigationInteractor
+) {
+ addBottomSheetCallback(
+ TraySheetBehaviorCallback(this, navigationInteractor)
+ )
+ state = if (isLandscape || maxNumberOfTabs >= numberForExpandingTray) {
+ BottomSheetBehavior.STATE_EXPANDED
+ } else {
+ BottomSheetBehavior.STATE_COLLAPSED
+ }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/TrayViewHolders.kt b/app/src/main/java/org/mozilla/fenix/tabstray/TrayViewHolders.kt
deleted file mode 100644
index e94226c8b..000000000
--- a/app/src/main/java/org/mozilla/fenix/tabstray/TrayViewHolders.kt
+++ /dev/null
@@ -1,41 +0,0 @@
-/* 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.tabstray
-
-import android.view.View
-import androidx.recyclerview.widget.LinearLayoutManager
-import androidx.recyclerview.widget.RecyclerView
-import kotlinx.android.extensions.LayoutContainer
-import org.mozilla.fenix.R
-import org.mozilla.fenix.tabstray.browser.BaseBrowserTrayList
-
-sealed class TrayViewHolder constructor(
- override val containerView: View
-) : RecyclerView.ViewHolder(containerView), LayoutContainer {
-
- abstract fun bind(adapter: RecyclerView.Adapter)
-}
-
-class BrowserTabViewHolder(
- containerView: View,
- interactor: TabsTrayInteractor
-) : TrayViewHolder(containerView) {
-
- private val trayList: BaseBrowserTrayList = itemView.findViewById(R.id.tray_list_item)
-
- init {
- trayList.interactor = interactor
- }
-
- override fun bind(adapter: RecyclerView.Adapter) {
- trayList.layoutManager = LinearLayoutManager(itemView.context)
- trayList.adapter = adapter
- }
-
- companion object {
- const val LAYOUT_ID_NORMAL_TAB = R.layout.normal_browser_tray_list
- const val LAYOUT_ID_PRIVATE_TAB = R.layout.private_browser_tray_list
- }
-}
diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/browser/BaseBrowserTrayList.kt b/app/src/main/java/org/mozilla/fenix/tabstray/browser/BaseBrowserTrayList.kt
index caaa51108..81029500f 100644
--- a/app/src/main/java/org/mozilla/fenix/tabstray/browser/BaseBrowserTrayList.kt
+++ b/app/src/main/java/org/mozilla/fenix/tabstray/browser/BaseBrowserTrayList.kt
@@ -6,18 +6,14 @@ package org.mozilla.fenix.tabstray.browser
import android.content.Context
import android.util.AttributeSet
+import androidx.annotation.VisibleForTesting
import androidx.recyclerview.widget.RecyclerView
-import mozilla.components.browser.tabstray.TabsAdapter
-import mozilla.components.feature.tabs.TabsUseCases
import mozilla.components.feature.tabs.tabstray.TabsFeature
-import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
-import org.mozilla.fenix.components.metrics.Event
-import org.mozilla.fenix.components.metrics.MetricController
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.tabstray.TabsTrayInteractor
+import org.mozilla.fenix.tabstray.TabsTrayStore
import org.mozilla.fenix.tabstray.TrayItem
import org.mozilla.fenix.tabstray.ext.filterFromConfig
-import org.mozilla.fenix.utils.view.LifecycleViewProvider
abstract class BaseBrowserTrayList @JvmOverloads constructor(
context: Context,
@@ -25,71 +21,78 @@ abstract class BaseBrowserTrayList @JvmOverloads constructor(
defStyleAttr: Int = 0
) : RecyclerView(context, attrs, defStyleAttr), TrayItem {
+ /**
+ * The browser tab types we would want to show.
+ */
enum class BrowserTabType { NORMAL, PRIVATE }
+
+ /**
+ * A configuration for classes that extend [BaseBrowserTrayList].
+ */
data class Configuration(val browserTabType: BrowserTabType)
abstract val configuration: Configuration
- var interactor: TabsTrayInteractor? = null
+ lateinit var interactor: TabsTrayInteractor
+ lateinit var tabsTrayStore: TabsTrayStore
+
+ private val tabsFeature by lazy {
+ // NB: The use cases here are duplicated because there isn't a nicer
+ // way to share them without a better dependency injection solution.
+ val selectTabUseCase = SelectTabUseCaseWrapper(
+ context.components.analytics.metrics,
+ context.components.useCases.tabsUseCases.selectTab
+ ) {
+ interactor.navigateToBrowser()
+ }
- private val lifecycleProvider = LifecycleViewProvider(this)
+ val removeTabUseCase = RemoveTabUseCaseWrapper(
+ context.components.analytics.metrics
+ ) { sessionId ->
+ interactor.onDeleteTab(sessionId)
+ }
- private val selectTabUseCase = SelectTabUseCaseWrapper(
- context.components.analytics.metrics,
- context.components.useCases.tabsUseCases.selectTab
- ) {
- interactor?.navigateToBrowser()
+ TabsFeature(
+ adapter as TabsAdapter,
+ context.components.core.store,
+ selectTabUseCase,
+ removeTabUseCase,
+ { it.filterFromConfig(configuration) },
+ { }
+ )
}
- private val removeTabUseCase = RemoveTabUseCaseWrapper(
- context.components.analytics.metrics
- ) { sessionId ->
- interactor?.tabRemoved(sessionId)
+ private val swipeToDelete by lazy {
+ SwipeToDeleteBinding(tabsTrayStore)
}
- private val tabsFeature by lazy {
- ViewBoundFeatureWrapper(
- feature = TabsFeature(
- adapter as TabsAdapter,
- context.components.core.store,
- selectTabUseCase,
- removeTabUseCase,
- { it.filterFromConfig(configuration) },
- { }
- ),
- owner = lifecycleProvider,
- view = this
+ private val touchHelper by lazy {
+ TabsTouchHelper(
+ observable = adapter as TabsAdapter,
+ onViewHolderTouched = { swipeToDelete.isSwipeable },
+ onViewHolderDraw = { context.components.settings.listTabView }
)
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
- // This is weird, but I don't have a better solution right now: We need to keep a
- // lazy reference to the feature/adapter so that we do not re-create
- // it every time it's attached. This reference is our way to init.
- tabsFeature
- }
-}
+ tabsFeature.start()
+ swipeToDelete.start()
-internal class SelectTabUseCaseWrapper(
- private val metrics: MetricController,
- private val selectTab: TabsUseCases.SelectTabUseCase,
- private val onSelect: (String) -> Unit
-) : TabsUseCases.SelectTabUseCase {
- override fun invoke(tabId: String) {
- metrics.track(Event.OpenedExistingTab)
- selectTab(tabId)
- onSelect(tabId)
+ touchHelper.attachToRecyclerView(this)
}
-}
-internal class RemoveTabUseCaseWrapper(
- private val metrics: MetricController,
- private val onRemove: (String) -> Unit
-) : TabsUseCases.RemoveTabUseCase {
- override fun invoke(sessionId: String) {
- metrics.track(Event.ClosedExistingTab)
- onRemove(sessionId)
+ @VisibleForTesting
+ public override fun onDetachedFromWindow() {
+ super.onDetachedFromWindow()
+
+ tabsFeature.stop()
+ swipeToDelete.stop()
+
+ // Notify the adapter that it is released from the view preemptively.
+ adapter?.onDetachedFromRecyclerView(this)
+
+ touchHelper.attachToRecyclerView(null)
}
}
diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/browser/BrowserTabsAdapter.kt b/app/src/main/java/org/mozilla/fenix/tabstray/browser/BrowserTabsAdapter.kt
new file mode 100644
index 000000000..3eb5550f3
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/tabstray/browser/BrowserTabsAdapter.kt
@@ -0,0 +1,118 @@
+/* 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.tabstray.browser
+
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
+import kotlinx.android.synthetic.main.tab_tray_item.view.*
+import mozilla.components.browser.tabstray.TabsAdapter.Companion.PAYLOAD_DONT_HIGHLIGHT_SELECTED_ITEM
+import mozilla.components.browser.tabstray.TabsAdapter.Companion.PAYLOAD_HIGHLIGHT_SELECTED_ITEM
+import mozilla.components.browser.thumbnails.loader.ThumbnailLoader
+import mozilla.components.concept.tabstray.Tab
+import mozilla.components.concept.tabstray.TabsTray
+import mozilla.components.support.base.observer.Observable
+import mozilla.components.support.base.observer.ObserverRegistry
+import org.mozilla.fenix.R
+import org.mozilla.fenix.ext.components
+import org.mozilla.fenix.selection.SelectionHolder
+import org.mozilla.fenix.tabstray.TabsTrayStore
+import org.mozilla.fenix.tabstray.TabsTrayViewHolder
+
+/**
+ * A [RecyclerView.Adapter] for browser tabs.
+ */
+class BrowserTabsAdapter(
+ private val context: Context,
+ private val interactor: BrowserTrayInteractor,
+ private val store: TabsTrayStore,
+ delegate: Observable = ObserverRegistry()
+) : TabsAdapter(delegate) {
+
+ /**
+ * The layout types for the tabs.
+ */
+ enum class ViewType(val layoutRes: Int) {
+ LIST(R.layout.tab_tray_item),
+ GRID(R.layout.tab_tray_grid_item)
+ }
+
+ /**
+ * Tracks the selected tabs in multi-select mode.
+ */
+ var selectionHolder: SelectionHolder? = null
+
+ private val selectedItemAdapterBinding = SelectedItemAdapterBinding(store, this)
+ private val imageLoader = ThumbnailLoader(context.components.core.thumbnailStorage)
+
+ override fun getItemViewType(position: Int): Int {
+ return if (context.components.settings.gridTabView) {
+ ViewType.GRID.layoutRes
+ } else {
+ ViewType.LIST.layoutRes
+ }
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TabsTrayViewHolder {
+ val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
+
+ return when (viewType) {
+ ViewType.GRID.layoutRes ->
+ TabsTrayGridViewHolder(imageLoader, interactor, store, selectionHolder, view)
+ else ->
+ TabsTrayListViewHolder(imageLoader, interactor, store, selectionHolder, view)
+ }
+ }
+
+ override fun onBindViewHolder(holder: TabsTrayViewHolder, position: Int) {
+ super.onBindViewHolder(holder, position)
+
+ holder.tab?.let { tab ->
+ holder.itemView.mozac_browser_tabstray_close.setOnClickListener {
+ interactor.close(tab)
+ }
+
+ selectionHolder?.let {
+ holder.showTabIsMultiSelectEnabled(it.selectedItems.contains(tab))
+ }
+ }
+ }
+
+ /**
+ * Over-ridden [onBindViewHolder] that uses the payloads to notify the selected tab how to
+ * display itself.
+ */
+ override fun onBindViewHolder(holder: TabsTrayViewHolder, position: Int, payloads: List) {
+ val tabs = tabs ?: return
+
+ if (tabs.list.isEmpty()) return
+
+ if (payloads.isEmpty()) {
+ onBindViewHolder(holder, position)
+ return
+ }
+
+ if (position == tabs.selectedIndex) {
+ if (payloads.contains(PAYLOAD_HIGHLIGHT_SELECTED_ITEM)) {
+ holder.updateSelectedTabIndicator(true)
+ } else if (payloads.contains(PAYLOAD_DONT_HIGHLIGHT_SELECTED_ITEM)) {
+ holder.updateSelectedTabIndicator(false)
+ }
+ }
+
+ selectionHolder?.let {
+ holder.showTabIsMultiSelectEnabled(it.selectedItems.contains(holder.tab))
+ }
+ }
+
+ override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
+ selectedItemAdapterBinding.start()
+ }
+
+ override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
+ selectedItemAdapterBinding.stop()
+ }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/browser/BrowserTrayInteractor.kt b/app/src/main/java/org/mozilla/fenix/tabstray/browser/BrowserTrayInteractor.kt
new file mode 100644
index 000000000..49cb4784d
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/tabstray/browser/BrowserTrayInteractor.kt
@@ -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.tabstray.browser
+
+import android.content.Context
+import androidx.recyclerview.widget.GridLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import mozilla.components.concept.tabstray.Tab
+import mozilla.components.feature.tabs.TabsUseCases
+import mozilla.components.support.base.feature.UserInteractionHandler
+import org.mozilla.fenix.selection.SelectionInteractor
+import org.mozilla.fenix.tabstray.TabsTrayAction
+import org.mozilla.fenix.components.metrics.MetricController
+import org.mozilla.fenix.tabstray.TabsTrayController
+import org.mozilla.fenix.tabstray.TabsTrayInteractor
+import org.mozilla.fenix.tabstray.TrayPagerAdapter
+import org.mozilla.fenix.tabstray.ext.numberOfGridColumns
+import org.mozilla.fenix.utils.Settings
+import org.mozilla.fenix.tabstray.TabsTrayState.Mode
+import org.mozilla.fenix.tabstray.TabsTrayStore
+
+/**
+ * For interacting with UI that is specifically for [BaseBrowserTrayList] and other browser
+ * tab tray views.
+ */
+interface BrowserTrayInteractor : SelectionInteractor, UserInteractionHandler {
+
+ /**
+ * Close the tab.
+ */
+ fun close(tab: Tab)
+
+ /**
+ * Returns the appropriate [RecyclerView.LayoutManager] to be used at [position].
+ */
+ fun getLayoutManagerForPosition(context: Context, position: Int): RecyclerView.LayoutManager
+
+ /**
+ * TabTray's Floating Action Button clicked.
+ */
+ fun onFabClicked(isPrivate: Boolean)
+}
+
+/**
+ * A default implementation of [BrowserTrayInteractor].
+ */
+class DefaultBrowserTrayInteractor(
+ private val store: TabsTrayStore,
+ private val trayInteractor: TabsTrayInteractor,
+ private val controller: TabsTrayController,
+ private val selectTab: TabsUseCases.SelectTabUseCase,
+ private val settings: Settings,
+ private val metrics: MetricController
+) : BrowserTrayInteractor {
+
+ private val selectTabWrapper by lazy {
+ SelectTabUseCaseWrapper(metrics, selectTab) {
+ trayInteractor.navigateToBrowser()
+ }
+ }
+
+ private val removeTabWrapper by lazy {
+ RemoveTabUseCaseWrapper(metrics) {
+ // Handle removal from the interactor where we can also handle "undo" visuals.
+ trayInteractor.onDeleteTab(it)
+ }
+ }
+
+ /**
+ * See [SelectionInteractor.open]
+ */
+ override fun open(item: Tab) {
+ selectTabWrapper.invoke(item.id)
+ trayInteractor.navigateToBrowser()
+ }
+
+ /**
+ * See [BrowserTrayInteractor.close].
+ */
+ override fun close(tab: Tab) {
+ removeTabWrapper.invoke(tab.id)
+ }
+
+ /**
+ * See [SelectionInteractor.select]
+ */
+ override fun select(item: Tab) {
+ store.dispatch(TabsTrayAction.AddSelectTab(item))
+ }
+
+ /**
+ * See [SelectionInteractor.deselect]
+ */
+ override fun deselect(item: Tab) {
+ store.dispatch(TabsTrayAction.RemoveSelectTab(item))
+ }
+
+ /**
+ * See [UserInteractionHandler.onBackPressed]
+ *
+ * TODO move this to the navigation interactor when it lands.
+ */
+ override fun onBackPressed(): Boolean {
+ if (store.state.mode is Mode.Select) {
+ store.dispatch(TabsTrayAction.ExitSelectMode)
+ return true
+ }
+ return false
+ }
+
+ override fun getLayoutManagerForPosition(
+ context: Context,
+ position: Int
+ ): RecyclerView.LayoutManager {
+ if (position == TrayPagerAdapter.POSITION_SYNCED_TABS) {
+ // Lists are just Grids with one column :)
+ return GridLayoutManager(context, 1)
+ }
+
+ // Normal/Private tabs
+ val numberOfColumns = if (settings.gridTabView) {
+ context.numberOfGridColumns
+ } else {
+ 1
+ }
+
+ return GridLayoutManager(context, numberOfColumns)
+ }
+
+ /**
+ * See [BrowserTrayInteractor.onFabClicked]
+ */
+ override fun onFabClicked(isPrivate: Boolean) {
+ controller.onNewTabTapped(isPrivate)
+ }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/browser/SelectedItemAdapterBinding.kt b/app/src/main/java/org/mozilla/fenix/tabstray/browser/SelectedItemAdapterBinding.kt
new file mode 100644
index 000000000..a725364dc
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/tabstray/browser/SelectedItemAdapterBinding.kt
@@ -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.tabstray.browser
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.drop
+import kotlinx.coroutines.flow.map
+import mozilla.components.browser.tabstray.TabsAdapter.Companion.PAYLOAD_DONT_HIGHLIGHT_SELECTED_ITEM
+import mozilla.components.browser.tabstray.TabsAdapter.Companion.PAYLOAD_HIGHLIGHT_SELECTED_ITEM
+import mozilla.components.lib.state.ext.flowScoped
+import mozilla.components.support.base.feature.LifecycleAwareFeature
+import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged
+import org.mozilla.fenix.tabstray.TabsTrayState.Mode
+import org.mozilla.fenix.tabstray.TabsTrayStore
+
+/**
+ * Notifies the adapter when the selection mode changes.
+ */
+class SelectedItemAdapterBinding(
+ val store: TabsTrayStore,
+ val adapter: BrowserTabsAdapter
+) : LifecycleAwareFeature {
+ private var scope: CoroutineScope? = null
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ override fun start() {
+ scope = store.flowScoped { flow ->
+ flow.map { it.mode }
+ // ignore initial mode update; the adapter is already in an updated state.
+ .drop(1)
+ .ifChanged()
+ .collect { mode ->
+ notifyAdapter(mode)
+ }
+ }
+ }
+
+ override fun stop() {
+ scope?.cancel()
+ }
+
+ private fun notifyAdapter(mode: Mode) = with(adapter) {
+ if (mode == Mode.Normal) {
+ notifyItemRangeChanged(0, itemCount, PAYLOAD_HIGHLIGHT_SELECTED_ITEM)
+ } else {
+ notifyItemRangeChanged(0, itemCount, PAYLOAD_DONT_HIGHLIGHT_SELECTED_ITEM)
+ }
+ }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/browser/SelectionBannerBinding.kt b/app/src/main/java/org/mozilla/fenix/tabstray/browser/SelectionBannerBinding.kt
new file mode 100644
index 000000000..8850049a4
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/tabstray/browser/SelectionBannerBinding.kt
@@ -0,0 +1,141 @@
+/* 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.tabstray.browser
+
+import android.content.Context
+import android.view.View
+import androidx.annotation.VisibleForTesting
+import androidx.core.content.ContextCompat
+import androidx.core.view.isVisible
+import kotlinx.android.synthetic.main.component_tabstray2.view.exit_multi_select
+import kotlinx.android.synthetic.main.component_tabstray2.view.multiselect_title
+import kotlinx.android.synthetic.main.tabstray_multiselect_items.view.*
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.drop
+import kotlinx.coroutines.flow.map
+import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged
+import org.mozilla.fenix.R
+import org.mozilla.fenix.components.AbstractBinding
+import org.mozilla.fenix.tabstray.NavigationInteractor
+import org.mozilla.fenix.tabstray.TabsTrayInteractor
+import org.mozilla.fenix.tabstray.TabsTrayState
+import org.mozilla.fenix.tabstray.TabsTrayStore
+import org.mozilla.fenix.tabstray.TabsTrayAction.ExitSelectMode
+import org.mozilla.fenix.tabstray.TabsTrayState.Mode
+import org.mozilla.fenix.tabstray.TabsTrayState.Mode.Select
+import org.mozilla.fenix.tabstray.ext.showWithTheme
+
+/**
+ * A binding that shows/hides the multi-select banner of the selected count of tabs.
+ *
+ * @property context An Android context.
+ * @property store The TabsTrayStore instance.
+ * @property navInteractor An instance of [NavigationInteractor] for navigating on menu clicks.
+ * @property tabsTrayInteractor An instance of [TabsTrayInteractor] for handling deletion.
+ * @property containerView The view in the layout that contains all the implicit multi-select
+ * views. NB: This parameter is a bit opaque and requires a larger layout refactor to correct.
+ * @property backgroundView The background view that we want to alter when changing [Mode].
+ * @property showOnSelectViews A variable list of views that will be made visible when in select mode.
+ * @property showOnNormalViews A variable list of views that will be made visible when in normal mode.
+ */
+@Suppress("LongParameterList")
+class SelectionBannerBinding(
+ private val context: Context,
+ private val store: TabsTrayStore,
+ private val navInteractor: NavigationInteractor,
+ private val tabsTrayInteractor: TabsTrayInteractor,
+ private val containerView: View,
+ private val backgroundView: View,
+ private val showOnSelectViews: VisibilityModifier,
+ private val showOnNormalViews: VisibilityModifier
+) : AbstractBinding(store) {
+
+ /**
+ * A holder of views that will be used by having their [View.setVisibility] modified.
+ */
+ class VisibilityModifier(vararg val views: View)
+
+ private var isPreviousModeSelect = false
+
+ override fun start() {
+ super.start()
+
+ initListeners(containerView)
+ }
+
+ override suspend fun onState(flow: Flow) {
+ flow.map { it.mode }
+ // ignore initial mode update; we never start in select mode.
+ .drop(1)
+ .ifChanged()
+ .collect { mode ->
+ val isSelectMode = mode is Select
+
+ showOnSelectViews.views.forEach {
+ it.isVisible = isSelectMode
+ }
+
+ showOnNormalViews.views.forEach {
+ it.isVisible = isSelectMode.not()
+ }
+
+ updateBackgroundColor(isSelectMode)
+
+ updateSelectTitle(isSelectMode, mode.selectedTabs.size)
+
+ isPreviousModeSelect = isSelectMode
+ }
+ }
+
+ private fun initListeners(containerView: View) {
+ containerView.share_multi_select.setOnClickListener {
+ navInteractor.onShareTabs(store.state.mode.selectedTabs)
+ }
+
+ containerView.collect_multi_select.setOnClickListener {
+ navInteractor.onSaveToCollections(store.state.mode.selectedTabs)
+ }
+
+ containerView.exit_multi_select.setOnClickListener {
+ store.dispatch(ExitSelectMode)
+ }
+
+ containerView.menu_multi_select.setOnClickListener { anchor ->
+ val menu = SelectionMenuIntegration(
+ context,
+ store,
+ navInteractor,
+ tabsTrayInteractor
+ ).build()
+
+ menu.showWithTheme(anchor)
+ }
+ }
+
+ @VisibleForTesting
+ private fun updateBackgroundColor(isSelectMode: Boolean) {
+ // memoize to avoid setting the background unnecessarily.
+ if (isPreviousModeSelect != isSelectMode) {
+ val colorResource = if (isSelectMode) {
+ R.color.accent_normal_theme
+ } else {
+ R.color.foundation_normal_theme
+ }
+
+ val color = ContextCompat.getColor(backgroundView.context, colorResource)
+
+ backgroundView.setBackgroundColor(color)
+ }
+ }
+
+ @VisibleForTesting
+ private fun updateSelectTitle(selectedMode: Boolean, tabCount: Int) {
+ if (selectedMode) {
+ containerView.multiselect_title.text =
+ context.getString(R.string.tab_tray_multi_select_title, tabCount)
+ }
+ }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/browser/SelectionHandleBinding.kt b/app/src/main/java/org/mozilla/fenix/tabstray/browser/SelectionHandleBinding.kt
new file mode 100644
index 000000000..6560b8d93
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/tabstray/browser/SelectionHandleBinding.kt
@@ -0,0 +1,108 @@
+/* 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.tabstray.browser
+
+import android.view.View
+import android.view.ViewGroup
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.core.content.ContextCompat
+import androidx.core.view.updateLayoutParams
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.drop
+import kotlinx.coroutines.flow.map
+import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged
+import org.mozilla.fenix.R
+import org.mozilla.fenix.components.AbstractBinding
+import org.mozilla.fenix.tabstray.TabsTrayState
+import org.mozilla.fenix.tabstray.TabsTrayState.Mode
+import org.mozilla.fenix.tabstray.TabsTrayStore
+
+private const val NORMAL_HANDLE_PERCENT_WIDTH = 0.1F
+
+/**
+ * Various layout updates that need to be applied to the "handle" view when switching
+ * between [Mode].
+ *
+ * @param store The TabsTrayStore instance.
+ * @property handle The "handle" of the Tabs Tray that is used to drag the tray open/close.
+ * @property containerLayout The [ConstraintLayout] that contains the "handle".
+ */
+class SelectionHandleBinding(
+ store: TabsTrayStore,
+ private val handle: View,
+ private val containerLayout: ConstraintLayout
+) : AbstractBinding(store) {
+
+ private var isPreviousModeSelect = false
+
+ override suspend fun onState(flow: Flow) {
+ flow.map { it.mode }
+ // ignore initial mode update; we never start in select mode.
+ .drop(1)
+ .ifChanged()
+ .collect { mode ->
+ val isSelectMode = mode is Mode.Select
+
+ // memoize to avoid unnecessary layout updates.
+ if (isPreviousModeSelect != isSelectMode) {
+ updateLayoutParams(handle, isSelectMode)
+
+ updateBackgroundColor(handle, isSelectMode)
+
+ updateWidthPercent(containerLayout, handle, isSelectMode)
+ }
+
+ isPreviousModeSelect = isSelectMode
+ }
+ }
+
+ private fun updateLayoutParams(handle: View, multiselect: Boolean) {
+ handle.updateLayoutParams {
+ height = handle.resources.getDimensionPixelSize(
+ if (multiselect) {
+ R.dimen.tab_tray_multiselect_handle_height
+ } else {
+ R.dimen.bottom_sheet_handle_height
+ }
+ )
+ topMargin = handle.resources.getDimensionPixelSize(
+ if (multiselect) {
+ R.dimen.tab_tray_multiselect_handle_top_margin
+ } else {
+ R.dimen.bottom_sheet_handle_top_margin
+ }
+ )
+ }
+ }
+
+ private fun updateBackgroundColor(handle: View, multiselect: Boolean) {
+ val colorResource = if (multiselect) {
+ R.color.accent_normal_theme
+ } else {
+ R.color.secondary_text_normal_theme
+ }
+
+ val color = ContextCompat.getColor(handle.context, colorResource)
+
+ handle.setBackgroundColor(color)
+ }
+
+ private fun updateWidthPercent(
+ container: ConstraintLayout,
+ handle: View,
+ multiselect: Boolean
+ ) {
+ val widthPercent = if (multiselect) 1F else NORMAL_HANDLE_PERCENT_WIDTH
+ container.run {
+ ConstraintSet().apply {
+ clone(this@run)
+ constrainPercentWidth(handle.id, widthPercent)
+ applyTo(this@run)
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/browser/SelectionMenu.kt b/app/src/main/java/org/mozilla/fenix/tabstray/browser/SelectionMenu.kt
new file mode 100644
index 000000000..1a789acb6
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/tabstray/browser/SelectionMenu.kt
@@ -0,0 +1,40 @@
+/* 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.tabstray.browser
+
+import android.content.Context
+import mozilla.components.browser.menu.BrowserMenuBuilder
+import mozilla.components.browser.menu.item.SimpleBrowserMenuItem
+import org.mozilla.fenix.R
+
+class SelectionMenu(
+ private val context: Context,
+ private val onItemTapped: (Item) -> Unit = {}
+) {
+ sealed class Item {
+ object BookmarkTabs : Item()
+ object DeleteTabs : Item()
+ }
+
+ val menuBuilder by lazy { BrowserMenuBuilder(menuItems) }
+
+ private val menuItems by lazy {
+ listOf(
+ SimpleBrowserMenuItem(
+ context.getString(R.string.tab_tray_multiselect_menu_item_bookmark),
+ textColorResource = R.color.primary_text_normal_theme
+ ) {
+ onItemTapped.invoke(Item.BookmarkTabs)
+ },
+
+ SimpleBrowserMenuItem(
+ context.getString(R.string.tab_tray_multiselect_menu_item_close),
+ textColorResource = R.color.primary_text_normal_theme
+ ) {
+ onItemTapped.invoke(Item.DeleteTabs)
+ }
+ )
+ }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/browser/SelectionMenuIntegration.kt b/app/src/main/java/org/mozilla/fenix/tabstray/browser/SelectionMenuIntegration.kt
new file mode 100644
index 000000000..a865ebae7
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/tabstray/browser/SelectionMenuIntegration.kt
@@ -0,0 +1,41 @@
+/* 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.tabstray.browser
+
+import android.content.Context
+import androidx.annotation.VisibleForTesting
+import mozilla.components.browser.menu.BrowserMenuBuilder
+import org.mozilla.fenix.tabstray.NavigationInteractor
+import org.mozilla.fenix.tabstray.TabsTrayInteractor
+import org.mozilla.fenix.tabstray.TabsTrayStore
+import org.mozilla.fenix.utils.Do
+
+class SelectionMenuIntegration(
+ private val context: Context,
+ private val store: TabsTrayStore,
+ private val navInteractor: NavigationInteractor,
+ private val trayInteractor: TabsTrayInteractor
+) {
+ private val menu by lazy {
+ SelectionMenu(context, ::handleMenuClicked)
+ }
+
+ /**
+ * Builds the internal menu items list. See [BrowserMenuBuilder.build].
+ */
+ fun build() = menu.menuBuilder.build(context)
+
+ @VisibleForTesting
+ internal fun handleMenuClicked(item: SelectionMenu.Item) {
+ Do exhaustive when (item) {
+ is SelectionMenu.Item.BookmarkTabs -> navInteractor.onSaveToBookmarks(
+ store.state.mode.selectedTabs
+ )
+ is SelectionMenu.Item.DeleteTabs -> trayInteractor.onDeleteTabs(
+ store.state.mode.selectedTabs
+ )
+ }
+ }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/browser/SwipeToDeleteBinding.kt b/app/src/main/java/org/mozilla/fenix/tabstray/browser/SwipeToDeleteBinding.kt
new file mode 100644
index 000000000..fd7052e81
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/tabstray/browser/SwipeToDeleteBinding.kt
@@ -0,0 +1,42 @@
+/* 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.tabstray.browser
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.map
+import mozilla.components.lib.state.ext.flowScoped
+import mozilla.components.support.base.feature.LifecycleAwareFeature
+import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged
+import org.mozilla.fenix.tabstray.TabsTrayState
+import org.mozilla.fenix.tabstray.TabsTrayStore
+
+/**
+ * Notifies whether a tab is accessible for using the swipe-to-delete gesture.
+ */
+class SwipeToDeleteBinding(
+ private val store: TabsTrayStore
+) : LifecycleAwareFeature {
+ private var scope: CoroutineScope? = null
+ var isSwipeable = false
+ private set
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ override fun start() {
+ scope = store.flowScoped { flow ->
+ flow.map { it.mode }
+ .ifChanged()
+ .collect { mode ->
+ isSwipeable = mode == TabsTrayState.Mode.Normal
+ }
+ }
+ }
+
+ override fun stop() {
+ scope?.cancel()
+ }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/browser/TabsAdapter.kt b/app/src/main/java/org/mozilla/fenix/tabstray/browser/TabsAdapter.kt
new file mode 100644
index 000000000..8e4820cd1
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/tabstray/browser/TabsAdapter.kt
@@ -0,0 +1,64 @@
+/* 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.tabstray.browser
+
+import androidx.annotation.CallSuper
+import androidx.recyclerview.widget.RecyclerView
+import mozilla.components.browser.tabstray.TabViewHolder
+import mozilla.components.browser.tabstray.TabsTrayStyling
+import mozilla.components.concept.tabstray.Tabs
+import mozilla.components.concept.tabstray.TabsTray
+import mozilla.components.support.base.observer.Observable
+import mozilla.components.support.base.observer.ObserverRegistry
+
+/**
+ * RecyclerView adapter implementation to display a list/grid of tabs.
+ *
+ * The previous tabs adapter was very restrictive and required Fenix to jump through
+ * may hoops to access and update certain methods. An abstract adapter is easier to manage
+ * for Android UI APIs.
+ *
+ * TODO Let's upstream this to AC with tests.
+ *
+ * @param delegate TabsTray.Observer registry to allow `TabsAdapter` to conform to `Observable`.
+ */
+abstract class TabsAdapter(
+ delegate: Observable = ObserverRegistry()
+) : RecyclerView.Adapter(), TabsTray, Observable by delegate {
+
+ protected var tabs: Tabs? = null
+ protected var styling: TabsTrayStyling = TabsTrayStyling()
+
+ @CallSuper
+ override fun updateTabs(tabs: Tabs) {
+ this.tabs = tabs
+
+ notifyObservers { onTabsUpdated() }
+ }
+
+ @CallSuper
+ override fun onBindViewHolder(holder: T, position: Int) {
+ val tabs = tabs ?: return
+
+ holder.bind(tabs.list[position], isTabSelected(tabs, position), styling, this)
+ }
+
+ override fun getItemCount(): Int = tabs?.list?.size ?: 0
+
+ final override fun isTabSelected(tabs: Tabs, position: Int): Boolean =
+ tabs.selectedIndex == position
+
+ final override fun onTabsChanged(position: Int, count: Int) =
+ notifyItemRangeChanged(position, count)
+
+ final override fun onTabsInserted(position: Int, count: Int) =
+ notifyItemRangeInserted(position, count)
+
+ final override fun onTabsMoved(fromPosition: Int, toPosition: Int) =
+ notifyItemMoved(fromPosition, toPosition)
+
+ final override fun onTabsRemoved(position: Int, count: Int) =
+ notifyItemRangeRemoved(position, count)
+}
diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/browser/TabsTouchHelper.kt b/app/src/main/java/org/mozilla/fenix/tabstray/browser/TabsTouchHelper.kt
new file mode 100644
index 000000000..25fc74c19
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/tabstray/browser/TabsTouchHelper.kt
@@ -0,0 +1,139 @@
+/* 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.tabstray.browser
+
+import android.graphics.Canvas
+import android.graphics.drawable.Drawable
+import androidx.appcompat.content.res.AppCompatResources
+import androidx.recyclerview.widget.ItemTouchHelper
+import androidx.recyclerview.widget.ItemTouchHelper.ACTION_STATE_IDLE
+import androidx.recyclerview.widget.RecyclerView
+import mozilla.components.browser.tabstray.TabTouchCallback
+import mozilla.components.concept.tabstray.TabsTray
+import mozilla.components.support.base.observer.Observable
+import mozilla.components.support.ktx.android.content.getColorFromAttr
+import mozilla.components.support.ktx.android.content.getDrawableWithTint
+import mozilla.components.support.ktx.android.util.dpToPx
+import org.mozilla.fenix.R
+import org.mozilla.fenix.home.sessioncontrol.SwipeToDeleteCallback
+
+/**
+ * A callback for consumers to know when a [RecyclerView.ViewHolder] is about to be touched.
+ * Return false if the custom behaviour should be ignored.
+ */
+typealias OnViewHolderTouched = (RecyclerView.ViewHolder) -> Boolean
+
+/**
+ * A callback for consumers to know when a [RecyclerView.ViewHolder] is about to be drawn.
+ * Return false if the custom drawing should be ignored.
+ */
+typealias OnViewHolderToDraw = (RecyclerView.ViewHolder) -> Boolean
+
+/**
+ * An [ItemTouchHelper] for handling tab swiping to delete.
+ *
+ * @param onViewHolderTouched See [OnViewHolderTouched].
+ */
+class TabsTouchHelper(
+ observable: Observable,
+ onViewHolderTouched: OnViewHolderTouched = { true },
+ onViewHolderDraw: OnViewHolderToDraw = { true },
+ delegate: Callback = TouchCallback(observable, onViewHolderTouched, onViewHolderDraw)
+) : ItemTouchHelper(delegate)
+
+/**
+ * An [ItemTouchHelper.Callback] for drawing custom layouts on [RecyclerView.ViewHolder] interactions.
+ *
+ * @param onViewHolderTouched invoked when a tab is about to be swiped. See [OnViewHolderTouched].
+ */
+class TouchCallback(
+ observable: Observable,
+ private val onViewHolderTouched: OnViewHolderTouched,
+ private val onViewHolderDraw: OnViewHolderToDraw
+) : TabTouchCallback(observable) {
+
+ override fun getMovementFlags(
+ recyclerView: RecyclerView,
+ viewHolder: RecyclerView.ViewHolder
+ ): Int {
+ if (!onViewHolderTouched.invoke(viewHolder)) {
+ return ItemTouchHelper.Callback.makeFlag(ACTION_STATE_IDLE, 0)
+ }
+
+ return super.getMovementFlags(recyclerView, viewHolder)
+ }
+
+ override fun onChildDraw(
+ c: Canvas,
+ recyclerView: RecyclerView,
+ viewHolder: RecyclerView.ViewHolder,
+ dX: Float,
+ dY: Float,
+ actionState: Int,
+ isCurrentlyActive: Boolean
+ ) {
+ super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive)
+
+ if (!onViewHolderDraw.invoke(viewHolder)) {
+ return
+ }
+
+ val icon = recyclerView.context.getDrawableWithTint(
+ R.drawable.ic_delete,
+ recyclerView.context.getColorFromAttr(R.attr.destructive)
+ )!!
+ val background = AppCompatResources.getDrawable(
+ recyclerView.context,
+ R.drawable.swipe_delete_background
+ )!!
+ val itemView = viewHolder.itemView
+ val iconLeft: Int
+ val iconRight: Int
+ val margin =
+ SwipeToDeleteCallback.MARGIN.dpToPx(recyclerView.resources.displayMetrics)
+ val iconWidth = icon.intrinsicWidth
+ val iconHeight = icon.intrinsicHeight
+ val cellHeight = itemView.bottom - itemView.top
+ val iconTop = itemView.top + (cellHeight - iconHeight) / 2
+ val iconBottom = iconTop + iconHeight
+
+ when {
+ dX > 0 -> { // Swiping to the right
+ iconLeft = itemView.left + margin
+ iconRight = itemView.left + margin + iconWidth
+ background.setBounds(
+ itemView.left, itemView.top,
+ (itemView.left + dX).toInt() + SwipeToDeleteCallback.BACKGROUND_CORNER_OFFSET,
+ itemView.bottom
+ )
+ icon.setBounds(iconLeft, iconTop, iconRight, iconBottom)
+ draw(background, icon, c)
+ }
+ dX < 0 -> { // Swiping to the left
+ iconLeft = itemView.right - margin - iconWidth
+ iconRight = itemView.right - margin
+ background.setBounds(
+ (itemView.right + dX).toInt() - SwipeToDeleteCallback.BACKGROUND_CORNER_OFFSET,
+ itemView.top, itemView.right, itemView.bottom
+ )
+ icon.setBounds(iconLeft, iconTop, iconRight, iconBottom)
+ draw(background, icon, c)
+ }
+ else -> { // View not swiped
+ background.setBounds(0, 0, 0, 0)
+ icon.setBounds(0, 0, 0, 0)
+ }
+ }
+ }
+
+ private fun draw(
+ background: Drawable,
+ icon: Drawable,
+ c: Canvas
+ ) {
+ background.draw(c)
+ icon.draw(c)
+ }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/browser/TabsTrayGridViewHolder.kt b/app/src/main/java/org/mozilla/fenix/tabstray/browser/TabsTrayGridViewHolder.kt
new file mode 100644
index 000000000..51915b03d
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/tabstray/browser/TabsTrayGridViewHolder.kt
@@ -0,0 +1,61 @@
+/* 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.tabstray.browser
+
+import android.view.View
+import androidx.appcompat.content.res.AppCompatResources
+import androidx.appcompat.widget.AppCompatImageButton
+import mozilla.components.browser.tabstray.TabsTrayStyling
+import mozilla.components.concept.base.images.ImageLoader
+import mozilla.components.concept.tabstray.Tab
+import mozilla.components.concept.tabstray.TabsTray
+import mozilla.components.support.base.observer.Observable
+import org.mozilla.fenix.R
+import org.mozilla.fenix.ext.increaseTapArea
+import kotlin.math.max
+import kotlinx.android.synthetic.main.tab_tray_grid_item.view.tab_tray_grid_item
+import org.mozilla.fenix.tabstray.TabsTrayViewHolder
+import org.mozilla.fenix.selection.SelectionHolder
+import org.mozilla.fenix.tabstray.TabsTrayStore
+
+/**
+ * A RecyclerView ViewHolder implementation for "tab" items with grid layout.
+ */
+class TabsTrayGridViewHolder(
+ imageLoader: ImageLoader,
+ override val browserTrayInteractor: BrowserTrayInteractor,
+ store: TabsTrayStore,
+ selectionHolder: SelectionHolder? = null,
+ itemView: View
+) : TabsTrayViewHolder(itemView, imageLoader, store, selectionHolder) {
+
+ private val closeButton: AppCompatImageButton = itemView.findViewById(R.id.mozac_browser_tabstray_close)
+
+ override val thumbnailSize: Int
+ get() = max(
+ itemView.resources.getDimensionPixelSize(R.dimen.tab_tray_grid_item_thumbnail_height),
+ itemView.resources.getDimensionPixelSize(R.dimen.tab_tray_grid_item_thumbnail_width)
+ )
+
+ override fun updateSelectedTabIndicator(showAsSelected: Boolean) {
+ itemView.tab_tray_grid_item.background = if (showAsSelected) {
+ AppCompatResources.getDrawable(itemView.context, R.drawable.tab_tray_grid_item_selected_border)
+ } else {
+ null
+ }
+ return
+ }
+
+ override fun bind(
+ tab: Tab,
+ isSelected: Boolean,
+ styling: TabsTrayStyling,
+ observable: Observable
+ ) {
+ super.bind(tab, isSelected, styling, observable)
+
+ closeButton.increaseTapArea(GRID_ITEM_CLOSE_BUTTON_EXTRA_DPS)
+ }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/browser/TabsTrayListViewHolder.kt b/app/src/main/java/org/mozilla/fenix/tabstray/browser/TabsTrayListViewHolder.kt
new file mode 100644
index 000000000..cc65f6846
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/tabstray/browser/TabsTrayListViewHolder.kt
@@ -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.tabstray.browser
+
+import android.view.View
+import androidx.core.content.ContextCompat
+import mozilla.components.concept.base.images.ImageLoader
+import mozilla.components.concept.tabstray.Tab
+import org.mozilla.fenix.R
+import org.mozilla.fenix.tabstray.TabsTrayViewHolder
+import org.mozilla.fenix.selection.SelectionHolder
+import org.mozilla.fenix.tabstray.TabsTrayStore
+import kotlin.math.max
+
+/**
+ * A RecyclerView ViewHolder implementation for "tab" items with list layout.
+ */
+class TabsTrayListViewHolder(
+ imageLoader: ImageLoader,
+ override val browserTrayInteractor: BrowserTrayInteractor,
+ store: TabsTrayStore,
+ selectionHolder: SelectionHolder? = null,
+ itemView: View
+) : TabsTrayViewHolder(itemView, imageLoader, store, selectionHolder) {
+ override val thumbnailSize: Int
+ get() = max(
+ itemView.resources.getDimensionPixelSize(R.dimen.tab_tray_list_item_thumbnail_height),
+ itemView.resources.getDimensionPixelSize(R.dimen.tab_tray_list_item_thumbnail_width)
+ )
+
+ override fun updateSelectedTabIndicator(showAsSelected: Boolean) {
+ val color = if (showAsSelected) {
+ R.color.tab_tray_item_selected_background_normal_theme
+ } else {
+ R.color.tab_tray_item_background_normal_theme
+ }
+ itemView.setBackgroundColor(
+ ContextCompat.getColor(
+ itemView.context,
+ color
+ )
+ )
+ }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/browser/UseCases.kt b/app/src/main/java/org/mozilla/fenix/tabstray/browser/UseCases.kt
new file mode 100644
index 000000000..7dbfaa457
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/tabstray/browser/UseCases.kt
@@ -0,0 +1,31 @@
+/* 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.tabstray.browser
+
+import mozilla.components.feature.tabs.TabsUseCases
+import org.mozilla.fenix.components.metrics.Event
+import org.mozilla.fenix.components.metrics.MetricController
+
+class SelectTabUseCaseWrapper(
+ private val metrics: MetricController,
+ private val selectTab: TabsUseCases.SelectTabUseCase,
+ private val onSelect: (String) -> Unit
+) : TabsUseCases.SelectTabUseCase {
+ override fun invoke(tabId: String) {
+ metrics.track(Event.OpenedExistingTab)
+ selectTab(tabId)
+ onSelect(tabId)
+ }
+}
+
+class RemoveTabUseCaseWrapper(
+ private val metrics: MetricController,
+ private val onRemove: (String) -> Unit
+) : TabsUseCases.RemoveTabUseCase {
+ override fun invoke(sessionId: String) {
+ metrics.track(Event.ClosedExistingTab)
+ onRemove(sessionId)
+ }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/ext/BrowserMenu.kt b/app/src/main/java/org/mozilla/fenix/tabstray/ext/BrowserMenu.kt
new file mode 100644
index 000000000..ec98a59ec
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/tabstray/ext/BrowserMenu.kt
@@ -0,0 +1,25 @@
+/* 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.tabstray.ext
+
+import android.view.View
+import androidx.cardview.widget.CardView
+import androidx.core.content.ContextCompat
+import mozilla.components.browser.menu.BrowserMenu
+import org.mozilla.fenix.R
+
+/**
+ * Invokes [BrowserMenu.show] and applies the default theme color background.
+ */
+fun BrowserMenu.showWithTheme(view: View) {
+ show(view).also { popupMenu ->
+ (popupMenu.contentView as? CardView)?.setCardBackgroundColor(
+ ContextCompat.getColor(
+ view.context,
+ R.color.foundation_normal_theme
+ )
+ )
+ }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/ext/BrowserStore.kt b/app/src/main/java/org/mozilla/fenix/tabstray/ext/BrowserStore.kt
new file mode 100644
index 000000000..c5577a13e
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/tabstray/ext/BrowserStore.kt
@@ -0,0 +1,19 @@
+/* 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.tabstray.ext
+
+import mozilla.components.browser.state.selector.findTab
+import mozilla.components.browser.state.state.TabSessionState
+import mozilla.components.browser.state.store.BrowserStore
+import mozilla.components.concept.tabstray.Tab
+
+/**
+ * Find and extract a list [TabSessionState] from the [BrowserStore] using the IDs from [tabs].
+ */
+fun BrowserStore.getTabSessionState(tabs: Collection): List {
+ return tabs.mapNotNull {
+ state.findTab(it.id)
+ }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/ext/Context.kt b/app/src/main/java/org/mozilla/fenix/tabstray/ext/Context.kt
new file mode 100644
index 000000000..82f522ec6
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/tabstray/ext/Context.kt
@@ -0,0 +1,19 @@
+/* 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.tabstray.ext
+
+import android.content.Context
+
+private const val MIN_COLUMN_WIDTH_DP = 180
+
+/**
+ * Returns the number of grid columns we can fit on the screen in the tabs tray.
+ */
+internal val Context.numberOfGridColumns: Int
+ get() {
+ val displayMetrics = resources.displayMetrics
+ val screenWidthDp = displayMetrics.widthPixels / displayMetrics.density
+ return (screenWidthDp / MIN_COLUMN_WIDTH_DP).toInt()
+ }
diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/ext/TabLayout.kt b/app/src/main/java/org/mozilla/fenix/tabstray/ext/TabLayout.kt
new file mode 100644
index 000000000..dfba62b0d
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/tabstray/ext/TabLayout.kt
@@ -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.tabstray.ext
+
+import com.google.android.material.tabs.TabLayout
+import org.mozilla.fenix.tabstray.TrayPagerAdapter
+
+fun TabLayout.isNormalModeSelected(): Boolean {
+ return selectedTabPosition == TrayPagerAdapter.POSITION_NORMAL_TABS
+}
+
+fun TabLayout.isPrivateModeSelected(): Boolean {
+ return selectedTabPosition == TrayPagerAdapter.POSITION_PRIVATE_TABS
+}
+
+fun TabLayout.isSyncedModeSelected(): Boolean {
+ return selectedTabPosition == TrayPagerAdapter.POSITION_SYNCED_TABS
+}
diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/ext/TabSessionState.kt b/app/src/main/java/org/mozilla/fenix/tabstray/ext/TabSessionState.kt
index cf11c8c39..777e4f47e 100644
--- a/app/src/main/java/org/mozilla/fenix/tabstray/ext/TabSessionState.kt
+++ b/app/src/main/java/org/mozilla/fenix/tabstray/ext/TabSessionState.kt
@@ -5,6 +5,7 @@
package org.mozilla.fenix.tabstray.ext
import mozilla.components.browser.state.state.TabSessionState
+import org.mozilla.fenix.tabstray.Page
import org.mozilla.fenix.tabstray.browser.BaseBrowserTrayList.BrowserTabType.PRIVATE
import org.mozilla.fenix.tabstray.browser.BaseBrowserTrayList.Configuration
@@ -13,3 +14,9 @@ fun TabSessionState.filterFromConfig(configuration: Configuration): Boolean {
return content.private == isPrivate
}
+
+fun TabSessionState.getTrayPosition(): Int =
+ when (content.private) {
+ true -> Page.NormalTabs.ordinal
+ false -> Page.NormalTabs.ordinal
+ }
diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/ext/TabsTrayState.kt b/app/src/main/java/org/mozilla/fenix/tabstray/ext/TabsTrayState.kt
new file mode 100644
index 000000000..96434cc5d
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/tabstray/ext/TabsTrayState.kt
@@ -0,0 +1,12 @@
+/* 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.tabstray.ext
+
+import org.mozilla.fenix.tabstray.TabsTrayState.Mode
+
+/**
+ * A helper to check if we're in [Mode.Select] mode.
+ */
+fun Mode.isSelect() = this is Mode.Select
diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/syncedtabs/SyncButtonBinding.kt b/app/src/main/java/org/mozilla/fenix/tabstray/syncedtabs/SyncButtonBinding.kt
new file mode 100644
index 000000000..e07c7008f
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/tabstray/syncedtabs/SyncButtonBinding.kt
@@ -0,0 +1,35 @@
+/* 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.tabstray.syncedtabs
+
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.map
+import mozilla.components.feature.syncedtabs.view.SyncedTabsView
+import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged
+import org.mozilla.fenix.components.AbstractBinding
+import org.mozilla.fenix.tabstray.TabsTrayState
+import org.mozilla.fenix.tabstray.TabsTrayStore
+
+/**
+ * An [AbstractBinding] that invokes the [onSyncNow] callback when the [TabsTrayState.syncing] is
+ * set.
+ *
+ * This binding is useful for connecting with [SyncedTabsView.Listener].
+ */
+class SyncButtonBinding(
+ tabsTrayStore: TabsTrayStore,
+ private val onSyncNow: () -> Unit
+) : AbstractBinding(tabsTrayStore) {
+ override suspend fun onState(flow: Flow) {
+ flow.map { it.syncing }
+ .ifChanged()
+ .collect { syncingNow ->
+ if (syncingNow) {
+ onSyncNow()
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/syncedtabs/SyncedTabsTrayLayout.kt b/app/src/main/java/org/mozilla/fenix/tabstray/syncedtabs/SyncedTabsTrayLayout.kt
new file mode 100644
index 000000000..d659f28a9
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/tabstray/syncedtabs/SyncedTabsTrayLayout.kt
@@ -0,0 +1,117 @@
+/* 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.tabstray.syncedtabs
+
+import android.content.Context
+import android.util.AttributeSet
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.fragment.app.findFragment
+import androidx.navigation.NavController
+import androidx.navigation.fragment.findNavController
+import kotlinx.android.synthetic.main.component_sync_tabs_tray_layout.view.*
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.launch
+import mozilla.components.browser.storage.sync.SyncedDeviceTabs
+import mozilla.components.feature.syncedtabs.SyncedTabsFeature
+import mozilla.components.feature.syncedtabs.view.SyncedTabsView
+import mozilla.components.support.base.observer.Observable
+import mozilla.components.support.base.observer.ObserverRegistry
+import org.mozilla.fenix.ext.components
+import org.mozilla.fenix.sync.SyncedTabsAdapter
+import org.mozilla.fenix.sync.ext.toAdapterItem
+import org.mozilla.fenix.sync.ext.toStringRes
+import org.mozilla.fenix.tabstray.TabsTrayAction
+import org.mozilla.fenix.tabstray.TabsTrayFragment
+import org.mozilla.fenix.tabstray.TabsTrayStore
+import org.mozilla.fenix.tabstray.TrayItem
+import org.mozilla.fenix.utils.view.LifecycleViewProvider
+
+class SyncedTabsTrayLayout @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0
+) : ConstraintLayout(context, attrs, defStyleAttr), SyncedTabsView, TrayItem,
+ Observable by ObserverRegistry() {
+
+ private val lifecycleProvider = LifecycleViewProvider(this)
+ private val coroutineScope = CoroutineScope(Dispatchers.Main)
+
+ private val syncedTabsFeature by lazy {
+ SyncedTabsFeature(
+ context = context,
+ storage = context.components.backgroundServices.syncedTabsStorage,
+ accountManager = context.components.backgroundServices.accountManager,
+ view = this,
+ lifecycleOwner = lifecycleProvider,
+ onTabClicked = {
+ // We can ignore this callback here because we're not connecting the adapter
+ // back to the feature. This works fine in other features, but passing the listener
+ // to other components in this case is annoying.
+ }
+ )
+ }
+
+ private val syncButtonBinding by lazy {
+ SyncButtonBinding(tabsTrayStore) {
+ listener?.onRefresh()
+ }
+ }
+
+ lateinit var tabsTrayStore: TabsTrayStore
+
+ override var listener: SyncedTabsView.Listener? = null
+
+ override fun displaySyncedTabs(syncedTabs: List) {
+ coroutineScope.launch {
+ (synced_tabs_list.adapter as SyncedTabsAdapter).updateData(syncedTabs)
+ }
+ }
+
+ override fun onError(error: SyncedTabsView.ErrorType) {
+ coroutineScope.launch {
+ // We may still be displaying a "loading" spinner, hide it.
+ stopLoading()
+
+ val navController: NavController? = try {
+ findFragment().findNavController()
+ } catch (exception: IllegalStateException) {
+ null
+ }
+
+ val descriptionResId = error.toStringRes()
+ val errorItem = error.toAdapterItem(descriptionResId, navController)
+
+ val errorList: List = listOf(errorItem)
+ (synced_tabs_list.adapter as SyncedTabsAdapter).submitList(errorList)
+ }
+ }
+
+ override fun onAttachedToWindow() {
+ super.onAttachedToWindow()
+
+ syncedTabsFeature.start()
+ syncButtonBinding.start()
+ }
+
+ override fun onDetachedFromWindow() {
+ super.onDetachedFromWindow()
+
+ syncedTabsFeature.stop()
+ syncButtonBinding.stop()
+
+ coroutineScope.cancel()
+ }
+
+ override fun stopLoading() {
+ tabsTrayStore.dispatch(TabsTrayAction.SyncCompleted)
+ }
+
+ /**
+ * Do nothing; the UI is handled with FloatingActionButtonBinding.
+ */
+ override fun startLoading() = Unit
+}
diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/syncedtabs/TabClickDelegate.kt b/app/src/main/java/org/mozilla/fenix/tabstray/syncedtabs/TabClickDelegate.kt
new file mode 100644
index 000000000..53f93ab6b
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/tabstray/syncedtabs/TabClickDelegate.kt
@@ -0,0 +1,22 @@
+/* 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.tabstray.syncedtabs
+
+import mozilla.components.browser.storage.sync.Tab
+import mozilla.components.feature.syncedtabs.view.SyncedTabsView
+import org.mozilla.fenix.tabstray.NavigationInteractor
+
+/**
+ * A wrapper class that handles tab clicks from a Synced Tabs list.
+ */
+class TabClickDelegate(
+ private val interactor: NavigationInteractor
+) : SyncedTabsView.Listener {
+ override fun onTabClicked(tab: Tab) {
+ interactor.onSyncedTabClicked(tab)
+ }
+
+ override fun onRefresh() = Unit
+}
diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/viewholders/AbstractTrayViewHolder.kt b/app/src/main/java/org/mozilla/fenix/tabstray/viewholders/AbstractTrayViewHolder.kt
new file mode 100644
index 000000000..ebe7725e5
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/tabstray/viewholders/AbstractTrayViewHolder.kt
@@ -0,0 +1,23 @@
+/* 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.tabstray.viewholders
+
+import android.view.View
+import androidx.recyclerview.widget.RecyclerView
+import kotlinx.android.extensions.LayoutContainer
+import org.mozilla.fenix.tabstray.TrayPagerAdapter
+
+/**
+ * An abstract [RecyclerView.ViewHolder] for [TrayPagerAdapter] items.
+ */
+abstract class AbstractTrayViewHolder constructor(
+ override val containerView: View
+) : RecyclerView.ViewHolder(containerView), LayoutContainer {
+
+ abstract fun bind(
+ adapter: RecyclerView.Adapter,
+ layoutManager: RecyclerView.LayoutManager
+ )
+}
diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/viewholders/BaseBrowserTabViewHolder.kt b/app/src/main/java/org/mozilla/fenix/tabstray/viewholders/BaseBrowserTabViewHolder.kt
new file mode 100644
index 000000000..faecdd921
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/tabstray/viewholders/BaseBrowserTabViewHolder.kt
@@ -0,0 +1,73 @@
+/* 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.tabstray.viewholders
+
+import android.view.View
+import android.view.View.GONE
+import android.view.View.VISIBLE
+import android.widget.TextView
+import androidx.annotation.CallSuper
+import androidx.recyclerview.widget.RecyclerView
+import org.mozilla.fenix.R
+import org.mozilla.fenix.tabstray.TabsTrayInteractor
+import org.mozilla.fenix.tabstray.TabsTrayStore
+import org.mozilla.fenix.tabstray.browser.BaseBrowserTrayList
+
+/**
+ * A shared view holder for browser tabs tray list.
+ */
+abstract class BaseBrowserTabViewHolder(
+ containerView: View,
+ tabsTrayStore: TabsTrayStore,
+ interactor: TabsTrayInteractor,
+ private val currentTabIndex: Int
+) : AbstractTrayViewHolder(containerView) {
+
+ private val trayList: BaseBrowserTrayList = itemView.findViewById(R.id.tray_list_item)
+ private val emptyList: TextView = itemView.findViewById(R.id.tab_tray_empty_view)
+ abstract val emptyStringText: String
+
+ init {
+ trayList.interactor = interactor
+ trayList.tabsTrayStore = tabsTrayStore
+ emptyList.text = emptyStringText
+ }
+
+ @CallSuper
+ override fun bind(
+ adapter: RecyclerView.Adapter,
+ layoutManager: RecyclerView.LayoutManager
+ ) {
+ adapter.registerAdapterDataObserver(OneTimeAdapterObserver(adapter) {
+ trayList.scrollToPosition(currentTabIndex)
+ updateTrayVisibility(adapter.itemCount)
+ })
+ trayList.layoutManager = layoutManager
+ trayList.adapter = adapter
+ }
+
+ private fun updateTrayVisibility(size: Int) {
+ if (size == 0) {
+ trayList.visibility = GONE
+ emptyList.visibility = VISIBLE
+ } else {
+ trayList.visibility = VISIBLE
+ emptyList.visibility = GONE
+ }
+ }
+}
+
+/**
+ * Observes the adapter and invokes the callback when data is first inserted.
+ */
+class OneTimeAdapterObserver(
+ private val adapter: RecyclerView.Adapter,
+ private val onAdapterReady: () -> Unit
+) : RecyclerView.AdapterDataObserver() {
+ override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
+ onAdapterReady.invoke()
+ adapter.unregisterAdapterDataObserver(this)
+ }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/viewholders/NormalBrowserTabViewHolder.kt b/app/src/main/java/org/mozilla/fenix/tabstray/viewholders/NormalBrowserTabViewHolder.kt
new file mode 100644
index 000000000..61a1f1a4e
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/tabstray/viewholders/NormalBrowserTabViewHolder.kt
@@ -0,0 +1,55 @@
+/* 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.tabstray.viewholders
+
+import android.view.View
+import androidx.recyclerview.widget.RecyclerView
+import mozilla.components.concept.tabstray.Tab
+import org.mozilla.fenix.R
+import org.mozilla.fenix.selection.SelectionHolder
+import org.mozilla.fenix.tabstray.TabsTrayInteractor
+import org.mozilla.fenix.tabstray.TabsTrayStore
+import org.mozilla.fenix.tabstray.browser.BrowserTabsAdapter
+
+/**
+ * View holder for the normal tabs tray list.
+ */
+class NormalBrowserTabViewHolder(
+ containerView: View,
+ private val store: TabsTrayStore,
+ interactor: TabsTrayInteractor,
+ currentTabIndex: Int
+) : BaseBrowserTabViewHolder(
+ containerView,
+ store,
+ interactor,
+ currentTabIndex
+), SelectionHolder {
+
+ /**
+ * Holds the list of selected tabs.
+ *
+ * Implementation notes: we do this here because we only want the normal tabs list to be able
+ * to select tabs.
+ */
+ override val selectedItems: Set
+ get() = store.state.mode.selectedTabs
+
+ override val emptyStringText: String
+ get() = itemView.resources.getString(R.string.no_open_tabs_description)
+
+ override fun bind(
+ adapter: RecyclerView.Adapter,
+ layoutManager: RecyclerView.LayoutManager
+ ) {
+ (adapter as BrowserTabsAdapter).selectionHolder = this
+
+ super.bind(adapter, layoutManager)
+ }
+
+ companion object {
+ const val LAYOUT_ID = R.layout.normal_browser_tray_list
+ }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/viewholders/PrivateBrowserTabViewHolder.kt b/app/src/main/java/org/mozilla/fenix/tabstray/viewholders/PrivateBrowserTabViewHolder.kt
new file mode 100644
index 000000000..b49bc8fc1
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/tabstray/viewholders/PrivateBrowserTabViewHolder.kt
@@ -0,0 +1,32 @@
+/* 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.tabstray.viewholders
+
+import android.view.View
+import org.mozilla.fenix.R
+import org.mozilla.fenix.tabstray.TabsTrayInteractor
+import org.mozilla.fenix.tabstray.TabsTrayStore
+
+/**
+ * View holder for the private tabs tray list.
+ */
+class PrivateBrowserTabViewHolder(
+ containerView: View,
+ store: TabsTrayStore,
+ interactor: TabsTrayInteractor,
+ currentTabIndex: Int
+) : BaseBrowserTabViewHolder(
+ containerView,
+ store,
+ interactor,
+ currentTabIndex
+) {
+ override val emptyStringText: String
+ get() = itemView.resources.getString(R.string.no_private_tabs_description)
+
+ companion object {
+ const val LAYOUT_ID = R.layout.private_browser_tray_list
+ }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/viewholders/SyncedTabViewHolder.kt b/app/src/main/java/org/mozilla/fenix/tabstray/viewholders/SyncedTabViewHolder.kt
new file mode 100644
index 000000000..066fce9fa
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/tabstray/viewholders/SyncedTabViewHolder.kt
@@ -0,0 +1,31 @@
+/* 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.tabstray.viewholders
+
+import android.view.View
+import androidx.recyclerview.widget.RecyclerView
+import kotlinx.android.synthetic.main.component_sync_tabs_tray_layout.*
+import org.mozilla.fenix.R
+import org.mozilla.fenix.tabstray.TabsTrayStore
+
+class SyncedTabViewHolder(
+ containerView: View,
+ private val tabsTrayStore: TabsTrayStore
+) : AbstractTrayViewHolder(containerView) {
+
+ override fun bind(
+ adapter: RecyclerView.Adapter,
+ layoutManager: RecyclerView.LayoutManager
+ ) {
+ synced_tabs_list.layoutManager = layoutManager
+ synced_tabs_list.adapter = adapter
+
+ synced_tabs_tray_layout.tabsTrayStore = tabsTrayStore
+ }
+
+ companion object {
+ const val LAYOUT_ID = R.layout.component_sync_tabs_tray_layout
+ }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayController.kt b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayController.kt
index 4ce4c635c..e6f2aa698 100644
--- a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayController.kt
+++ b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayController.kt
@@ -26,6 +26,7 @@ import org.mozilla.fenix.browser.browsingmode.BrowsingModeManager
import org.mozilla.fenix.components.TabCollectionStorage
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.components.metrics.MetricController
+import org.mozilla.fenix.ext.navigateBlockingForAsyncNavGraph
import org.mozilla.fenix.home.HomeFragment
/**
@@ -104,7 +105,8 @@ class DefaultTabTrayController(
override fun handleNewTabTapped(private: Boolean) {
val startTime = profiler?.getProfilerTime()
browsingModeManager.mode = BrowsingMode.fromBoolean(private)
- navController.navigate(TabTrayDialogFragmentDirections.actionGlobalHome(focusOnAddressBar = true))
+ navController.navigateBlockingForAsyncNavGraph(
+ TabTrayDialogFragmentDirections.actionGlobalHome(focusOnAddressBar = true))
dismissTabTray()
profiler?.addMarker(
"DefaultTabTrayController.onNewTabTapped",
@@ -113,7 +115,8 @@ class DefaultTabTrayController(
}
override fun handleTabSettingsClicked() {
- navController.navigate(TabTrayDialogFragmentDirections.actionGlobalTabSettingsFragment())
+ navController.navigateBlockingForAsyncNavGraph(
+ TabTrayDialogFragmentDirections.actionGlobalTabSettingsFragment())
}
override fun handleTabTrayDismissed() {
@@ -148,7 +151,7 @@ class DefaultTabTrayController(
val directions = TabTrayDialogFragmentDirections.actionGlobalShareFragment(
data = data.toTypedArray()
)
- navController.navigate(directions)
+ navController.navigateBlockingForAsyncNavGraph(directions)
}
override fun handleShareSelectedTabsClicked(selectedTabs: Set) {
@@ -158,7 +161,7 @@ class DefaultTabTrayController(
val directions = TabTrayDialogFragmentDirections.actionGlobalShareFragment(
data = data.toTypedArray()
)
- navController.navigate(directions)
+ navController.navigateBlockingForAsyncNavGraph(directions)
}
override fun handleBookmarkSelectedTabs(selectedTabs: Set) {
@@ -236,13 +239,13 @@ class DefaultTabTrayController(
override fun handleRecentlyClosedClicked() {
val directions = TabTrayDialogFragmentDirections.actionGlobalRecentlyClosed()
- navController.navigate(directions)
+ navController.navigateBlockingForAsyncNavGraph(directions)
metrics.track(Event.RecentlyClosedTabsOpened)
}
override fun handleGoToTabsSettingClicked() {
val directions = TabTrayDialogFragmentDirections.actionGlobalTabSettingsFragment()
- navController.navigate(directions)
+ navController.navigateBlockingForAsyncNavGraph(directions)
metrics.track(Event.TabsTrayCfrTapped)
}
}
diff --git a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayDialogFragment.kt b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayDialogFragment.kt
index ef6f3fe70..0c650171d 100644
--- a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayDialogFragment.kt
+++ b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayDialogFragment.kt
@@ -50,12 +50,14 @@ import mozilla.components.support.utils.ext.right
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.NavGraphDirections
import org.mozilla.fenix.R
+import org.mozilla.fenix.collections.CollectionsListAdapter
import org.mozilla.fenix.components.FenixSnackbar
import org.mozilla.fenix.components.StoreProvider
import org.mozilla.fenix.components.TabCollectionStorage
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.getDefaultCollectionNumber
+import org.mozilla.fenix.ext.navigateBlockingForAsyncNavGraph
import org.mozilla.fenix.ext.metrics
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.settings
@@ -326,7 +328,7 @@ class TabTrayDialogFragment : AppCompatDialogFragment(), UserInteractionHandler
private fun dismissTabTrayAndNavigateHome(sessionId: String) {
homeViewModel.sessionToDelete = sessionId
val directions = NavGraphDirections.actionGlobalHome()
- findNavController().navigate(directions)
+ findNavController().navigateBlockingForAsyncNavGraph(directions)
dismissAllowingStateLoss()
}
@@ -339,7 +341,7 @@ class TabTrayDialogFragment : AppCompatDialogFragment(), UserInteractionHandler
dismissAllowingStateLoss()
if (findNavController().currentDestination?.id == R.id.browserFragment) return
if (!findNavController().popBackStack(R.id.browserFragment, false)) {
- findNavController().navigate(R.id.browserFragment)
+ findNavController().navigateBlockingForAsyncNavGraph(R.id.browserFragment)
}
}
@@ -374,7 +376,7 @@ class TabTrayDialogFragment : AppCompatDialogFragment(), UserInteractionHandler
.setText(requireContext().getString(messageStringRes))
.setAction(requireContext().getString(R.string.create_collection_view)) {
dismissAllowingStateLoss()
- findNavController().navigate(
+ findNavController().navigateBlockingForAsyncNavGraph(
TabTrayDialogFragmentDirections.actionGlobalHome(
focusOnAddressBar = false,
focusOnCollection = collectionToSelect ?: -1L
@@ -398,7 +400,7 @@ class TabTrayDialogFragment : AppCompatDialogFragment(), UserInteractionHandler
.setText(requireContext().getString(R.string.snackbar_message_bookmarks_saved))
.setAction(requireContext().getString(R.string.snackbar_message_bookmarks_view)) {
dismissAllowingStateLoss()
- findNavController().navigate(
+ findNavController().navigateBlockingForAsyncNavGraph(
TabTrayDialogFragmentDirections.actionGlobalBookmarkFragment(BookmarkRoot.Mobile.id)
)
}
@@ -428,7 +430,7 @@ class TabTrayDialogFragment : AppCompatDialogFragment(), UserInteractionHandler
.setView(customLayout)
.setPositiveButton(android.R.string.ok) { dialog, _ ->
val selectedCollection =
- (list.adapter as CollectionsAdapter).getSelectedCollection()
+ (list.adapter as CollectionsListAdapter).getSelectedCollection()
val collection = tabCollectionStorage.cachedTabCollections[selectedCollection]
viewLifecycleOwner.lifecycleScope.launch(Main) {
tabCollectionStorage.addTabsToCollection(collection, sessionList)
@@ -448,7 +450,7 @@ class TabTrayDialogFragment : AppCompatDialogFragment(), UserInteractionHandler
val dialog = builder.create()
val adapter =
- CollectionsAdapter(arrayOf(it.getString(R.string.tab_tray_add_new_collection)) + collections) {
+ CollectionsListAdapter(arrayOf(it.getString(R.string.tab_tray_add_new_collection)) + collections) {
dialog.dismiss()
showAddNewCollectionDialog(sessionList)
}
diff --git a/app/src/main/java/org/mozilla/fenix/telemetry/TelemetryLifecycleObserver.kt b/app/src/main/java/org/mozilla/fenix/telemetry/TelemetryLifecycleObserver.kt
new file mode 100644
index 000000000..ec86ae2f4
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/telemetry/TelemetryLifecycleObserver.kt
@@ -0,0 +1,76 @@
+/* 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.telemetry
+
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleObserver
+import androidx.lifecycle.OnLifecycleEvent
+import mozilla.components.browser.state.store.BrowserStore
+import mozilla.components.support.base.android.Clock
+import org.mozilla.fenix.GleanMetrics.EngineTab as EngineMetrics
+import org.mozilla.fenix.GleanMetrics.EngineTab.foregroundMetricsKeys as MetricsKeys
+
+/**
+ * [LifecycleObserver] to used on the process lifecycle to measure the amount of tabs getting killed
+ * while the app is in the background.
+ *
+ * See:
+ * - https://github.com/mozilla-mobile/android-components/issues/9624
+ * - https://github.com/mozilla-mobile/android-components/issues/9997
+ */
+class TelemetryLifecycleObserver(
+ private val store: BrowserStore
+) : LifecycleObserver {
+ private var pausedState: TabState? = null
+
+ @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
+ fun onPause() {
+ pausedState = createTabState()
+ }
+
+ @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
+ fun onResume() {
+ val lastState = pausedState ?: return
+ val currentState = createTabState()
+
+ EngineMetrics.foregroundMetrics.record(mapOf(
+ MetricsKeys.backgroundActiveTabs to lastState.activeEngineTabs.toString(),
+ MetricsKeys.backgroundCrashedTabs to lastState.crashedTabs.toString(),
+ MetricsKeys.backgroundTotalTabs to lastState.totalTabs.toString(),
+ MetricsKeys.foregroundActiveTabs to currentState.activeEngineTabs.toString(),
+ MetricsKeys.foregroundCrashedTabs to currentState.crashedTabs.toString(),
+ MetricsKeys.foregroundTotalTabs to currentState.totalTabs.toString(),
+ MetricsKeys.timeInBackground to (currentState.timestamp - lastState.timestamp).toString()
+ ))
+
+ pausedState = null
+ }
+
+ private fun createTabState(): TabState {
+ val tabsWithEngineSession = store.state.tabs
+ .filter { tab -> tab.engineState.engineSession != null }
+ .filter { tab -> !tab.engineState.crashed }
+ .count()
+
+ val totalTabs = store.state.tabs.count()
+
+ val crashedTabs = store.state.tabs
+ .filter { tab -> tab.engineState.crashed }
+ .count()
+
+ return TabState(
+ activeEngineTabs = tabsWithEngineSession,
+ totalTabs = totalTabs,
+ crashedTabs = crashedTabs
+ )
+ }
+}
+
+private data class TabState(
+ val timestamp: Long = Clock.elapsedRealtime(),
+ val totalTabs: Int,
+ val crashedTabs: Int,
+ val activeEngineTabs: Int
+)
diff --git a/app/src/main/java/org/mozilla/fenix/TelemetryMiddleware.kt b/app/src/main/java/org/mozilla/fenix/telemetry/TelemetryMiddleware.kt
similarity index 99%
rename from app/src/main/java/org/mozilla/fenix/TelemetryMiddleware.kt
rename to app/src/main/java/org/mozilla/fenix/telemetry/TelemetryMiddleware.kt
index d4dad123e..0441a9a32 100644
--- a/app/src/main/java/org/mozilla/fenix/TelemetryMiddleware.kt
+++ b/app/src/main/java/org/mozilla/fenix/telemetry/TelemetryMiddleware.kt
@@ -2,7 +2,7 @@
* 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
+package org.mozilla.fenix.telemetry
import androidx.annotation.VisibleForTesting
import mozilla.components.browser.state.action.BrowserAction
diff --git a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt
index 6c4801216..c67a40e32 100644
--- a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt
+++ b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt
@@ -33,13 +33,17 @@ import org.mozilla.fenix.components.metrics.MozillaProductDetector
import org.mozilla.fenix.components.settings.counterPreference
import org.mozilla.fenix.components.settings.featureFlagPreference
import org.mozilla.fenix.components.toolbar.ToolbarPosition
+import org.mozilla.fenix.experiments.ExperimentBranch
+import org.mozilla.fenix.experiments.Experiments
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.getPreferenceKey
+import org.mozilla.fenix.ext.withExperiment
import org.mozilla.fenix.settings.PhoneFeature
import org.mozilla.fenix.settings.deletebrowsingdata.DeleteBrowsingDataOnQuitType
import org.mozilla.fenix.settings.logins.SavedLoginsSortingStrategyMenu
import org.mozilla.fenix.settings.logins.SortingStrategy
import org.mozilla.fenix.settings.registerOnSharedPreferenceChangeListener
+import org.mozilla.fenix.settings.sitepermissions.AUTOPLAY_BLOCK_ALL
import java.security.InvalidParameterException
private const val AUTOPLAY_USER_SETTING = "AUTOPLAY_USER_SETTING"
@@ -60,6 +64,7 @@ class Settings(private val appContext: Context) : PreferencesHolder {
private const val ALLOWED_INT = 2
private const val CFR_COUNT_CONDITION_FOCUS_INSTALLED = 1
private const val CFR_COUNT_CONDITION_FOCUS_NOT_INSTALLED = 3
+ private const val APP_LAUNCHES_TO_SHOW_DEFAULT_BROWSER_CARD = 3
const val ONE_DAY_MS = 60 * 60 * 24 * 1000L
const val THREE_DAYS_MS = 3 * ONE_DAY_MS
@@ -291,6 +296,31 @@ class Settings(private val appContext: Context) : PreferencesHolder {
default = false
)
+ /**
+ * Shows if the user has chosen to close the set default browser experiment card
+ * on home screen or has clicked the set as default browser button.
+ */
+ var userDismissedExperimentCard by booleanPreference(
+ appContext.getPreferenceKey(R.string.pref_key_experiment_card_home),
+ default = false
+ )
+
+ /**
+ * Shows if the set default browser experiment card should be shown on home screen.
+ */
+ fun shouldShowSetAsDefaultBrowserCard(): Boolean {
+ val browsers = BrowsersCache.all(appContext)
+ val experiments = appContext.components.analytics.experiments
+ val isExperimentBranch =
+ experiments.withExperiment(Experiments.DEFAULT_BROWSER) { experimentBranch ->
+ (experimentBranch == ExperimentBranch.DEFAULT_BROWSER_NEW_TAB_BANNER)
+ }
+ return isExperimentBranch &&
+ !userDismissedExperimentCard &&
+ !browsers.isFirefoxDefaultBrowser &&
+ numberOfAppLaunches > APP_LAUNCHES_TO_SHOW_DEFAULT_BROWSER_CARD
+ }
+
var listTabView by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_tab_view_list),
default = true
@@ -323,7 +353,7 @@ class Settings(private val appContext: Context) : PreferencesHolder {
var tabsTrayRewrite by featureFlagPreference(
appContext.getPreferenceKey(R.string.pref_key_new_tabs_tray),
- default = false,
+ default = true,
featureFlag = FeatureFlags.tabsTrayRewrite
)
@@ -754,9 +784,7 @@ class Settings(private val appContext: Context) : PreferencesHolder {
* either [AUTOPLAY_ALLOW_ALL] or [AUTOPLAY_BLOCK_ALL]. Because of this, we are forced to save
* the user selected setting as well.
*/
- fun getAutoplayUserSetting(
- default: Int
- ) = preferences.getInt(AUTOPLAY_USER_SETTING, default)
+ fun getAutoplayUserSetting() = preferences.getInt(AUTOPLAY_USER_SETTING, AUTOPLAY_BLOCK_ALL)
private fun getSitePermissionsPhoneFeatureAutoplayAction(
feature: PhoneFeature,
@@ -833,11 +861,6 @@ class Settings(private val appContext: Context) : PreferencesHolder {
default = true
)
- var fxaSignedIn by booleanPreference(
- appContext.getPreferenceKey(R.string.pref_key_fxa_signed_in),
- default = false
- )
-
var fxaHasSyncedItems by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_fxa_has_synced_items),
default = false
@@ -898,6 +921,11 @@ class Settings(private val appContext: Context) : PreferencesHolder {
default = false
)
+ var allowDomesticChinaFxaServer by booleanPreference(
+ appContext.getPreferenceKey(R.string.pref_key_allow_domestic_china_fxa_server),
+ default = true
+ )
+
var overrideFxAServer by stringPreference(
appContext.getPreferenceKey(R.string.pref_key_override_fxa_server),
default = ""
@@ -1040,4 +1068,13 @@ class Settings(private val appContext: Context) : PreferencesHolder {
default = false,
featureFlag = FeatureFlags.addressesFeature
)
+
+ /**
+ * Storing desktop item checkbox value in the home screen menu.
+ * If set to true, next opened tab from home screen will be opened in desktop mode.
+ */
+ var openNextTabInDesktopMode by booleanPreference(
+ appContext.getPreferenceKey(R.string.pref_key_open_next_tab_desktop_mode),
+ default = false
+ )
}
diff --git a/app/src/main/java/org/mozilla/fenix/utils/view/LifecycleViewProvider.kt b/app/src/main/java/org/mozilla/fenix/utils/view/LifecycleViewProvider.kt
index 010f82b6f..b06c46768 100644
--- a/app/src/main/java/org/mozilla/fenix/utils/view/LifecycleViewProvider.kt
+++ b/app/src/main/java/org/mozilla/fenix/utils/view/LifecycleViewProvider.kt
@@ -14,7 +14,7 @@ import androidx.lifecycle.LifecycleRegistry
/**
* Provides a [LifecycleOwner] on a given [View] for features that function on lifecycle events.
*
- * When the [View] is attached to the window, observers will receive the [Lifecycle.Event.ON_START] event.
+ * When the [View] is attached to the window, observers will receive the [Lifecycle.Event.ON_RESUME] event.
* When the [View] is detached to the window, observers will receive the [Lifecycle.Event.ON_STOP] event.
*
* @param view The [View] that will be observed.
@@ -36,7 +36,7 @@ internal class ViewBinding(
private val registry: LifecycleRegistry
) : View.OnAttachStateChangeListener {
override fun onViewAttachedToWindow(v: View?) {
- registry.currentState = State.STARTED
+ registry.currentState = State.RESUMED
}
override fun onViewDetachedFromWindow(v: View?) {
diff --git a/app/src/main/java/org/mozilla/fenix/wifi/SitePermissionsWifiIntegration.kt b/app/src/main/java/org/mozilla/fenix/wifi/SitePermissionsWifiIntegration.kt
index eec7d25e6..603c76bdc 100644
--- a/app/src/main/java/org/mozilla/fenix/wifi/SitePermissionsWifiIntegration.kt
+++ b/app/src/main/java/org/mozilla/fenix/wifi/SitePermissionsWifiIntegration.kt
@@ -4,11 +4,12 @@
package org.mozilla.fenix.wifi
-import mozilla.components.feature.sitepermissions.SitePermissionsRules
+import androidx.annotation.VisibleForTesting
+import mozilla.components.feature.sitepermissions.SitePermissionsRules.Action.ALLOWED
+import mozilla.components.feature.sitepermissions.SitePermissionsRules.Action.BLOCKED
import mozilla.components.support.base.feature.LifecycleAwareFeature
import org.mozilla.fenix.settings.PhoneFeature
import org.mozilla.fenix.settings.sitepermissions.AUTOPLAY_ALLOW_ON_WIFI
-import org.mozilla.fenix.settings.sitepermissions.AUTOPLAY_BLOCK_ALL
import org.mozilla.fenix.utils.Settings
/**
@@ -24,11 +25,11 @@ class SitePermissionsWifiIntegration(
* Adds listener for autoplay setting [AUTOPLAY_ALLOW_ON_WIFI]. Sets all autoplay to allowed when
* WIFI is connected, blocked otherwise.
*/
- private val wifiConnectedListener: ((Boolean) -> Unit) by lazy {
+ @VisibleForTesting
+ internal val wifiConnectedListener: ((Boolean) -> Unit) by lazy {
{ connected: Boolean ->
- val setting =
- if (connected) SitePermissionsRules.Action.ALLOWED else SitePermissionsRules.Action.BLOCKED
- if (settings.getAutoplayUserSetting(default = AUTOPLAY_BLOCK_ALL) == AUTOPLAY_ALLOW_ON_WIFI) {
+ if (settings.getAutoplayUserSetting() == AUTOPLAY_ALLOW_ON_WIFI) {
+ val setting = if (connected) ALLOWED else BLOCKED
settings.setSitePermissionsPhoneFeatureAction(
PhoneFeature.AUTOPLAY_AUDIBLE,
setting
@@ -39,21 +40,11 @@ class SitePermissionsWifiIntegration(
)
} else {
// The autoplay setting has changed, we can remove the listener
- removeWifiConnectedListener()
+ stop()
}
}
}
- /**
- * If autoplay is only enabled on WIFI, sets a WIFI listener to set them accordingly. Otherwise
- * noop.
- */
- fun maybeAddWifiConnectedListener() {
- if (settings.getAutoplayUserSetting(default = AUTOPLAY_BLOCK_ALL) == AUTOPLAY_ALLOW_ON_WIFI) {
- addWifiConnectedListener()
- }
- }
-
fun addWifiConnectedListener() {
wifiConnectionMonitor.addOnWifiConnectedChangedListener(wifiConnectedListener)
}
@@ -62,15 +53,15 @@ class SitePermissionsWifiIntegration(
wifiConnectionMonitor.removeOnWifiConnectedChangedListener(wifiConnectedListener)
}
- // Until https://bugzilla.mozilla.org/show_bug.cgi?id=1621825 is fixed, AUTOPLAY_ALLOW_ALL
- // only works while WIFI is active, so we are not using AUTOPLAY_ALLOW_ON_WIFI (or this class).
- // Once that is fixed, [start] and [maybeAddWifiConnectedListener] will need to be called on
- // activity startup.
override fun start() {
- wifiConnectionMonitor.start()
+ if (settings.getAutoplayUserSetting() == AUTOPLAY_ALLOW_ON_WIFI) {
+ wifiConnectionMonitor.start()
+ addWifiConnectedListener()
+ }
}
override fun stop() {
wifiConnectionMonitor.stop()
+ removeWifiConnectedListener()
}
}
diff --git a/app/src/main/java/org/mozilla/fenix/wifi/WifiConnectionMonitor.kt b/app/src/main/java/org/mozilla/fenix/wifi/WifiConnectionMonitor.kt
index 4db646e12..682a2f1e0 100644
--- a/app/src/main/java/org/mozilla/fenix/wifi/WifiConnectionMonitor.kt
+++ b/app/src/main/java/org/mozilla/fenix/wifi/WifiConnectionMonitor.kt
@@ -10,6 +10,7 @@ import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkRequest
+import androidx.annotation.VisibleForTesting
/**
* Attaches itself to the [Application] and listens for WIFI available/not available events. This
@@ -26,20 +27,28 @@ import android.net.NetworkRequest
* ```
*/
class WifiConnectionMonitor(app: Application) {
- private val callbacks = mutableListOf<(Boolean) -> Unit>()
- private val connectivityManager = app.getSystemService(Context.CONNECTIVITY_SERVICE) as
+ @VisibleForTesting
+ internal val callbacks = mutableListOf<(Boolean) -> Unit>()
+
+ @VisibleForTesting
+ internal var connectivityManager = app.getSystemService(Context.CONNECTIVITY_SERVICE) as
ConnectivityManager
- private var lastKnownStateWasAvailable: Boolean? = null
- private var isRegistered = false
+ @VisibleForTesting
+ internal var lastKnownStateWasAvailable: Boolean? = null
+
+ @VisibleForTesting
+ internal var isRegistered = false
+ @Synchronized set
- private val frameworkListener = object : ConnectivityManager.NetworkCallback() {
- override fun onLost(network: Network?) {
+ @VisibleForTesting
+ internal val frameworkListener = object : ConnectivityManager.NetworkCallback() {
+ override fun onLost(network: Network) {
notifyListeners(false)
lastKnownStateWasAvailable = false
}
- override fun onAvailable(network: Network?) {
+ override fun onAvailable(network: Network) {
notifyListeners(true)
lastKnownStateWasAvailable = true
}
@@ -86,6 +95,8 @@ class WifiConnectionMonitor(app: Application) {
if (!isRegistered) return
connectivityManager.unregisterNetworkCallback(frameworkListener)
isRegistered = false
+ lastKnownStateWasAvailable = null
+ callbacks.clear()
}
/**
diff --git a/app/src/main/res/animator/fill_disable.xml b/app/src/main/res/animator/fill_disable.xml
deleted file mode 100644
index c0887a6ac..000000000
--- a/app/src/main/res/animator/fill_disable.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
diff --git a/app/src/main/res/animator/fill_enable.xml b/app/src/main/res/animator/fill_enable.xml
deleted file mode 100644
index 52d745f77..000000000
--- a/app/src/main/res/animator/fill_enable.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
diff --git a/app/src/main/res/animator/strike_thru_mask_disable.xml b/app/src/main/res/animator/strike_thru_mask_disable.xml
deleted file mode 100644
index 09049a7b6..000000000
--- a/app/src/main/res/animator/strike_thru_mask_disable.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
diff --git a/app/src/main/res/animator/strike_thru_mask_enable.xml b/app/src/main/res/animator/strike_thru_mask_enable.xml
deleted file mode 100644
index e45b8c676..000000000
--- a/app/src/main/res/animator/strike_thru_mask_enable.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
diff --git a/app/src/main/res/animator/strike_thru_path_disable.xml b/app/src/main/res/animator/strike_thru_path_disable.xml
deleted file mode 100644
index b56cca0e2..000000000
--- a/app/src/main/res/animator/strike_thru_path_disable.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
-
-
diff --git a/app/src/main/res/animator/strike_thru_path_enable.xml b/app/src/main/res/animator/strike_thru_path_enable.xml
deleted file mode 100644
index 8ea41146c..000000000
--- a/app/src/main/res/animator/strike_thru_path_enable.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
-
-
diff --git a/app/src/main/res/drawable-v24/shield_dark.xml b/app/src/main/res/drawable-v24/shield_dark.xml
index a40e505f4..0e322f569 100644
--- a/app/src/main/res/drawable-v24/shield_dark.xml
+++ b/app/src/main/res/drawable-v24/shield_dark.xml
@@ -13,23 +13,27 @@
android:viewportHeight="24">
+
+ android:pathData="M12.0002,22C11.5342,22 11.0662,21.885 10.6462,21.668C8.4962,20.547 6.5242,18.735 5.0942,16.566C4.3852,15.493 3.8882,14.198 3.6162,12.716L2.9092,8.872C2.6692,7.563 3.2942,6.259 4.4632,5.625L10.5652,2.363C11.4632,1.88 12.5362,1.88 13.4322,2.359L19.5342,5.682C20.7082,6.313 21.3352,7.621 21.0912,8.936L20.3862,12.728C20.1152,14.201 19.6172,15.49 18.9112,16.56C17.4822,18.729 15.5102,20.542 13.3562,21.666C12.9372,21.885 12.4682,22 12.0002,22ZM11.5152,3.556L4.8452,7.13L4.3382,8.368L5.0922,12.445C5.3272,13.727 5.7502,14.836 6.3472,15.74C7.6392,17.7 9.4122,19.332 11.3392,20.337H12.6632C14.5932,19.329 16.3672,17.694 17.6582,15.735C18.2532,14.833 18.6752,13.729 18.9102,12.455L19.6632,8.368L19.0532,7.129L12.4942,3.557H11.5152V3.556Z">
+ android:endX="3"
+ android:endY="22"
+ android:startX="21"
+ android:startY="2"
+ android:type="linear">
+
+
+
@@ -41,8 +45,8 @@
android:duration="330"
android:interpolator="@android:interpolator/fast_out_slow_in"
android:propertyName="pathData"
- android:valueFrom="M 20 6 C 20 5 19.2 4.1 18.2 4 L 12 3 L 5.8 4 C 4.8 4 4 5 4 6 L 4.1 11 C 4.4 14.2 5.1 16 6.6 18 C 7.929 19.627 9.821 20.698 11.9 21 L 12.1 21 C 14.2 20.7 16.1 19.6 17.4 18 C 19 16 19.6 14.2 19.9 11 L 20 6 Z M 17.9 10.8 C 17.9 12.963 17.198 15.069 15.9 16.8 C 14.9 17.9 13.5 18.8 12 19.1 C 10.452 18.8 9.066 17.946 8.1 16.7 C 6.816 15 6.114 12.93 6.1 10.8 C 6.041 9.167 6.041 7.533 6.1 5.9 L 12 5 L 17.9 6 L 18 6.2 L 17.9 10.9 Z M 8 7.6 L 8 10.6 C 8.3 13.3 8.8 14.3 9.7 15.6 C 10.3 16.2 11.1 16.8 12 17 L 12 7 L 8 7.6 Z"
- android:valueTo="M 20 6 C 20 5 19.2 4.1 18.2 4 L 12 3 L 5.8 4 C 4.8 4 4 5 4 6 L 4.1 11 C 4.4 14.2 5.1 16 6.6 18 C 7.929 19.627 9.821 20.698 11.9 21 L 12.1 21 C 14.2 20.7 16.1 19.6 17.4 18 C 19 16 19.6 14.2 19.9 11 L 20 6 Z M 17.9 10.8 C 17.9 12.963 17.198 15.069 15.9 16.8 C 14.9 17.9 13.5 18.8 12 19.1 C 10.452 18.8 9.066 17.946 8.1 16.7 C 6.816 15 6.114 12.93 6.1 10.8 C 6.041 9.167 6.041 7.533 6.1 5.9 L 12 5 L 17.9 6 L 18 6.2 L 17.9 10.9 Z M 8 7.6 L 8 10.6 C 8.3 13.3 8.8 14.3 9.7 15.6 C 10.3 16.2 11.1 16.8 12 17 L 12 7 L 8 7.6 Z"
+ android:valueFrom="M12.0002,22C11.5342,22 11.0662,21.885 10.6462,21.668C8.4962,20.547 6.5242,18.735 5.0942,16.566C4.3852,15.493 3.8882,14.198 3.6162,12.716L2.9092,8.872C2.6692,7.563 3.2942,6.259 4.4632,5.625L10.5652,2.363C11.4632,1.88 12.5362,1.88 13.4322,2.359L19.5342,5.682C20.7082,6.313 21.3352,7.621 21.0912,8.936L20.3862,12.728C20.1152,14.201 19.6172,15.49 18.9112,16.56C17.4822,18.729 15.5102,20.542 13.3562,21.666C12.9372,21.885 12.4682,22 12.0002,22ZM11.5152,3.556L4.8452,7.13L4.3382,8.368L5.0922,12.445C5.3272,13.727 5.7502,14.836 6.3472,15.74C7.6392,17.7 9.4122,19.332 11.3392,20.337H12.6632C14.5932,19.329 16.3672,17.694 17.6582,15.735C18.2532,14.833 18.6752,13.729 18.9102,12.455L19.6632,8.368L19.0532,7.129L12.4942,3.557H11.5152V3.556Z"
+ android:valueTo="M12.0002,22C11.5342,22 11.0662,21.885 10.6462,21.668C8.4962,20.547 6.5242,18.735 5.0942,16.566C4.3852,15.493 3.8882,14.198 3.6162,12.716L2.9092,8.872C2.6692,7.563 3.2942,6.259 4.4632,5.625L10.5652,2.363C11.4632,1.88 12.5362,1.88 13.4322,2.359L19.5342,5.682C20.7082,6.313 21.3352,7.621 21.0912,8.936L20.3862,12.728C20.1152,14.201 19.6172,15.49 18.9112,16.56C17.4822,18.729 15.5102,20.542 13.3562,21.666C12.9372,21.885 12.4682,22 12.0002,22ZM11.5152,3.556L4.8452,7.13L4.3382,8.368L5.0922,12.445C5.3272,13.727 5.7502,14.836 6.3472,15.74C7.6392,17.7 9.4122,19.332 11.3392,20.337H12.6632C14.5932,19.329 16.3672,17.694 17.6582,15.735C18.2532,14.833 18.6752,13.729 18.9102,12.455L19.6632,8.368L19.0532,7.129L12.4942,3.557H11.5152V3.556Z"
android:valueType="pathType" />
+
+ android:pathData="M12.0002,22C11.5342,22 11.0662,21.885 10.6462,21.668C8.4962,20.547 6.5242,18.735 5.0942,16.566C4.3852,15.493 3.8882,14.198 3.6162,12.716L2.9092,8.872C2.6692,7.563 3.2942,6.259 4.4632,5.625L10.5652,2.363C11.4632,1.88 12.5362,1.88 13.4322,2.359L19.5342,5.682C20.7082,6.313 21.3352,7.621 21.0912,8.936L20.3862,12.728C20.1152,14.201 19.6172,15.49 18.9112,16.56C17.4822,18.729 15.5102,20.542 13.3562,21.666C12.9372,21.885 12.4682,22 12.0002,22ZM11.5152,3.556L4.8452,7.13L4.3382,8.368L5.0922,12.445C5.3272,13.727 5.7502,14.836 6.3472,15.74C7.6392,17.7 9.4122,19.332 11.3392,20.337H12.6632C14.5932,19.329 16.3672,17.694 17.6582,15.735C18.2532,14.833 18.6752,13.729 18.9102,12.455L19.6632,8.368L19.0532,7.129L12.4942,3.557H11.5152V3.556Z">
+ android:endX="3"
+ android:endY="22"
+ android:startX="21"
+ android:startY="2"
+ android:type="linear">
+
+
+
@@ -41,8 +45,8 @@
android:duration="330"
android:interpolator="@android:interpolator/fast_out_slow_in"
android:propertyName="pathData"
- android:valueFrom="M 20 6 C 20 5 19.2 4.1 18.2 4 L 12 3 L 5.8 4 C 4.8 4 4 5 4 6 L 4.1 11 C 4.4 14.2 5.1 16 6.6 18 C 7.929 19.627 9.821 20.698 11.9 21 L 12.1 21 C 14.2 20.7 16.1 19.6 17.4 18 C 19 16 19.6 14.2 19.9 11 L 20 6 Z M 17.9 10.8 C 17.9 12.963 17.198 15.069 15.9 16.8 C 14.9 17.9 13.5 18.8 12 19.1 C 10.452 18.8 9.066 17.946 8.1 16.7 C 6.816 15 6.114 12.93 6.1 10.8 C 6.041 9.167 6.041 7.533 6.1 5.9 L 12 5 L 17.9 6 L 18 6.2 L 17.9 10.9 Z M 8 7.6 L 8 10.6 C 8.3 13.3 8.8 14.3 9.7 15.6 C 10.3 16.2 11.1 16.8 12 17 L 12 7 L 8 7.6 Z"
- android:valueTo="M 20 6 C 20 5 19.2 4.1 18.2 4 L 12 3 L 5.8 4 C 4.8 4 4 5 4 6 L 4.1 11 C 4.4 14.2 5.1 16 6.6 18 C 7.929 19.627 9.821 20.698 11.9 21 L 12.1 21 C 14.2 20.7 16.1 19.6 17.4 18 C 19 16 19.6 14.2 19.9 11 L 20 6 Z M 17.9 10.8 C 17.9 12.963 17.198 15.069 15.9 16.8 C 14.9 17.9 13.5 18.8 12 19.1 C 10.452 18.8 9.066 17.946 8.1 16.7 C 6.816 15 6.114 12.93 6.1 10.8 C 6.041 9.167 6.041 7.533 6.1 5.9 L 12 5 L 17.9 6 L 18 6.2 L 17.9 10.9 Z M 8 7.6 L 8 10.6 C 8.3 13.3 8.8 14.3 9.7 15.6 C 10.3 16.2 11.1 16.8 12 17 L 12 7 L 8 7.6 Z"
+ android:valueFrom="M12.0002,22C11.5342,22 11.0662,21.885 10.6462,21.668C8.4962,20.547 6.5242,18.735 5.0942,16.566C4.3852,15.493 3.8882,14.198 3.6162,12.716L2.9092,8.872C2.6692,7.563 3.2942,6.259 4.4632,5.625L10.5652,2.363C11.4632,1.88 12.5362,1.88 13.4322,2.359L19.5342,5.682C20.7082,6.313 21.3352,7.621 21.0912,8.936L20.3862,12.728C20.1152,14.201 19.6172,15.49 18.9112,16.56C17.4822,18.729 15.5102,20.542 13.3562,21.666C12.9372,21.885 12.4682,22 12.0002,22ZM11.5152,3.556L4.8452,7.13L4.3382,8.368L5.0922,12.445C5.3272,13.727 5.7502,14.836 6.3472,15.74C7.6392,17.7 9.4122,19.332 11.3392,20.337H12.6632C14.5932,19.329 16.3672,17.694 17.6582,15.735C18.2532,14.833 18.6752,13.729 18.9102,12.455L19.6632,8.368L19.0532,7.129L12.4942,3.557H11.5152V3.556Z"
+ android:valueTo="M12.0002,22C11.5342,22 11.0662,21.885 10.6462,21.668C8.4962,20.547 6.5242,18.735 5.0942,16.566C4.3852,15.493 3.8882,14.198 3.6162,12.716L2.9092,8.872C2.6692,7.563 3.2942,6.259 4.4632,5.625L10.5652,2.363C11.4632,1.88 12.5362,1.88 13.4322,2.359L19.5342,5.682C20.7082,6.313 21.3352,7.621 21.0912,8.936L20.3862,12.728C20.1152,14.201 19.6172,15.49 18.9112,16.56C17.4822,18.729 15.5102,20.542 13.3562,21.666C12.9372,21.885 12.4682,22 12.0002,22ZM11.5152,3.556L4.8452,7.13L4.3382,8.368L5.0922,12.445C5.3272,13.727 5.7502,14.836 6.3472,15.74C7.6392,17.7 9.4122,19.332 11.3392,20.337H12.6632C14.5932,19.329 16.3672,17.694 17.6582,15.735C18.2532,14.833 18.6752,13.729 18.9102,12.455L19.6632,8.368L19.0532,7.129L12.4942,3.557H11.5152V3.556Z"
android:valueType="pathType" />
+ android:pathData="m15.535,10.526 l1.6,1.6a0.5,0.5 0,0 1,-0.351 0.854l-4.233,0.02 -0.551,-0.551 0.021,-4.232a0.5,0.5 0,0 1,0.854 -0.351l1.596,1.596L19.72,4.22a0.75,0.75 0,1 1,1.061 1.061l-5.246,5.245zM7.6,14H6.4l-0.4,0.4v1.2l0.4,0.4h1.2l0.4,-0.4v-1.2l-0.4,-0.4zM10.6,14H9.4l-0.4,0.4v1.2l0.4,0.4h1.2l0.4,-0.4v-1.2l-0.4,-0.4zM13.6,14h-1.2l-0.4,0.4v1.2l0.4,0.4h1.2l0.4,-0.4v-1.2l-0.4,-0.4zM10.6,11H9.4l-0.4,0.4v1.2l0.4,0.4h1.2l0.4,-0.4v-1.2l-0.4,-0.4z"
+ android:fillColor="?primaryText"/>
+
diff --git a/app/src/main/res/drawable/ic_addons_extensions.xml b/app/src/main/res/drawable/ic_addons_extensions.xml
index 5fabf10b2..efe803ba3 100644
--- a/app/src/main/res/drawable/ic_addons_extensions.xml
+++ b/app/src/main/res/drawable/ic_addons_extensions.xml
@@ -8,6 +8,6 @@
android:viewportWidth="24"
android:viewportHeight="24">
diff --git a/app/src/main/res/drawable/ic_alert.xml b/app/src/main/res/drawable/ic_alert.xml
index 1546d32e6..94ab8c95e 100644
--- a/app/src/main/res/drawable/ic_alert.xml
+++ b/app/src/main/res/drawable/ic_alert.xml
@@ -8,6 +8,12 @@
android:viewportWidth="24"
android:viewportHeight="24">
-
+ android:fillColor="@color/sync_error_text_color"
+ android:pathData="M11.75,7C11.336,7 11,7.336 11,7.75V13.25C11,13.664 11.336,14 11.75,14C12.164,14 12.5,13.664 12.5,13.25V7.75C12.5,7.336 12.164,7 11.75,7Z" />
+
+
+
diff --git a/app/src/main/res/drawable/ic_arrowhead_right.xml b/app/src/main/res/drawable/ic_arrowhead_right.xml
index c899ca92f..268da1cce 100644
--- a/app/src/main/res/drawable/ic_arrowhead_right.xml
+++ b/app/src/main/res/drawable/ic_arrowhead_right.xml
@@ -5,9 +5,9 @@
android:width="24dp"
android:height="24dp"
android:autoMirrored="true"
- android:viewportWidth="7"
- android:viewportHeight="13">
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ android:pathData="M14.289,12 L7.57,5.28a0.75,0.75 0,1 1,1.061 -1.061l7.37,7.37v0.821l-7.37,7.37a0.748,0.748 0,0 1,-1.061 0,0.75 0.75,0 0,1 0,-1.061L14.289,12z" />
diff --git a/app/src/main/res/drawable/ic_autoplay_disabled.xml b/app/src/main/res/drawable/ic_autoplay_disabled.xml
index d4416eefb..4d069cf50 100644
--- a/app/src/main/res/drawable/ic_autoplay_disabled.xml
+++ b/app/src/main/res/drawable/ic_autoplay_disabled.xml
@@ -9,11 +9,8 @@
android:viewportHeight="24">
+ android:pathData="M3.326 4.265A0.83 0.83 0 0 1 3.225 4h-0.36C2.842 4 2.822 4.008 2.8 4.01a0.737 0.737 0 0 0-0.58 0.21 0.75 0.75 0 0 0 0 1.061l1.847 1.847A9.441 9.441 0 0 0 2.7 12c0 5.238 4.262 9.5 9.5 9.5a9.42 9.42 0 0 0 4.879-1.36l1.64 1.64a0.748 0.748 0 0 0 1.06 0 0.75 0.75 0 0 0 0-1.061L3.326 4.265zM12.2 20c-4.411 0-8-3.589-8-8 0-1.328 0.352-2.609 0.975-3.764l4.301 4.301v3.026a0.89 0.89 0 0 0 1.333 0.773l1.569-0.897 3.601 3.601A7.918 7.918 0 0 1 12.2 20zM8 4.364L7.636 4H5.14L8 6.86V4.364zM12.2 4c2.137 0 4.146 0.832 5.657 2.343A7.948 7.948 0 0 1 20.2 12a7.964 7.964 0 0 1-1.898 5.162l1.06 1.06A9.45 9.45 0 0 0 21.7 12a9.442 9.442 0 0 0-2.782-6.718A9.442 9.442 0 0 0 12.2 2.5a0.75 0.75 0 0 0 0 1.5z" />
-
+ android:pathData="M17.046 12.773a0.89 0.89 0 0 0 0-1.547L10.81 7.664a0.887 0.887 0 0 0-1.322 0.685l5.564 5.564 1.994-1.14z" />
diff --git a/app/src/main/res/drawable/ic_autoplay_enabled.xml b/app/src/main/res/drawable/ic_autoplay_enabled.xml
index 424118209..fa534a57c 100644
--- a/app/src/main/res/drawable/ic_autoplay_enabled.xml
+++ b/app/src/main/res/drawable/ic_autoplay_enabled.xml
@@ -5,5 +5,10 @@
-
+
+
diff --git a/app/src/main/res/drawable/ic_bookmark_filled.xml b/app/src/main/res/drawable/ic_bookmark_filled.xml
index 090a843c0..8989f5484 100644
--- a/app/src/main/res/drawable/ic_bookmark_filled.xml
+++ b/app/src/main/res/drawable/ic_bookmark_filled.xml
@@ -9,6 +9,6 @@
android:viewportHeight="24">
diff --git a/app/src/main/res/drawable/ic_bookmark_list.xml b/app/src/main/res/drawable/ic_bookmark_list.xml
index eec7c8ffe..9937d1a68 100644
--- a/app/src/main/res/drawable/ic_bookmark_list.xml
+++ b/app/src/main/res/drawable/ic_bookmark_list.xml
@@ -10,6 +10,9 @@
android:viewportWidth="24"
android:viewportHeight="24">
+ android:pathData="m7.225,11.324 l-2.86,-2.375a1.207,1.207 0,0 1,-0.378 -1.309,1.205 1.205,0 0,1 1.074,-0.835l3.697,-0.239 0.501,-0.375 1.364,-3.427A1.209,1.209 0,0 1,11.75 2c0.499,0 0.941,0.3 1.127,0.763v0.001l1.364,3.427 0.501,0.375 3.697,0.239c0.498,0.032 0.92,0.36 1.074,0.835 0.155,0.476 0.006,0.99 -0.378,1.309L16.28,11.32l-0.18,0.592 0.91,3.575c0.123,0.485 -0.059,0.986 -0.463,1.279a1.202,1.202 0,0 1,-1.359 0.046l-3.127,-1.977h-0.621l-3.126,1.977c-0.426,0.266 -0.96,0.247 -1.361,-0.045a1.205,1.205 0,0 1,-0.463 -1.279l0.908,-3.566 -0.173,-0.598z"
+ android:fillColor="?primaryText"/>
+
diff --git a/app/src/main/res/drawable/ic_bookmark_outline.xml b/app/src/main/res/drawable/ic_bookmark_outline.xml
index da59f26a6..67c872b2b 100644
--- a/app/src/main/res/drawable/ic_bookmark_outline.xml
+++ b/app/src/main/res/drawable/ic_bookmark_outline.xml
@@ -9,5 +9,5 @@
android:viewportHeight="24">
+ android:pathData="M16.6,21.1a2,2 0,0 1,-1.2 -0.3l-3.3,-2H12l-3.3,2c-0.8,0.5 -1.7,0.4 -2.4,0a2,2 0,0 1,-0.8 -2.3l1,-3.8 -0.1,-0.2 -3,-2.5a2,2 0,0 1,-0.7 -2.3,2 2,0 0,1 1.9,-1.5L8.4,8l0.2,-0.2L10,4.2a2.1,2.1 0,0 1,2 -1.3c0.9,0 1.6,0.5 2,1.3l1.4,3.6 0.2,0.2 4,0.2a2,2 0,0 1,1.8 1.5,2 2,0 0,1 -0.7,2.3l-3,2.5v0.2l1,3.8a2,2 0,0 1,-0.9 2.2,2 2,0 0,1 -1.2,0.4zM12,4.4a0.6,0.6 0,0 0,-0.6 0.3l-1.6,4 -0.9,0.8 -4.3,0.2a0.6,0.6 0,0 0,-0.6 0.5,0.6 0.6,0 0,0 0.2,0.6l3.4,2.8 0.3,1L7,19a0.6,0.6 0,0 0,0.2 0.6,0.6 0.6,0 0,0 0.7,0l3.6,-2.3h1.2l3.6,2.3c0.2,0.2 0.5,0.2 0.7,0a0.6,0.6 0,0 0,0.3 -0.6L16,14.7l0.3,-1.1 3.4,-2.8a0.6,0.6 0,0 0,0.2 -0.6,0.6 0.6,0 0,0 -0.6,-0.5l-4.3,-0.2 -1,-0.7 -1.5,-4a0.6,0.6 0,0 0,-0.6 -0.4z" />
diff --git a/app/src/main/res/drawable/ic_bookmarks_menu.xml b/app/src/main/res/drawable/ic_bookmarks_menu.xml
new file mode 100644
index 000000000..fce6d6829
--- /dev/null
+++ b/app/src/main/res/drawable/ic_bookmarks_menu.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_camera.xml b/app/src/main/res/drawable/ic_camera.xml
index 00ef35e6a..9c5bca788 100644
--- a/app/src/main/res/drawable/ic_camera.xml
+++ b/app/src/main/res/drawable/ic_camera.xml
@@ -10,13 +10,4 @@
-
-
-
diff --git a/app/src/main/res/drawable/ic_camera_anim_disable.xml b/app/src/main/res/drawable/ic_camera_anim_disable.xml
deleted file mode 100644
index 8cf218a7d..000000000
--- a/app/src/main/res/drawable/ic_camera_anim_disable.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
-
-
diff --git a/app/src/main/res/drawable/ic_camera_anim_enable.xml b/app/src/main/res/drawable/ic_camera_anim_enable.xml
deleted file mode 100644
index 4cf33171a..000000000
--- a/app/src/main/res/drawable/ic_camera_anim_enable.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
-
-
diff --git a/app/src/main/res/drawable/ic_camera_disabled.xml b/app/src/main/res/drawable/ic_camera_disabled.xml
index ce49afbc2..6802c51fa 100644
--- a/app/src/main/res/drawable/ic_camera_disabled.xml
+++ b/app/src/main/res/drawable/ic_camera_disabled.xml
@@ -3,25 +3,12 @@
- 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/. -->
-
-
-
-
-
-
-
-
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+
diff --git a/app/src/main/res/drawable/ic_camera_enabled.xml b/app/src/main/res/drawable/ic_camera_enabled.xml
index 7eead7c05..3818f98e4 100644
--- a/app/src/main/res/drawable/ic_camera_enabled.xml
+++ b/app/src/main/res/drawable/ic_camera_enabled.xml
@@ -7,21 +7,8 @@
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
-
-
-
-
-
-
-
-
+
diff --git a/app/src/main/res/drawable/ic_chevron_down.xml b/app/src/main/res/drawable/ic_chevron_down.xml
index aef6a00b1..6177fe8a1 100644
--- a/app/src/main/res/drawable/ic_chevron_down.xml
+++ b/app/src/main/res/drawable/ic_chevron_down.xml
@@ -3,10 +3,10 @@
- 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/. -->
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ android:pathData="M12 14.289L5.28 7.57a0.75 0.75 0 1 0-1.061 1.061l7.37 7.37h0.821l7.37-7.37a0.748 0.748 0 0 0 0-1.061 0.75 0.75 0 0 0-1.061 0L12 14.289z"/>
diff --git a/app/src/main/res/drawable/ic_chevron_up.xml b/app/src/main/res/drawable/ic_chevron_up.xml
index 9148fa55d..60d8c82f8 100644
--- a/app/src/main/res/drawable/ic_chevron_up.xml
+++ b/app/src/main/res/drawable/ic_chevron_up.xml
@@ -3,10 +3,10 @@
- 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/. -->
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ android:pathData="m12,8.711 l-6.72,6.72a0.75,0.75 0,1 1,-1.061 -1.061L11.589,7h0.821l7.37,7.37c0.147,0.146 0.22,0.338 0.22,0.53a0.75,0.75 0,0 1,-1.281 0.53L12,8.711z"/>
diff --git a/app/src/main/res/drawable/ic_clear.xml b/app/src/main/res/drawable/ic_clear.xml
index 04435d15c..049b0b031 100644
--- a/app/src/main/res/drawable/ic_clear.xml
+++ b/app/src/main/res/drawable/ic_clear.xml
@@ -5,9 +5,9 @@
+ android:viewportWidth="20"
+ android:viewportHeight="20">
+ android:pathData="M10,2.5C5.858,2.5 2.5,5.858 2.5,10C2.5,14.142 5.858,17.5 10,17.5C14.142,17.5 17.5,14.142 17.5,10C17.5,5.858 14.142,2.5 10,2.5ZM12.817,11.933C13.06,12.177 13.06,12.574 12.817,12.818C12.695,12.939 12.535,13 12.375,13C12.215,13 12.055,12.939 11.933,12.817L10.204,11.088L9.797,11.087L8.068,12.816C7.945,12.939 7.785,13 7.625,13C7.465,13 7.305,12.939 7.183,12.817C6.94,12.573 6.94,12.176 7.183,11.932L8.897,10.218V9.781L7.183,8.067C6.94,7.823 6.94,7.426 7.183,7.182C7.427,6.938 7.824,6.938 8.068,7.182L9.787,8.901H10.215L11.934,7.182C12.178,6.938 12.575,6.938 12.819,7.182C13.062,7.426 13.062,7.823 12.819,8.067L11.1,9.786V10.214L12.817,11.933Z" />
diff --git a/app/src/main/res/drawable/ic_close.xml b/app/src/main/res/drawable/ic_close.xml
index e2c2ea022..7b82c9846 100644
--- a/app/src/main/res/drawable/ic_close.xml
+++ b/app/src/main/res/drawable/ic_close.xml
@@ -9,5 +9,5 @@
android:viewportHeight="24">
+ android:pathData="M13.108 11.776l4.709-4.709c0.244-0.244 0.244-0.641 0-0.885s-0.64-0.244-0.884 0l-4.689 4.689h-0.488L7.067 6.183c-0.244-0.244-0.64-0.244-0.884 0s-0.244 0.641 0 0.885l4.687 4.686v0.491l-4.687 4.687c-0.244 0.244-0.244 0.641 0 0.885C6.305 17.939 6.465 18 6.625 18c0.16 0 0.32-0.061 0.442-0.183l4.687-4.687h0.491l4.687 4.687C17.055 17.939 17.215 18 17.375 18c0.16 0 0.32-0.061 0.442-0.183 0.244-0.244 0.244-0.641 0-0.885l-4.709-4.709v-0.447z" />
diff --git a/app/src/main/res/drawable/ic_confirm_email.xml b/app/src/main/res/drawable/ic_confirm_email.xml
new file mode 100644
index 000000000..19fa069b0
--- /dev/null
+++ b/app/src/main/res/drawable/ic_confirm_email.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_cookies.xml b/app/src/main/res/drawable/ic_cookies.xml
index 553f16518..5c2e0c053 100644
--- a/app/src/main/res/drawable/ic_cookies.xml
+++ b/app/src/main/res/drawable/ic_cookies.xml
@@ -9,5 +9,5 @@
android:viewportHeight="24">
+ android:pathData="M20.862,11.69a7.886,7.886 0,0 1,-1.856 0.244L19,11.934v0.817l-0.5,0.5L17,13.251l-0.5,-0.5L16.5,11.53c-3.193,-1.052 -5.501,-4.055 -5.501,-7.6 0,-0.296 0.023,-0.585 0.055,-0.87l-0.565,-0.499c-4.531,0.725 -7.997,4.64 -7.997,9.375A9.508,9.508 0,0 0,12 21.443c5.146,0 9.327,-4.09 9.492,-9.198l-0.63,-0.556zM5,12.75v-1.5l0.5,-0.5L7,10.75l0.5,0.5v1.5l-0.5,0.5L5.5,13.25l-0.5,-0.5zM9.5,18l-0.5,0.5L7.5,18.5L7,18v-1.5l0.5,-0.5L9,16l0.5,0.5L9.5,18zM9.5,7.5L9,8L7.5,8L7,7.5L7,6l0.5,-0.5L9,5.5l0.5,0.5v1.5zM13.25,12.75 L12.75,13.25h-1.5l-0.5,-0.5v-1.5l0.5,-0.5h1.5l0.5,0.5v1.5zM17,18l-0.5,0.5L15,18.5l-0.5,-0.5v-1.5l0.5,-0.5h1.5l0.5,0.5L17,18z" />
diff --git a/app/src/main/res/drawable/ic_cryptominers.xml b/app/src/main/res/drawable/ic_cryptominers.xml
index 9d0aba381..9764b05a1 100644
--- a/app/src/main/res/drawable/ic_cryptominers.xml
+++ b/app/src/main/res/drawable/ic_cryptominers.xml
@@ -9,6 +9,5 @@
android:viewportHeight="24">
+ android:pathData="M16.5 4h-3V2.75a0.75 0.75 0 0 0-1.5 0V4H9.039C8.631 4 8.24 4.167 7.957 4.461L6.418 6.065A1.499 1.499 0 0 0 6 7.103V7.5A1.5 1.5 0 0 0 7.5 9H12v11.25a0.75 0.75 0 0 0 1.5 0V9h3A1.5 1.5 0 0 0 18 7.5v-2A1.5 1.5 0 0 0 16.5 4z" />
diff --git a/app/src/main/res/drawable/ic_delete.xml b/app/src/main/res/drawable/ic_delete.xml
index c8f283867..016f2ad0b 100644
--- a/app/src/main/res/drawable/ic_delete.xml
+++ b/app/src/main/res/drawable/ic_delete.xml
@@ -8,6 +8,9 @@
android:viewportWidth="24"
android:viewportHeight="24">
+ android:pathData="M20.25,6L16,6L16,4.5A2.5,2.5 0,0 0,13.5 2h-3A2.5,2.5 0,0 0,8 4.5L8,6L3.75,6a0.75,0.75 0,0 0,0 1.5L4,7.5v12A2.5,2.5 0,0 0,6.5 22h11a2.5,2.5 0,0 0,2.5 -2.5v-12h0.25a0.75,0.75 0,0 0,0 -1.5zM9.5,4.3l0.8,-0.8h3.4l0.8,0.8L14.5,6h-5L9.5,4.3zM18.5,19.7 L17.7,20.5L6.3,20.5l-0.8,-0.8L5.5,7.5h13v12.2z"
+ android:fillColor="?primaryText"/>
+
diff --git a/app/src/main/res/drawable/ic_desktop.xml b/app/src/main/res/drawable/ic_desktop.xml
index 9eec900b6..76629aa8e 100644
--- a/app/src/main/res/drawable/ic_desktop.xml
+++ b/app/src/main/res/drawable/ic_desktop.xml
@@ -8,5 +8,5 @@
android:viewportWidth="24"
android:viewportHeight="24">
+ android:pathData="M20.5,17h-2a2.5,2.5 0,0 0,2.5 -2.5v-9A2.5,2.5 0,0 0,18.5 3h-13A2.5,2.5 0,0 0,3 5.5v9A2.5,2.5 0,0 0,5.5 17h-2a1.5,1.5 0,0 0,0 3h17a1.5,1.5 0,0 0,0 -3zM5.3,15.5l-0.8,-0.8L4.5,5.3l0.8,-0.8h13.4l0.8,0.8v9.4l-0.8,0.8L5.3,15.5zM13.25,18.5h-2.5a0.75,0.75 0,0 1,0 -1.5h2.5a0.75,0.75 0,0 1,0 1.5z" />
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_download.xml b/app/src/main/res/drawable/ic_download.xml
index 144122018..ecccd5dd1 100644
--- a/app/src/main/res/drawable/ic_download.xml
+++ b/app/src/main/res/drawable/ic_download.xml
@@ -9,5 +9,8 @@
android:viewportHeight="24">
+ android:pathData="M5.782 20.5l-0.8-0.8v-1.95a0.75 0.75 0 0 0-1.5 0v1.75a2.5 2.5 0 0 0 2.5 2.5h11.535a2.5 2.5 0 0 0 2.5-2.5v-1.75a0.75 0.75 0 0 0-1.5 0v1.95l-0.8 0.8H5.782z" />
+
diff --git a/app/src/main/res/drawable/ic_edit.xml b/app/src/main/res/drawable/ic_edit.xml
index e92193503..3da6ea077 100644
--- a/app/src/main/res/drawable/ic_edit.xml
+++ b/app/src/main/res/drawable/ic_edit.xml
@@ -9,5 +9,5 @@
android:viewportWidth="24"
android:viewportHeight="24">
+ android:pathData="M20.976 19.308l-0.752-4.055a2.602 2.602 0 0 0-0.719-1.369L8.356 2.735a2.522 2.522 0 0 0-3.559 0L2.735 4.797a2.522 2.522 0 0 0 0 3.559l11.151 11.15c0.375 0.374 0.849 0.622 1.366 0.717l4.056 0.752a1.43 1.43 0 0 0 1.668-1.667zM5.166 8.666L3.642 7.142V6.011l2.369-2.369h1.131l1.524 1.524-3.5 3.5zm10.357 10.082a1.098 1.098 0 0 1-0.577-0.303l-8.72-8.719 3.5-3.5 8.714 8.714c0.161 0.162 0.267 0.363 0.308 0.586l0.625 3.371-0.476 0.476-3.374-0.625z"/>
diff --git a/app/src/main/res/drawable/ic_fab_sync.xml b/app/src/main/res/drawable/ic_fab_sync.xml
new file mode 100644
index 000000000..52ac21e97
--- /dev/null
+++ b/app/src/main/res/drawable/ic_fab_sync.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_fingerprinters.xml b/app/src/main/res/drawable/ic_fingerprinters.xml
index 9a4652791..14dd24df1 100644
--- a/app/src/main/res/drawable/ic_fingerprinters.xml
+++ b/app/src/main/res/drawable/ic_fingerprinters.xml
@@ -9,5 +9,11 @@
android:viewportHeight="24">
-
+ android:pathData="M6.688 18H2.75a0.75 0.75 0 0 1 0-1.5h3.938c3.392 0 6.15-2.76 6.15-6.151V9.722a0.75 0.75 0 0 1 1.5 0v0.627C14.339 14.567 10.907 18 6.688 18z" />
+
+
+
diff --git a/app/src/main/res/drawable/ic_folder_icon.xml b/app/src/main/res/drawable/ic_folder_icon.xml
index 437fe48e0..ea7cea353 100644
--- a/app/src/main/res/drawable/ic_folder_icon.xml
+++ b/app/src/main/res/drawable/ic_folder_icon.xml
@@ -6,8 +6,8 @@
android:autoMirrored="true"
android:width="24dp"
android:height="24dp"
- android:viewportWidth="20"
- android:viewportHeight="17">
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ android:pathData="M18.5 6h-6.354l-2.039-2.199A2.503 2.503 0 0 0 8.274 3H4.5A2.502 2.502 0 0 0 2 5.5v12C2 18.879 3.121 20 4.5 20h14c1.379 0 2.5-1.121 2.5-2.5v-9C21 7.121 19.879 6 18.5 6zm1 11.7l-0.8 0.8H4.3l-0.8-0.8V5.3l0.8-0.8h3.974c0.277 0 0.545 0.117 0.733 0.32l2.146 2.315 0.869 0.365H18.7l0.8 0.8v9.4z" />
diff --git a/app/src/main/res/drawable/ic_folder_new.xml b/app/src/main/res/drawable/ic_folder_new.xml
index a8de11332..fc41e1d89 100644
--- a/app/src/main/res/drawable/ic_folder_new.xml
+++ b/app/src/main/res/drawable/ic_folder_new.xml
@@ -9,5 +9,9 @@
android:viewportHeight="24">
+ android:pathData="M21 18.5a0.745 0.745 0 0 0 0.53-0.22A0.75 0.75 0 0 0 21 17h-1.7l-0.8-0.8v-1.7a0.75 0.75 0 0 0-1.5 0v1.7L16.2 17h-1.7a0.75 0.75 0 0 0 0 1.5h1.7l0.8 0.8V21a0.75 0.75 0 0 0 1.5 0v-1.7l0.8-0.8H21z" />
+
+
diff --git a/app/src/main/res/drawable/ic_help.xml b/app/src/main/res/drawable/ic_help.xml
index ca731897c..e15b9ed93 100644
--- a/app/src/main/res/drawable/ic_help.xml
+++ b/app/src/main/res/drawable/ic_help.xml
@@ -9,5 +9,8 @@
android:viewportHeight="24">
+ android:pathData="M11.75 14A0.75 0.75 0 0 1 11 13.25v-0.959c0-0.645 0.452-1.206 1.074-1.336a1.6 1.6 0 0 0 1.27-1.561 1.596 1.596 0 0 0-3.101-0.522 0.75 0.75 0 1 1-1.418-0.49A3.097 3.097 0 0 1 11.75 6.3a3.098 3.098 0 0 1 3.094 3.094c0 1.418-0.979 2.661-2.344 3.002v0.854A0.75 0.75 0 0 1 11.75 14zm0.45 3h-0.9L11 16.7v-0.9l0.3-0.3h0.9l0.3 0.3v0.9L12.2 17z" />
+
diff --git a/app/src/main/res/drawable/ic_history.xml b/app/src/main/res/drawable/ic_history.xml
index aa49fa9ec..757559a7e 100644
--- a/app/src/main/res/drawable/ic_history.xml
+++ b/app/src/main/res/drawable/ic_history.xml
@@ -9,5 +9,8 @@
android:viewportHeight="24">
+ android:pathData="M11.974 21.5c-5.238 0-9.5-4.262-9.5-9.5s4.262-9.5 9.5-9.5 9.5 4.262 9.5 9.5-4.262 9.5-9.5 9.5zm0-17.5c-4.411 0-8 3.589-8 8s3.589 8 8 8 8-3.589 8-8-3.589-8-8-8z" />
+
diff --git a/app/src/main/res/drawable/ic_info.xml b/app/src/main/res/drawable/ic_info.xml
index 23964049b..0897d1761 100644
--- a/app/src/main/res/drawable/ic_info.xml
+++ b/app/src/main/res/drawable/ic_info.xml
@@ -8,6 +8,9 @@
android:viewportWidth="24"
android:viewportHeight="24">
+ android:pathData="M11.75,17a0.75,0.75 0,0 1,-0.75 -0.75v-5.5a0.75,0.75 0,0 1,1.5 0v5.5a0.75,0.75 0,0 1,-0.75 0.75zM12.2,7h-0.9l-0.3,0.3v0.9l0.3,0.3h0.9l0.3,-0.3v-0.9l-0.3,-0.3z"
+ android:fillColor="?primaryText"/>
+
diff --git a/app/src/main/res/drawable/ic_internet.xml b/app/src/main/res/drawable/ic_internet.xml
index 7aea69447..f6276035e 100644
--- a/app/src/main/res/drawable/ic_internet.xml
+++ b/app/src/main/res/drawable/ic_internet.xml
@@ -9,5 +9,5 @@
android:viewportHeight="24">
+ android:pathData="M12 2.5A9.5 9.5 0 0 0 2.5 12a9.5 9.5 0 0 0 9.5 9.5 9.5 9.5 0 0 0 9.5-9.5A9.5 9.5 0 0 0 12 2.5zm7.962 8.75h-3.008a11.503 11.503 0 0 0-2.666-6.65h0.742a8.014 8.014 0 0 1 4.932 6.65zm-7.749 8.15h-0.429a9.988 9.988 0 0 1-3.238-6.65h6.908a9.978 9.978 0 0 1-3.241 6.65zm-3.667-8.15a10.007 10.007 0 0 1 3.232-6.65h0.446a10.015 10.015 0 0 1 3.231 6.65H8.546zM8.97 4.6h0.743a11.499 11.499 0 0 0-2.667 6.65H4.038A8.014 8.014 0 0 1 8.97 4.6zm-4.932 8.15h3.008a11.476 11.476 0 0 0 2.67 6.65H8.97a8.014 8.014 0 0 1-4.932-6.65zM15.03 19.4h-0.733a11.465 11.465 0 0 0 2.658-6.65h3.007a8.014 8.014 0 0 1-4.932 6.65z" />
diff --git a/app/src/main/res/drawable/ic_link_disabled.xml b/app/src/main/res/drawable/ic_link_disabled.xml
index 083c07dc6..b0084eee8 100644
--- a/app/src/main/res/drawable/ic_link_disabled.xml
+++ b/app/src/main/res/drawable/ic_link_disabled.xml
@@ -7,7 +7,14 @@
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
+
+ android:pathData="M10.08 15.526l4.324 4.324h1.13l3.815-3.815v-1.13l-4.324-4.324a1.5 1.5 0 0 1 2.12 0l3.123 3.123a2.498 2.498 0 0 1 0 3.533l-3.533 3.533a2.498 2.498 0 0 1-3.533 0l-3.123-3.123a1.5 1.5 0 0 1 0.001-2.121z" />
+
+
diff --git a/app/src/main/res/drawable/ic_link_enabled.xml b/app/src/main/res/drawable/ic_link_enabled.xml
index 87e43535b..ecbc5488c 100644
--- a/app/src/main/res/drawable/ic_link_enabled.xml
+++ b/app/src/main/res/drawable/ic_link_enabled.xml
@@ -9,5 +9,11 @@
android:viewportHeight="24">
+ android:pathData="M10.08 15.526l4.324 4.324h1.13l3.815-3.815v-1.13l-4.324-4.324a1.5 1.5 0 0 1 2.12 0l3.123 3.123a2.498 2.498 0 0 1 0 3.533l-3.533 3.533a2.498 2.498 0 0 1-3.533 0l-3.123-3.123a1.5 1.5 0 0 1 0.001-2.121z" />
+
+
diff --git a/app/src/main/res/drawable/ic_location.xml b/app/src/main/res/drawable/ic_location.xml
index a6fbc4b6d..13454f7f2 100644
--- a/app/src/main/res/drawable/ic_location.xml
+++ b/app/src/main/res/drawable/ic_location.xml
@@ -10,13 +10,4 @@
-
-
-
diff --git a/app/src/main/res/drawable/ic_location_anim_disable.xml b/app/src/main/res/drawable/ic_location_anim_disable.xml
deleted file mode 100644
index 4664906a7..000000000
--- a/app/src/main/res/drawable/ic_location_anim_disable.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
-
-
diff --git a/app/src/main/res/drawable/ic_location_anim_enable.xml b/app/src/main/res/drawable/ic_location_anim_enable.xml
deleted file mode 100644
index 89b92fd4a..000000000
--- a/app/src/main/res/drawable/ic_location_anim_enable.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
-
-
diff --git a/app/src/main/res/drawable/ic_location_disabled.xml b/app/src/main/res/drawable/ic_location_disabled.xml
index b7f85fa0a..9b0e5fb95 100644
--- a/app/src/main/res/drawable/ic_location_disabled.xml
+++ b/app/src/main/res/drawable/ic_location_disabled.xml
@@ -3,25 +3,14 @@
- 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/. -->
-
-
-
-
-
-
-
-
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+
+
diff --git a/app/src/main/res/drawable/ic_location_enabled.xml b/app/src/main/res/drawable/ic_location_enabled.xml
index 41270ef01..785b356ab 100644
--- a/app/src/main/res/drawable/ic_location_enabled.xml
+++ b/app/src/main/res/drawable/ic_location_enabled.xml
@@ -3,25 +3,14 @@
- 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/. -->
-
-
-
-
-
-
-
-
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+
+
diff --git a/app/src/main/res/drawable/ic_login.xml b/app/src/main/res/drawable/ic_login.xml
index e95281678..a61455db0 100644
--- a/app/src/main/res/drawable/ic_login.xml
+++ b/app/src/main/res/drawable/ic_login.xml
@@ -3,11 +3,11 @@
- 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/. -->
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
diff --git a/app/src/main/res/drawable/ic_microphone.xml b/app/src/main/res/drawable/ic_microphone.xml
index e3d9712f5..69a5db52e 100644
--- a/app/src/main/res/drawable/ic_microphone.xml
+++ b/app/src/main/res/drawable/ic_microphone.xml
@@ -11,12 +11,4 @@
android:id="@+id/disabled"
android:drawable="@drawable/ic_microphone_disabled" />
-
-
-
+
diff --git a/app/src/main/res/drawable/ic_microphone_anim_disable.xml b/app/src/main/res/drawable/ic_microphone_anim_disable.xml
deleted file mode 100644
index 556580d5b..000000000
--- a/app/src/main/res/drawable/ic_microphone_anim_disable.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
-
-
diff --git a/app/src/main/res/drawable/ic_microphone_anim_enable.xml b/app/src/main/res/drawable/ic_microphone_anim_enable.xml
deleted file mode 100644
index bccb4de81..000000000
--- a/app/src/main/res/drawable/ic_microphone_anim_enable.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
-
-
diff --git a/app/src/main/res/drawable/ic_microphone_disabled.xml b/app/src/main/res/drawable/ic_microphone_disabled.xml
index ffe797fc9..929cb80a0 100644
--- a/app/src/main/res/drawable/ic_microphone_disabled.xml
+++ b/app/src/main/res/drawable/ic_microphone_disabled.xml
@@ -3,25 +3,11 @@
- 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/. -->
-
-
-
-
-
-
-
-
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+
diff --git a/app/src/main/res/drawable/ic_microphone_enabled.xml b/app/src/main/res/drawable/ic_microphone_enabled.xml
index 148a6695d..e639d9faa 100644
--- a/app/src/main/res/drawable/ic_microphone_enabled.xml
+++ b/app/src/main/res/drawable/ic_microphone_enabled.xml
@@ -7,21 +7,10 @@
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
-
-
-
-
-
-
-
-
+
+
diff --git a/app/src/main/res/drawable/ic_multiple_tabs.xml b/app/src/main/res/drawable/ic_multiple_tabs.xml
index e57048109..4df88b72c 100644
--- a/app/src/main/res/drawable/ic_multiple_tabs.xml
+++ b/app/src/main/res/drawable/ic_multiple_tabs.xml
@@ -8,6 +8,6 @@
android:viewportWidth="24"
android:viewportHeight="24">
diff --git a/app/src/main/res/drawable/ic_new.xml b/app/src/main/res/drawable/ic_new.xml
index 52f76c04b..ea1ea7ba1 100644
--- a/app/src/main/res/drawable/ic_new.xml
+++ b/app/src/main/res/drawable/ic_new.xml
@@ -9,5 +9,5 @@
android:viewportHeight="24">
+ android:pathData="M19.25 12.75c0.207 0 0.395-0.084 0.53-0.22C19.916 12.394 20 12.207 20 12c0-0.414-0.336-0.75-0.75-0.75h-5.7l-0.8-0.8v-5.7C12.75 4.336 12.414 4 12 4s-0.75 0.336-0.75 0.75v5.7l-0.8 0.8h-5.7C4.336 11.25 4 11.586 4 12s0.336 0.75 0.75 0.75h5.7l0.8 0.8v5.7c0 0.414 0.336 0.75 0.75 0.75 0.207 0 0.395-0.084 0.53-0.22 0.136-0.136 0.22-0.323 0.22-0.53v-5.7l0.8-0.8h5.7z" />
diff --git a/app/src/main/res/drawable/ic_notifications.xml b/app/src/main/res/drawable/ic_notifications.xml
index 23fd8c1d0..813693ef5 100644
--- a/app/src/main/res/drawable/ic_notifications.xml
+++ b/app/src/main/res/drawable/ic_notifications.xml
@@ -10,13 +10,4 @@
-
-
-
diff --git a/app/src/main/res/drawable/ic_notifications_anim_disable.xml b/app/src/main/res/drawable/ic_notifications_anim_disable.xml
deleted file mode 100644
index b54382c08..000000000
--- a/app/src/main/res/drawable/ic_notifications_anim_disable.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
-
-
diff --git a/app/src/main/res/drawable/ic_notifications_anim_enable.xml b/app/src/main/res/drawable/ic_notifications_anim_enable.xml
deleted file mode 100644
index 8f4aa3bda..000000000
--- a/app/src/main/res/drawable/ic_notifications_anim_enable.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
-
-
diff --git a/app/src/main/res/drawable/ic_notifications_disabled.xml b/app/src/main/res/drawable/ic_notifications_disabled.xml
index 9c30ac7bd..dcce0b521 100644
--- a/app/src/main/res/drawable/ic_notifications_disabled.xml
+++ b/app/src/main/res/drawable/ic_notifications_disabled.xml
@@ -7,21 +7,10 @@
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
-
-
-
-
-
-
-
-
-
+
+
+
diff --git a/app/src/main/res/drawable/ic_notifications_enabled.xml b/app/src/main/res/drawable/ic_notifications_enabled.xml
index 86447ec03..8fcc4d39d 100644
--- a/app/src/main/res/drawable/ic_notifications_enabled.xml
+++ b/app/src/main/res/drawable/ic_notifications_enabled.xml
@@ -7,21 +7,10 @@
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
-
-
-
-
-
-
-
-
+
+
diff --git a/app/src/main/res/drawable/ic_onboarding_avatar_anonymous_large.xml b/app/src/main/res/drawable/ic_onboarding_avatar_anonymous_large.xml
index 422897fd9..c8312c4bf 100644
--- a/app/src/main/res/drawable/ic_onboarding_avatar_anonymous_large.xml
+++ b/app/src/main/res/drawable/ic_onboarding_avatar_anonymous_large.xml
@@ -8,9 +8,12 @@
android:viewportWidth="24"
android:viewportHeight="24">
+ android:fillColor="?onboardingSelected"
+ android:pathData="M12 6.5a3 3 0 1 0 0 6 3 3 0 0 0 0-6z" />
+ android:fillColor="?onboardingSelected"
+ android:pathData="M12 21.5c-5.238 0-9.5-4.262-9.5-9.5S6.762 2.5 12 2.5s9.5 4.262 9.5 9.5-4.262 9.5-9.5 9.5zM12 4c-4.411 0-8 3.589-8 8s3.589 8 8 8 8-3.589 8-8-3.589-8-8-8z" />
+
diff --git a/app/src/main/res/drawable/ic_onboarding_privacy_notice.xml b/app/src/main/res/drawable/ic_onboarding_privacy_notice.xml
deleted file mode 100644
index e836da928..000000000
--- a/app/src/main/res/drawable/ic_onboarding_privacy_notice.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/drawable/ic_onboarding_tracking_protection.xml b/app/src/main/res/drawable/ic_onboarding_tracking_protection.xml
index 6ec758a14..3fc0e565c 100644
--- a/app/src/main/res/drawable/ic_onboarding_tracking_protection.xml
+++ b/app/src/main/res/drawable/ic_onboarding_tracking_protection.xml
@@ -2,23 +2,29 @@
-
+
+ android:pathData="M12.0002,22C11.5342,22 11.0662,21.885 10.6462,21.668C8.4962,20.547 6.5242,18.735 5.0942,16.566C4.3852,15.493 3.8882,14.198 3.6162,12.716L2.9092,8.872C2.6692,7.563 3.2942,6.259 4.4632,5.625L10.5652,2.363C11.4632,1.88 12.5362,1.88 13.4322,2.359L19.5342,5.682C20.7082,6.313 21.3352,7.621 21.0912,8.936L20.3862,12.728C20.1152,14.201 19.6172,15.49 18.9112,16.56C17.4822,18.729 15.5102,20.542 13.3562,21.666C12.9372,21.885 12.4682,22 12.0002,22ZM11.5152,3.556L4.8452,7.13L4.3382,8.368L5.0922,12.445C5.3272,13.727 5.7502,14.836 6.3472,15.74C7.6392,17.7 9.4122,19.332 11.3392,20.337H12.6632C14.5932,19.329 16.3672,17.694 17.6582,15.735C18.2532,14.833 18.6752,13.729 18.9102,12.455L19.6632,8.368L19.0532,7.129L12.4942,3.557H11.5152V3.556Z">
-
-
+ android:endX="3"
+ android:endY="22"
+ android:startX="21"
+ android:startY="2"
+ android:type="linear">
+
+
diff --git a/app/src/main/res/drawable/ic_pdd.png b/app/src/main/res/drawable/ic_pdd.png
new file mode 100644
index 000000000..c96a7c578
Binary files /dev/null and b/app/src/main/res/drawable/ic_pdd.png differ
diff --git a/app/src/main/res/drawable/ic_private_browsing.xml b/app/src/main/res/drawable/ic_private_browsing.xml
index baf1a8062..10276e31e 100644
--- a/app/src/main/res/drawable/ic_private_browsing.xml
+++ b/app/src/main/res/drawable/ic_private_browsing.xml
@@ -9,5 +9,5 @@
android:viewportHeight="24">
+ android:pathData="M21.914 8.476c-1.906-2.502-5.646-2.821-7.976-0.681l-1.303 1.197h-1.276l-1.303-1.197c-2.329-2.14-6.07-1.82-7.976 0.681-0.247 0.975 0.134 4.575 0.35 5.141 0.434 2.287 2.303 4.007 4.543 4.007 1.119 0 2.132-0.447 2.933-1.161l0.488-0.418c0.91-0.778 2.217-0.789 3.138-0.026l0.733 0.608v-0.003c0.772 0.621 1.718 1.001 2.754 1.001 2.24 0 4.109-1.72 4.543-4.007 0.218-0.567 0.613-4.152 0.352-5.142zM9.79 12.045C9.257 12.626 8.43 13 7.5 13c-0.93 0-1.757-0.374-2.29-0.955-0.28-0.305-0.28-0.785 0-1.09C5.743 10.374 6.57 10 7.5 10c0.93 0 1.757 0.374 2.29 0.955 0.28 0.305 0.28 0.785 0 1.09zm9 0C18.257 12.626 17.43 13 16.5 13c-0.93 0-1.757-0.374-2.29-0.955-0.28-0.305-0.28-0.785 0-1.09C14.743 10.374 15.57 10 16.5 10c0.93 0 1.757 0.374 2.29 0.955 0.28 0.305 0.28 0.785 0 1.09z" />
diff --git a/app/src/main/res/drawable/ic_qr.xml b/app/src/main/res/drawable/ic_qr.xml
index aa81e7c49..2b237163d 100644
--- a/app/src/main/res/drawable/ic_qr.xml
+++ b/app/src/main/res/drawable/ic_qr.xml
@@ -9,5 +9,5 @@
android:viewportHeight="24">
+ android:pathData="M15.8 4.5a0.8 0.8 0 0 1 0-1.5h2.7A2.5 2.5 0 0 1 21 5.5v2.8a0.8 0.8 0 0 1-1.5 0v-3l-0.8-0.8h-3zm-7.6 0a0.8 0.8 0 0 0 0-1.5H5.5A2.5 2.5 0 0 0 3 5.5v2.8a0.8 0.8 0 0 0 1.5 0v-3l0.8-0.8h3zm7.6 15a0.8 0.8 0 0 0 0 1.5h2.7a2.5 2.5 0 0 0 2.5-2.5v-2.8a0.8 0.8 0 0 0-1.5 0v3l-0.8 0.8h-3zm-7.6 0a0.8 0.8 0 0 1 0 1.5H5.5A2.5 2.5 0 0 1 3 18.5v-2.8a0.8 0.8 0 0 1 1.5 0v3l0.8 0.8h3zm2-8.5H7.8A0.8 0.8 0 0 1 7 10.2V7.8A0.8 0.8 0 0 1 7.8 7h2.5A0.8 0.8 0 0 1 11 7.8v2.5a0.8 0.8 0 0 1-0.8 0.7zm6.6 4H15v1.8s0.1 0.2 0.3 0.2h1.4a0.3 0.3 0 0 0 0.3-0.3v-1.4a0.3 0.3 0 0 0-0.3-0.3zm0-6H15V7.2A0.3 0.3 0 0 1 15.3 7h1.4A0.3 0.3 0 0 1 17 7.3v1.5A0.3 0.3 0 0 1 16.7 9zM15 11h-1.8a0.3 0.3 0 0 1-0.2-0.3V9.3A0.3 0.3 0 0 1 13.3 9H15v2zm1.8 2H15v-2h1.8a0.3 0.3 0 0 1 0.2 0.3v1.4a0.3 0.3 0 0 1-0.3 0.3zM15 13h-1.8a0.3 0.3 0 0 0-0.2 0.3V15h2v-2zm-2 2h-2v1.8s0.1 0.2 0.3 0.2h1.4a0.3 0.3 0 0 0 0.3-0.3V15zm-2.3-2H9.3A0.3 0.3 0 0 0 9 13.3V15h2v-1.8a0.3 0.3 0 0 0-0.3-0.2zM9 15H7.2A0.3 0.3 0 0 0 7 15.3v1.4C7 16.9 7.1 17 7.3 17h1.5A0.3 0.3 0 0 0 9 16.7V15z" />
diff --git a/app/src/main/res/drawable/ic_readermode.xml b/app/src/main/res/drawable/ic_readermode.xml
index d5aac5b81..396e54072 100644
--- a/app/src/main/res/drawable/ic_readermode.xml
+++ b/app/src/main/res/drawable/ic_readermode.xml
@@ -9,5 +9,8 @@
android:viewportHeight="24">
+ android:pathData="M6.5 3A2.5 2.5 0 0 0 4 5.5v13A2.5 2.5 0 0 0 6.5 21h11a2.5 2.5 0 0 0 2.5-2.5v-13A2.5 2.5 0 0 0 17.5 3h-11zm12 15.7l-0.8 0.8H6.3l-0.8-0.8V5.3l0.8-0.8h11.4l0.8 0.8v13.4z" />
+
diff --git a/app/src/main/res/drawable/ic_readermode_selected.xml b/app/src/main/res/drawable/ic_readermode_selected.xml
index c72b5267b..9a87ee124 100644
--- a/app/src/main/res/drawable/ic_readermode_selected.xml
+++ b/app/src/main/res/drawable/ic_readermode_selected.xml
@@ -9,7 +9,20 @@
android:viewportWidth="24"
android:viewportHeight="24">
+ android:pathData="M6.5 3A2.5 2.5 0 0 0 4 5.5v13A2.5 2.5 0 0 0 6.5 21h11a2.5 2.5 0 0 0 2.5-2.5v-13A2.5 2.5 0 0 0 17.5 3h-11zm12 15.7l-0.8 0.8H6.3l-0.8-0.8V5.3l0.8-0.8h11.4l0.8 0.8v13.4z" >
+
+
+
+
+
+ android:pathData="M15.685,14.074C16.511,12.925 17,11.519 17,10C17,6.141 13.86,3 10,3C6.14,3 3,6.141 3,10C3,13.859 6.14,17 10,17C11.525,17 12.936,16.508 14.087,15.676L14.611,15.67L19.721,20.78C19.867,20.926 20.059,21 20.251,21C20.443,21 20.635,20.927 20.781,20.78C21.074,20.487 21.074,20.012 20.781,19.719L15.675,14.612L15.685,14.074ZM10,15.5C6.967,15.5 4.5,13.032 4.5,10C4.5,6.968 6.967,4.5 10,4.5C13.033,4.5 15.5,6.968 15.5,10C15.5,13.032 13.033,15.5 10,15.5Z" />
diff --git a/app/src/main/res/drawable/ic_settings.xml b/app/src/main/res/drawable/ic_settings.xml
index 051a9abb9..6c3315b26 100644
--- a/app/src/main/res/drawable/ic_settings.xml
+++ b/app/src/main/res/drawable/ic_settings.xml
@@ -8,6 +8,9 @@
android:viewportWidth="24"
android:viewportHeight="24">
+ android:pathData="M12.8,22h-1.6a2,2 0,0 1,-1.9 -1.4l-0.5,-1.8 -1,-0.6 -1.9,0.4a2,2 0,0 1,-2.2 -1L3,16.4a2,2 0,0 1,0.3 -2.4l1.3,-1.3a6.7,6.7 0,0 1,0 -1.2l-1.3,-1.3A2,2 0,0 1,3 7.7l0.8,-1.4a2,2 0,0 1,2.2 -1l1.9,0.5 1,-0.6 0.5,-1.8a2,2 0,0 1,2 -1.4h1.5a2,2 0,0 1,2 1.4l0.4,1.8 1,0.6 1.9,-0.4a2,2 0,0 1,2.2 1l0.8,1.3a2,2 0,0 1,-0.3 2.4l-1.3,1.3a6.6,6.6 0,0 1,0 1.2l1.3,1.3c0.6,0.7 0.7,1.7 0.3,2.4l-0.8,1.4a2,2 0,0 1,-2.2 1l-1.8,-0.5 -1,0.6 -0.6,1.8a2,2 0,0 1,-2 1.4zM7.8,16.6l0.6,0.2 1.3,0.7 0.4,0.5 0.6,2.1c0.1,0.3 0.3,0.4 0.5,0.4h1.6c0.2,0 0.4,-0.1 0.5,-0.4l0.6,-2 0.4,-0.6a6,6 0,0 0,1.3 -0.7l0.7,-0.2 2.1,0.6c0.2,0 0.5,0 0.6,-0.3l0.8,-1.3 -0.1,-0.6 -1.6,-1.7 -0.2,-0.6a5.8,5.8 0,0 0,0 -1.5l0.2,-0.6L19.7,9c0.1,-0.1 0.2,-0.4 0,-0.6L19,7.1a0.5,0.5 0,0 0,-0.6 -0.3l-2.2,0.6 -0.6,-0.2a6,6 0,0 0,-1.3 -0.7L14,6l-0.6,-2.1c0,-0.3 -0.3,-0.4 -0.5,-0.4h-1.6c-0.2,0 -0.4,0.1 -0.5,0.4l-0.6,2 -0.4,0.6a6,6 0,0 0,-1.3 0.7l-0.6,0.2 -2.2,-0.6c-0.2,0 -0.5,0 -0.6,0.3l-0.8,1.3 0.1,0.6L6,10.6l0.2,0.6a6.1,6.1 0,0 0,0 1.5l-0.2,0.7L4.3,15c-0.1,0.1 -0.2,0.4 0,0.6l0.7,1.3c0.1,0.2 0.4,0.3 0.6,0.3l2.1,-0.6z"
+ android:fillColor="?primaryText"/>
+
diff --git a/app/src/main/res/drawable/ic_share.xml b/app/src/main/res/drawable/ic_share.xml
new file mode 100644
index 000000000..c36e1839d
--- /dev/null
+++ b/app/src/main/res/drawable/ic_share.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_share_filled.xml b/app/src/main/res/drawable/ic_share_filled.xml
deleted file mode 100644
index d8b6ccbf8..000000000
--- a/app/src/main/res/drawable/ic_share_filled.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
diff --git a/app/src/main/res/drawable/ic_sign_in.xml b/app/src/main/res/drawable/ic_sign_in.xml
index e02a2ef30..28629bb9c 100644
--- a/app/src/main/res/drawable/ic_sign_in.xml
+++ b/app/src/main/res/drawable/ic_sign_in.xml
@@ -8,6 +8,6 @@
android:viewportWidth="24"
android:viewportHeight="24">
diff --git a/app/src/main/res/drawable/ic_signed_out.xml b/app/src/main/res/drawable/ic_signed_out.xml
new file mode 100644
index 000000000..2a52d161e
--- /dev/null
+++ b/app/src/main/res/drawable/ic_signed_out.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_social_media_trackers.xml b/app/src/main/res/drawable/ic_social_media_trackers.xml
index fe473248a..a1c78bd68 100644
--- a/app/src/main/res/drawable/ic_social_media_trackers.xml
+++ b/app/src/main/res/drawable/ic_social_media_trackers.xml
@@ -9,5 +9,5 @@
android:viewportHeight="24">
+ android:pathData="M4.5,15h-1A1.5,1.5 0,0 1,2 13.5v-8A1.5,1.5 0,0 1,3.5 4h1A1.5,1.5 0,0 1,6 5.5v8A1.5,1.5 0,0 1,4.5 15zM18.767,16.077a2.461,2.461 0,0 0,2.335 -3.24l-2.051,-6.154A2.46,2.46 0,0 0,16.716 5H8v9.023l2.154,3.285 1.037,3.112a0.85,0.85 0,0 0,0.806 0.58H12c1.02,0 1.846,-0.827 1.846,-1.846v-2.708l0.369,-0.369h4.552z" />
diff --git a/app/src/main/res/drawable/ic_static_shortcut_private_tab_foreground.xml b/app/src/main/res/drawable/ic_static_shortcut_private_tab_foreground.xml
index 77047eb00..0b22e26b8 100644
--- a/app/src/main/res/drawable/ic_static_shortcut_private_tab_foreground.xml
+++ b/app/src/main/res/drawable/ic_static_shortcut_private_tab_foreground.xml
@@ -9,7 +9,7 @@
-
+ android:pathData="M7.2,11h-0.9l-0.3,0.3v0.9l0.3,0.3h0.9l0.3,-0.3v-0.9l-0.3,-0.3z"
+ android:fillColor="@color/disabled_text"/>
+
+
diff --git a/app/src/main/res/drawable/ic_storage_enabled.xml b/app/src/main/res/drawable/ic_storage_enabled.xml
index dc941e619..b5d2f7b4a 100644
--- a/app/src/main/res/drawable/ic_storage_enabled.xml
+++ b/app/src/main/res/drawable/ic_storage_enabled.xml
@@ -11,5 +11,8 @@
android:autoMirrored="true">
-
+ android:pathData="M7.2 11H6.3L6 11.3v0.9l0.3 0.3h0.9l0.3-0.3v-0.9L7.2 11z" />
+
+
diff --git a/app/src/main/res/drawable/ic_sync_disconnected.xml b/app/src/main/res/drawable/ic_sync_disconnected.xml
index ab6bd0299..b4d61e51c 100644
--- a/app/src/main/res/drawable/ic_sync_disconnected.xml
+++ b/app/src/main/res/drawable/ic_sync_disconnected.xml
@@ -3,13 +3,14 @@
- 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/. -->
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ android:fillColor="?primaryText"
+ android:pathData="M10.07 20.336l1.198-2.089A6.364 6.364 0 0 1 9.5 18.5c-2.353 0-4.395-1.277-5.525-3.162a2.451 2.451 0 0 1 1.84-0.838h7.372c0.075 0 0.144 0.017 0.217 0.024l1.28-2.231c0.916-1.596 3.218-1.596 4.134 0l0.12 0.208a9.71 9.71 0 0 0-0.07-2.613c-0.65-3.933-3.823-7.105-7.756-7.755C4.62 1.06-0.94 6.62 0.133 13.111c0.65 3.933 3.822 7.105 7.755 7.755a9.794 9.794 0 0 0 1.93 0.111c0.052-0.217 0.132-0.432 0.251-0.641zM9.5 6.5a3 3 0 1 1 0 6 3 3 0 0 1 0-6z" />
+
diff --git a/app/src/main/res/drawable/ic_synced_tabs.xml b/app/src/main/res/drawable/ic_synced_tabs.xml
index a707e9daf..5968047c9 100644
--- a/app/src/main/res/drawable/ic_synced_tabs.xml
+++ b/app/src/main/res/drawable/ic_synced_tabs.xml
@@ -8,9 +8,9 @@
android:viewportWidth="24"
android:viewportHeight="24">
diff --git a/app/src/main/res/drawable/ic_tab_collection.xml b/app/src/main/res/drawable/ic_tab_collection.xml
index b78a92c35..df421740c 100644
--- a/app/src/main/res/drawable/ic_tab_collection.xml
+++ b/app/src/main/res/drawable/ic_tab_collection.xml
@@ -9,8 +9,8 @@
android:viewportHeight="24">
+ android:pathData="M8.75 12.5h6.5a0.75 0.75 0 0 0 0-1.5h-6.5a0.75 0.75 0 0 0 0 1.5zm0 4h6.5a0.75 0.75 0 0 0 0-1.5h-6.5a0.75 0.75 0 0 0 0 1.5z" />
-
+ android:pathData="M19.962 7.811l0.018-0.005-0.788-2.951A2.504 2.504 0 0 0 16.776 3H7.22a2.502 2.502 0 0 0-2.419 1.868L4.088 7.596C4.072 7.632 4.07 7.673 4.058 7.711L4.041 7.777l0.004 0.001C4.028 7.852 4 7.921 4 8v10.5A2.5 2.5 0 0 0 6.5 21h11a2.5 2.5 0 0 0 2.5-2.5V8c0-0.067-0.026-0.125-0.038-0.189zM7.12 4.5h9.756l0.858 0.713L18.212 7H5.794l0.484-1.787L7.12 4.5zM18.5 18.7l-0.8 0.8H6.3l-0.8-0.8V8.5h13v10.2z" />
+
diff --git a/app/src/main/res/drawable/ic_tabs.xml b/app/src/main/res/drawable/ic_tabs.xml
index eed851286..04f47f556 100644
--- a/app/src/main/res/drawable/ic_tabs.xml
+++ b/app/src/main/res/drawable/ic_tabs.xml
@@ -8,7 +8,7 @@
android:viewportWidth="24"
android:viewportHeight="24">
diff --git a/app/src/main/res/drawable/ic_top_sites.xml b/app/src/main/res/drawable/ic_top_sites.xml
index 44ca20fe3..af3d6c0b4 100644
--- a/app/src/main/res/drawable/ic_top_sites.xml
+++ b/app/src/main/res/drawable/ic_top_sites.xml
@@ -9,6 +9,6 @@
android:viewportHeight="24">
+ android:pathData="M21.28 20.22l-5.472-5.472 2.986-2.986c0.527-0.528 0.68-1.285 0.398-1.976a1.796 1.796 0 0 0-1.665-1.133l-2.475-0.029-3.775-3.776 1.068-1.068a0.75 0.75 0 1 0-1.061-1.061L2.72 11.287a0.75 0.75 0 0 0 1.06 1.061l1.079-1.079 3.775 3.775 0.029 2.475a1.794 1.794 0 0 0 1.135 1.665 1.795 1.795 0 0 0 1.973-0.398l2.977-2.977 5.472 5.472a0.748 0.748 0 0 0 1.06 0 0.75 0.75 0 0 0 0-1.061zm-10.569-2.495a0.304 0.304 0 0 1-0.348 0.07 0.307 0.307 0 0 1-0.2-0.294l-0.037-3.086-4.207-4.207 4.299-4.299 4.206 4.207 3.086 0.037a0.306 0.306 0 0 1 0.294 0.199 0.31 0.31 0 0 1-0.07 0.349l-7.023 7.024z" />
diff --git a/app/src/main/res/drawable/ic_tracking_protection.xml b/app/src/main/res/drawable/ic_tracking_protection.xml
index ed41ca68e..64f2d3cac 100644
--- a/app/src/main/res/drawable/ic_tracking_protection.xml
+++ b/app/src/main/res/drawable/ic_tracking_protection.xml
@@ -11,12 +11,4 @@
android:id="@+id/disabled"
android:drawable="@drawable/ic_tracking_protection_disabled" />
-
-
diff --git a/app/src/main/res/drawable/ic_tracking_protection_anim_disable.xml b/app/src/main/res/drawable/ic_tracking_protection_anim_disable.xml
deleted file mode 100644
index 14f17035d..000000000
--- a/app/src/main/res/drawable/ic_tracking_protection_anim_disable.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
-
-
diff --git a/app/src/main/res/drawable/ic_tracking_protection_anim_enable.xml b/app/src/main/res/drawable/ic_tracking_protection_anim_enable.xml
deleted file mode 100644
index 3080ef51b..000000000
--- a/app/src/main/res/drawable/ic_tracking_protection_anim_enable.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
-
-
diff --git a/app/src/main/res/drawable/ic_tracking_protection_disabled.xml b/app/src/main/res/drawable/ic_tracking_protection_disabled.xml
index 0ef9a1b5c..54d9215ad 100644
--- a/app/src/main/res/drawable/ic_tracking_protection_disabled.xml
+++ b/app/src/main/res/drawable/ic_tracking_protection_disabled.xml
@@ -7,21 +7,10 @@
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
-
-
-
-
-
-
-
-
+
+
diff --git a/app/src/main/res/drawable/ic_tracking_protection_enabled.xml b/app/src/main/res/drawable/ic_tracking_protection_enabled.xml
index 633271b15..2c5cf04a7 100644
--- a/app/src/main/res/drawable/ic_tracking_protection_enabled.xml
+++ b/app/src/main/res/drawable/ic_tracking_protection_enabled.xml
@@ -3,25 +3,11 @@
- 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/. -->
-
-
-
-
-
-
-
-
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+
diff --git a/app/src/main/res/drawable/ic_whats_new.xml b/app/src/main/res/drawable/ic_whats_new.xml
index 7fb9e238c..c96c8b5b9 100644
--- a/app/src/main/res/drawable/ic_whats_new.xml
+++ b/app/src/main/res/drawable/ic_whats_new.xml
@@ -9,5 +9,5 @@
android:viewportHeight="24">
+ android:pathData="M18.5 7h-1.1A3.3 3.3 0 0 0 17 3a3.3 3.3 0 0 0-4.6 0 2 2 0 0 0-0.3 0.2A1.9 1.9 0 0 0 12 3a3.3 3.3 0 0 0-4.6 0A3.3 3.3 0 0 0 7 7H5.5A2.5 2.5 0 0 0 3 9.5v1c0 0.8 0.4 1.5 1 2v6A2.5 2.5 0 0 0 6.5 21h11a2.5 2.5 0 0 0 2.5-2.5v-6a2.5 2.5 0 0 0 1-2v-1A2.5 2.5 0 0 0 18.5 7zm0.2 1.5l0.8 0.8v1.4l-0.8 0.8h-6v-3h6zM13.4 4c0.7-0.7 1.8-0.7 2.5 0a1.8 1.8 0 0 1 0 2.5C15.5 6.9 14 7 12.9 7V6.3c0-1 0.2-2 0.5-2.3zm-5 2.5a1.8 1.8 0 0 1 1.2-3c0.5 0 1 0.2 1.3 0.5 0.3 0.3 0.5 1.3 0.5 2.3V7h-0.8c-1 0-1.9-0.2-2.2-0.5zM4.5 9.3l0.8-0.8h6v3h-6l-0.8-0.8V9.3zm1.8 10.2l-0.8-0.8V13h5.8v6.5h-5zm12.2-0.8l-0.8 0.8h-5V13h5.8v5.7z"/>
diff --git a/app/src/main/res/drawable/mozac_ic_extensions_black.xml b/app/src/main/res/drawable/mozac_ic_extensions_black.xml
index 097a4a094..9395d6040 100644
--- a/app/src/main/res/drawable/mozac_ic_extensions_black.xml
+++ b/app/src/main/res/drawable/mozac_ic_extensions_black.xml
@@ -5,9 +5,9 @@
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ android:pathData="M17.5,7H15.5V5.127C15.5,3.533 14.317,2.166 12.807,2.015C11.958,1.931 11.116,2.206 10.488,2.775C9.86,3.342 9.5,4.154 9.5,4.999V7H6.5C5.119,7 4,8.119 4,9.5V11.75C4,12.44 4.56,13 5.25,13H6.873C7.706,13 8.417,13.59 8.493,14.343C8.536,14.775 8.402,15.188 8.114,15.506C7.829,15.82 7.424,16 7.001,16H5.25C4.56,16 4,16.56 4,17.25V19.5C4,20.881 5.119,22 6.5,22H17.5C18.881,22 20,20.881 20,19.5V9.5C20,8.119 18.881,7 17.5,7ZM18.5,19.7L17.7,20.5H6.3L5.5,19.7V17.5H7.001C7.847,17.5 8.658,17.14 9.226,16.512C9.794,15.885 10.07,15.039 9.985,14.194C9.834,12.683 8.467,11.5 6.873,11.5H5.5V9.3L6.3,8.5H10C10.552,8.5 11,8.052 11,7.5V4.999C11,4.576 11.18,4.171 11.494,3.887C11.812,3.599 12.227,3.466 12.657,3.507C13.41,3.582 14,4.294 14,5.127V7.5C14,8.052 14.448,8.5 15,8.5H17.7L18.5,9.3V19.7Z" />
diff --git a/app/src/main/res/drawable/private_browsing_button.xml b/app/src/main/res/drawable/private_browsing_button.xml
index 344500088..be68036ec 100644
--- a/app/src/main/res/drawable/private_browsing_button.xml
+++ b/app/src/main/res/drawable/private_browsing_button.xml
@@ -23,7 +23,7 @@
android:viewportHeight="24">
+ android:pathData="M21.914 8.476c-1.906-2.502-5.646-2.821-7.976-0.681l-1.303 1.197h-1.276l-1.303-1.197c-2.329-2.14-6.07-1.82-7.976 0.681-0.247 0.975 0.134 4.575 0.35 5.141 0.434 2.287 2.303 4.007 4.543 4.007 1.119 0 2.132-0.447 2.933-1.161l0.488-0.418c0.91-0.778 2.217-0.789 3.138-0.026l0.733 0.608v-0.003c0.772 0.621 1.718 1.001 2.754 1.001 2.24 0 4.109-1.72 4.543-4.007 0.218-0.567 0.613-4.152 0.352-5.142zM9.79 12.045C9.257 12.626 8.43 13 7.5 13c-0.93 0-1.757-0.374-2.29-0.955-0.28-0.305-0.28-0.785 0-1.09C5.743 10.374 6.57 10 7.5 10c0.93 0 1.757 0.374 2.29 0.955 0.28 0.305 0.28 0.785 0 1.09zm9 0C18.257 12.626 17.43 13 16.5 13c-0.93 0-1.757-0.374-2.29-0.955-0.28-0.305-0.28-0.785 0-1.09C14.743 10.374 15.57 10 16.5 10c0.93 0 1.757 0.374 2.29 0.955 0.28 0.305 0.28 0.785 0 1.09z" />
diff --git a/app/src/main/res/drawable/shield_dark.xml b/app/src/main/res/drawable/shield_dark.xml
index a230ffa1e..f97e24bdb 100644
--- a/app/src/main/res/drawable/shield_dark.xml
+++ b/app/src/main/res/drawable/shield_dark.xml
@@ -13,9 +13,8 @@
android:viewportHeight="24">
+ android:fillColor="#FF00DDFF"
+ android:pathData="M12.0002,22C11.5342,22 11.0662,21.885 10.6462,21.668C8.4962,20.547 6.5242,18.735 5.0942,16.566C4.3852,15.493 3.8882,14.198 3.6162,12.716L2.9092,8.872C2.6692,7.563 3.2942,6.259 4.4632,5.625L10.5652,2.363C11.4632,1.88 12.5362,1.88 13.4322,2.359L19.5342,5.682C20.7082,6.313 21.3352,7.621 21.0912,8.936L20.3862,12.728C20.1152,14.201 19.6172,15.49 18.9112,16.56C17.4822,18.729 15.5102,20.542 13.3562,21.666C12.9372,21.885 12.4682,22 12.0002,22ZM11.5152,3.556L4.8452,7.13L4.3382,8.368L5.0922,12.445C5.3272,13.727 5.7502,14.836 6.3472,15.74C7.6392,17.7 9.4122,19.332 11.3392,20.337H12.6632C14.5932,19.329 16.3672,17.694 17.6582,15.735C18.2532,14.833 18.6752,13.729 18.9102,12.455L19.6632,8.368L19.0532,7.129L12.4942,3.557H11.5152V3.556Z" />
@@ -25,7 +24,7 @@
android:interpolator="@android:interpolator/fast_out_slow_in"
android:propertyName="fillColor"
android:valueFrom="#fbfbfe"
- android:valueTo="#00b3f4"
+ android:valueTo="#FF00DDFF"
android:valueType="colorType" />
diff --git a/app/src/main/res/drawable/shield_light.xml b/app/src/main/res/drawable/shield_light.xml
index 083f74145..f621f8390 100644
--- a/app/src/main/res/drawable/shield_light.xml
+++ b/app/src/main/res/drawable/shield_light.xml
@@ -14,8 +14,7 @@
+ android:pathData="M12.0002,22C11.5342,22 11.0662,21.885 10.6462,21.668C8.4962,20.547 6.5242,18.735 5.0942,16.566C4.3852,15.493 3.8882,14.198 3.6162,12.716L2.9092,8.872C2.6692,7.563 3.2942,6.259 4.4632,5.625L10.5652,2.363C11.4632,1.88 12.5362,1.88 13.4322,2.359L19.5342,5.682C20.7082,6.313 21.3352,7.621 21.0912,8.936L20.3862,12.728C20.1152,14.201 19.6172,15.49 18.9112,16.56C17.4822,18.729 15.5102,20.542 13.3562,21.666C12.9372,21.885 12.4682,22 12.0002,22ZM11.5152,3.556L4.8452,7.13L4.3382,8.368L5.0922,12.445C5.3272,13.727 5.7502,14.836 6.3472,15.74C7.6392,17.7 9.4122,19.332 11.3392,20.337H12.6632C14.5932,19.329 16.3672,17.694 17.6582,15.735C18.2532,14.833 18.6752,13.729 18.9102,12.455L19.6632,8.368L19.0532,7.129L12.4942,3.557H11.5152V3.556Z" />
@@ -25,7 +24,7 @@
android:interpolator="@android:interpolator/fast_out_slow_in"
android:propertyName="fillColor"
android:valueFrom="#20123a"
- android:valueTo="#0250bb"
+ android:valueTo="#FF0250BB"
android:valueType="colorType" />
diff --git a/app/src/main/res/drawable/top_sites_background.xml b/app/src/main/res/drawable/top_sites_background.xml
new file mode 100644
index 000000000..c5ff9918d
--- /dev/null
+++ b/app/src/main/res/drawable/top_sites_background.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_home.xml b/app/src/main/res/layout/activity_home.xml
index 5dc4bdefa..c016bdfd3 100644
--- a/app/src/main/res/layout/activity_home.xml
+++ b/app/src/main/res/layout/activity_home.xml
@@ -17,11 +17,11 @@
android:layout_width="match_parent"
android:layout_height="56dp" />
+
+ app:defaultNavHost="true"/>
diff --git a/app/src/main/res/layout/checkbox_left_sub_preference.xml b/app/src/main/res/layout/checkbox_left_sub_preference.xml
index 796d6be42..2913a9811 100644
--- a/app/src/main/res/layout/checkbox_left_sub_preference.xml
+++ b/app/src/main/res/layout/checkbox_left_sub_preference.xml
@@ -7,6 +7,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:minHeight="48dp"
android:background="?android:selectableItemBackground"
android:clickable="true"
android:focusable="true"
diff --git a/app/src/main/res/layout/collection_home_list_row.xml b/app/src/main/res/layout/collection_home_list_row.xml
index 15feff9e8..d37675e47 100644
--- a/app/src/main/res/layout/collection_home_list_row.xml
+++ b/app/src/main/res/layout/collection_home_list_row.xml
@@ -48,8 +48,8 @@
tools:text="@tools:sample/lorem/random" />
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/component_sync_tabs_tray_layout.xml b/app/src/main/res/layout/component_sync_tabs_tray_layout.xml
new file mode 100644
index 000000000..451b9a780
--- /dev/null
+++ b/app/src/main/res/layout/component_sync_tabs_tray_layout.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/component_tabstray2.xml b/app/src/main/res/layout/component_tabstray2.xml
index 6bcbe4e4f..81fab8e3b 100644
--- a/app/src/main/res/layout/component_tabstray2.xml
+++ b/app/src/main/res/layout/component_tabstray2.xml
@@ -25,30 +25,13 @@
app:layout_constraintWidth_percent="0.1" />
-
-
+ android:layout="@layout/tabs_tray_tab_counter2" />
+
+
+ app:layout_constraintTop_toBottomOf="@+id/info_banner" />
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/default_browser_experiment_preference.xml b/app/src/main/res/layout/default_browser_experiment_preference.xml
new file mode 100644
index 000000000..028e8d017
--- /dev/null
+++ b/app/src/main/res/layout/default_browser_experiment_preference.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/download_dialog_layout.xml b/app/src/main/res/layout/download_dialog_layout.xml
index 9485b40e5..344a7f617 100644
--- a/app/src/main/res/layout/download_dialog_layout.xml
+++ b/app/src/main/res/layout/download_dialog_layout.xml
@@ -50,7 +50,7 @@
android:contentDescription="@string/mozac_feature_downloads_button_close"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
- app:srcCompat="@drawable/mozac_ic_close"
+ app:srcCompat="@drawable/ic_close"
app:tint="?primaryText" />
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_credit_card_editor.xml b/app/src/main/res/layout/fragment_credit_card_editor.xml
new file mode 100644
index 000000000..4efcf05ca
--- /dev/null
+++ b/app/src/main/res/layout/fragment_credit_card_editor.xml
@@ -0,0 +1,237 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_saved_cards.xml b/app/src/main/res/layout/fragment_saved_cards.xml
new file mode 100644
index 000000000..d0cee56d7
--- /dev/null
+++ b/app/src/main/res/layout/fragment_saved_cards.xml
@@ -0,0 +1,11 @@
+
+
+
diff --git a/app/src/main/res/layout/normal_browser_tray_list.xml b/app/src/main/res/layout/normal_browser_tray_list.xml
index 4e243c9d7..a8a12f582 100644
--- a/app/src/main/res/layout/normal_browser_tray_list.xml
+++ b/app/src/main/res/layout/normal_browser_tray_list.xml
@@ -1,6 +1,24 @@
-
+ android:layout_height="match_parent">
+
+
+
+
+
diff --git a/app/src/main/res/layout/onboarding_manual_signin.xml b/app/src/main/res/layout/onboarding_manual_signin.xml
index c1503c97b..dbbcadd16 100644
--- a/app/src/main/res/layout/onboarding_manual_signin.xml
+++ b/app/src/main/res/layout/onboarding_manual_signin.xml
@@ -7,7 +7,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/onboarding_card"
- style="@style/OnboardingCardDark"
+ style="@style/OnboardingCardLightWithPadding"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
@@ -26,37 +26,31 @@
android:id="@+id/header_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:textAppearance="@style/Header16TextStyle"
+ android:textAppearance="@style/HeaderTextStyle"
android:lineSpacingExtra="8sp"
- android:textColor="@color/neutral_text"
- app:drawableTint="@color/white_color"
+ android:layout_marginTop="10dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/avatar_icon"
android:layout_marginStart="52dp"
- tools:text="@string/onboarding_account_sign_in_header"
- />
+ tools:text="@string/onboarding_account_sign_in_header_1" />
-
+ android:layout_marginTop="14dp"
+ android:text="@string/onboarding_manual_sign_in_description"
+ android:textAppearance="@style/Body14TextStyle"
+ app:layout_constraintStart_toStartOf="@id/avatar_icon"
+ app:layout_constraintTop_toBottomOf="@id/avatar_icon" />
+ app:layout_constraintStart_toEndOf="parent" />
diff --git a/app/src/main/res/layout/onboarding_privacy_notice.xml b/app/src/main/res/layout/onboarding_privacy_notice.xml
index 302af0c13..6a51e8243 100644
--- a/app/src/main/res/layout/onboarding_privacy_notice.xml
+++ b/app/src/main/res/layout/onboarding_privacy_notice.xml
@@ -15,7 +15,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/onboarding_privacy_notice_header"
- tools:drawableStart="@drawable/ic_onboarding_privacy_notice"
+ tools:drawableStart="@drawable/ic_info"
android:drawablePadding="12dp"
android:textAppearance="@style/HeaderTextStyle"
android:gravity="center_vertical"
diff --git a/app/src/main/res/layout/onboarding_theme_picker.xml b/app/src/main/res/layout/onboarding_theme_picker.xml
index bf6757c7b..0236c7a15 100644
--- a/app/src/main/res/layout/onboarding_theme_picker.xml
+++ b/app/src/main/res/layout/onboarding_theme_picker.xml
@@ -29,7 +29,7 @@
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingEnd="16dp"
- android:text="@string/onboarding_theme_picker_description1"
+ android:text="@string/onboarding_theme_picker_description_2"
android:textAppearance="@style/Body14TextStyle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/header_text" />
diff --git a/app/src/main/res/layout/onboarding_toolbar_position_picker.xml b/app/src/main/res/layout/onboarding_toolbar_position_picker.xml
index 309892490..8af86bc5c 100644
--- a/app/src/main/res/layout/onboarding_toolbar_position_picker.xml
+++ b/app/src/main/res/layout/onboarding_toolbar_position_picker.xml
@@ -18,7 +18,7 @@
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingBottom="14dp"
- android:text="@string/onboarding_toolbar_position_header"
+ android:text="@string/onboarding_toolbar_placement_header_1"
android:textAppearance="@style/HeaderTextStyle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
@@ -29,52 +29,11 @@
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingEnd="16dp"
- android:text="@string/onboarding_toolbar_position_description"
+ android:text="@string/onboarding_toolbar_placement_description_1"
android:textAppearance="@style/Body14TextStyle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/header_text" />
-
-
-
-
-
-
@@ -117,5 +76,45 @@
app:layout_constraintTop_toBottomOf="@id/toolbar_bottom_image"
app:layout_constraintBottom_toBottomOf="parent"/>
+
+
+
+
+
diff --git a/app/src/main/res/layout/onboarding_tracking_protection.xml b/app/src/main/res/layout/onboarding_tracking_protection.xml
index 46e07abd6..e97cc3041 100644
--- a/app/src/main/res/layout/onboarding_tracking_protection.xml
+++ b/app/src/main/res/layout/onboarding_tracking_protection.xml
@@ -18,23 +18,13 @@
android:drawablePadding="12dp"
android:gravity="center_vertical"
android:lines="1"
- android:text="@string/onboarding_tracking_protection_header_2"
+ android:text="@string/onboarding_tracking_protection_header_3"
android:textAppearance="@style/HeaderTextStyle"
- app:layout_constraintEnd_toStartOf="@id/tracking_protection_toggle"
+ app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:drawableStart="@drawable/ic_onboarding_tracking_protection" />
-
-
+ tools:text="@string/onboarding_tracking_protection_description_3" />
@@ -87,7 +77,7 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tracking_protection_standard_option"
app:onboardingKey="@string/pref_key_tracking_protection_strict_default"
- app:onboardingKeyDescription="@string/onboarding_tracking_protection_strict_button_description_2"
+ app:onboardingKeyDescription="@string/onboarding_tracking_protection_strict_button_description_3"
app:onboardingKeyTitle="@string/onboarding_tracking_protection_strict_option"
tools:text="Strict" />
\ No newline at end of file
diff --git a/app/src/main/res/layout/preference_credit_cards_add_credit_card.xml b/app/src/main/res/layout/preference_credit_cards_add_credit_card.xml
new file mode 100644
index 000000000..41c8b247c
--- /dev/null
+++ b/app/src/main/res/layout/preference_credit_cards_add_credit_card.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/private_browser_tray_list.xml b/app/src/main/res/layout/private_browser_tray_list.xml
index bd746632a..21ce16541 100644
--- a/app/src/main/res/layout/private_browser_tray_list.xml
+++ b/app/src/main/res/layout/private_browser_tray_list.xml
@@ -1,6 +1,24 @@
-
+ android:layout_height="match_parent">
+
+
+
+
+
diff --git a/app/src/main/res/layout/quicksettings_tracking_protection.xml b/app/src/main/res/layout/quicksettings_tracking_protection.xml
deleted file mode 100644
index e04f94ed8..000000000
--- a/app/src/main/res/layout/quicksettings_tracking_protection.xml
+++ /dev/null
@@ -1,38 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/layout/share_close.xml b/app/src/main/res/layout/share_close.xml
index 1221af606..8bc84de86 100644
--- a/app/src/main/res/layout/share_close.xml
+++ b/app/src/main/res/layout/share_close.xml
@@ -24,7 +24,7 @@
app:iconTint="@color/neutral_text"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
- app:srcCompat="@drawable/mozac_ic_close" />
+ app:srcCompat="@drawable/ic_close" />
diff --git a/app/src/main/res/layout/tab_tray_grid_item.xml b/app/src/main/res/layout/tab_tray_grid_item.xml
index dd83b20dc..35cdb4c8f 100644
--- a/app/src/main/res/layout/tab_tray_grid_item.xml
+++ b/app/src/main/res/layout/tab_tray_grid_item.xml
@@ -76,7 +76,7 @@ A FrameLayout here is an efficient way of having a views stack while allowing:
app:layout_constraintBottom_toTopOf="@+id/horizonatal_divider"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
- app:srcCompat="@drawable/mozac_ic_close"
+ app:srcCompat="@drawable/ic_close"
app:tint="@color/photonInk80" />
diff --git a/app/src/main/res/layout/tabs_tray_tab_counter2.xml b/app/src/main/res/layout/tabs_tray_tab_counter2.xml
new file mode 100644
index 000000000..25036e94f
--- /dev/null
+++ b/app/src/main/res/layout/tabs_tray_tab_counter2.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/tabstray_multiselect_items.xml b/app/src/main/res/layout/tabstray_multiselect_items.xml
index 795753f94..22d325a1a 100644
--- a/app/src/main/res/layout/tabstray_multiselect_items.xml
+++ b/app/src/main/res/layout/tabstray_multiselect_items.xml
@@ -32,7 +32,7 @@
app:layout_constraintBottom_toBottomOf="@id/topBar"
app:layout_constraintEnd_toStartOf="@id/menu_multi_select"
app:layout_constraintTop_toTopOf="@id/topBar"
- app:srcCompat="@drawable/ic_share_filled"
+ app:srcCompat="@drawable/ic_share"
app:tint="@color/contrast_text_normal_theme" />
diff --git a/app/src/main/res/menu/credit_card_editor.xml b/app/src/main/res/menu/credit_card_editor.xml
new file mode 100644
index 000000000..5b4c672a2
--- /dev/null
+++ b/app/src/main/res/menu/credit_card_editor.xml
@@ -0,0 +1,20 @@
+
+
+
diff --git a/app/src/main/res/menu/history_select_multi.xml b/app/src/main/res/menu/history_select_multi.xml
index 52f7aed44..344b19c44 100644
--- a/app/src/main/res/menu/history_select_multi.xml
+++ b/app/src/main/res/menu/history_select_multi.xml
@@ -6,7 +6,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto">
diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml
index 23f548ae9..a302b5d2f 100644
--- a/app/src/main/res/navigation/nav_graph.xml
+++ b/app/src/main/res/navigation/nav_graph.xml
@@ -27,7 +27,7 @@
android:id="@+id/action_global_search_dialog"
app:destination="@id/searchDialogFragment"
app:popUpTo="@id/searchDialogFragment"
- app:popUpToInclusive="true"/>
+ app:popUpToInclusive="true" />
+ app:popUpToInclusive="true" />
+
+
+ app:argType="mozilla.components.browser.state.state.content.PermissionHighlightsState" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml
index 7e4a14da9..0d4a6b2d1 100644
--- a/app/src/main/res/values-ar/strings.xml
+++ b/app/src/main/res/values-ar/strings.xml
@@ -609,12 +609,16 @@
جلسة خاصةالألسنة الخاصة
+
+ الألسنة المُزامنةأضِف لسانًاأضِف لسانًا خاصًاخاص
+
+ زامِنالألسنة المفتوحة
@@ -625,6 +629,8 @@
شارِك كل الألسنةالألسنة المُغلقة حديثًا
+
+ إعدادات الحسابإعدادات اللسان
@@ -979,8 +985,6 @@
كل الإجراءاتالمستخدمة حديثًا
-
- ولجت باسم %1$sلِج كي تبدأ المزامنة
@@ -1180,6 +1184,8 @@
ألديك أسئلة عن متصفّح %s الذي أعدنا تصميمه؟ أتريد معرفة ما تغيّر؟ستجد هنا إجابات أسئلتك
+
+ زامِن Firefox بين الأجهزةاسحب العلامات والتأريخ وكلمات السر إلى Firefox على هذا الجهاز.أضِف بطاقة ائتمان
+
+ أدِر البطاقات المحفوظةأضِف بطاقة
+
+ حرّر البطاقةرقم البطاقة
@@ -1564,6 +1574,8 @@
الاسم على البطاقةالاسم المستعار للبطاقة
+
+ احذف البطاقةاحذف البطاقة
@@ -1573,6 +1585,9 @@
ألغِ
+
+ البطاقات المحفوظة
+
أضِف محرك بحث
@@ -1732,6 +1747,9 @@
ألغِ
+
+ اضبط روابط المواقع والبريد الإلكتروني والرسائل لتفتح تلقائيًا في Firefox.
+
أزِل
diff --git a/app/src/main/res/values-ast/strings.xml b/app/src/main/res/values-ast/strings.xml
index 086fb48cb..dde8d7e9b 100644
--- a/app/src/main/res/values-ast/strings.xml
+++ b/app/src/main/res/values-ast/strings.xml
@@ -413,18 +413,18 @@
- Proteición escontra\'l rastrexu
+ Proteición antirrastrexu
- Proteición escontra\'l rastrexu
+ Proteición antirrastrexuEsceiciones
- La proteición escontra\'l rastrexu ta desactivada pa estos sitios web
+ La proteición antirrastrexu ta desactivada pa estos sitios webActivar en tolos sitios
- Les esceiciones déxente desactivar la proteición escontra\'l rastrexu nos sitios esbillaos.
+ Les esceiciones déxente desactivar la proteición antirrastrexu nos sitios esbillaos.Deprender más
@@ -588,6 +588,8 @@
Llingüetes privaes
+
+ Llingüetes sincronizaesAmestar una llingüeta
@@ -949,8 +951,6 @@
Usóse apocayá
-
- Aniciesti sesión como %1$sAniciar sesión pa sincronizar
@@ -1160,6 +1160,8 @@
¿Tienes entrugues tocante al rediseñu de %s?¿Quies saber qué camudó?Consigui les rempuestes equí
+
+ Lleva los marcadores, l\'historial y les contraseñes de Firefox nesti preséu.
@@ -1175,12 +1177,22 @@
Hebo un fallu al aniciar sesión
+
+ Privacidá permanente
+
+ Firefox fai automáticamente que les compañes dexen de siguite dafurto pela web.Estándar (por defeutu)
+
+ Equilibriu ente privacidá y rindimientu. Les páxines van cargar con normalidá.Estricta (aconséyase)Estricta
+
+ Bloquia más rastrexadores polo que les páxines van cargar más rápido mas dalgunes puen estropiase.
+
+ Sitiu de la barra de ferramientesRestolar en privaoEscoyeta d\'un estilu
+
+ Aforra daqué de batería y amenorga\'l cansanciu de güeyos col mou escuru.Siguir l\'estilu del sistema
@@ -1256,7 +1270,7 @@ en llinia y con nós.Axustes de la proteición
- Proteición ameyorada escontra\'l rastrexu
+ Proteición antirrastrexu ameyoradaRestola ensin que te sigan
@@ -1265,10 +1279,14 @@ en llinia y con nós.Deprender másEstándar (por defeutu)
+
+ Equilibriu ente privacidá y rindimientu. Les páxines van cargar con normalidá.Qué se bloquia coles proteición estándar escontra\'l rastrexuEstricta
+
+ Bloquia más rastrexadores polo que les páxines van cargar más rápido mas dalgunes puen estropiase.Lo que la proteición estricta escontra\'l rastrexu bloquia
@@ -1331,7 +1349,7 @@ en llinia y con nós.DESACTIVÓSE nesti sitiu
- La proteición ameyorada escontra\'l rastrexu ta desactivada pa estos sitios web
+ La proteición antirrastrexu ameyorada ta desactivada pa estos sitios webDir p\'atrás
diff --git a/app/src/main/res/values-be/strings.xml b/app/src/main/res/values-be/strings.xml
index 819bf3b10..1c303a0e5 100644
--- a/app/src/main/res/values-be/strings.xml
+++ b/app/src/main/res/values-be/strings.xml
@@ -49,7 +49,7 @@
Вылучана
- %1$s распрацаваны @fork-maintainers.
+ %1$s распрацаваны Mozilla.
@@ -181,6 +181,9 @@
Дадаць
+
+ Змяніць
+
Немагчыма злучыцца. Невядомая схема URL.
@@ -606,12 +609,16 @@
Прыватныя карткі
+
+ Сінхранізаваныя карткіДадаць карткуДадаць прыватную карткуПрыватная
+
+ СінхранізацыяАдкрытыя карткі
@@ -622,6 +629,8 @@
Падзяліцца ўсімі карткаміНядаўна закрытыя карткі
+
+ Налады ўліковага запісуНалады картак
@@ -981,6 +990,8 @@
Усе дзеянніНядаўна выкарыстаныя
+
+ Увайсці ў сінхранізацыюУвайсці ў сінхранізацыю
@@ -1188,6 +1199,8 @@
Адказы тутСінхранізуйце Firefox паміж прыладамі
+
+ Перанясіце закладкі, гісторыю і паролі ў Firefox на гэтай прыладзе.
@@ -1204,12 +1217,24 @@
Сінхранізацыя ўключанаНяўдача ўваходу
+
+ Прыватнасць заўжды ўключана
+
+ Firefox аўтаматычна спыняе таемнае сачэнне кампаній за вамі ў Інтэрнэце.Стандартная (прадвызначана)
+
+ Збалансаваная прыватнасць і прадукцыйнасць. Старонкі загружаюцца нармальна.Строгая (рэкамендуецца)Строгая
+
+ Блакуе больш трэкераў, так што старонкі атрымліваюцца хутчэй, але частка іх функцый можа парушыцца.
+
+ Выберыце размяшчэнне панэлі інструментаў
+
+ Майце панэль інструментаў пад рукой. Пакіньце яе ўнізе або перамясціце ўверх.Аглядайце прыватнаВыберыце тэму
+
+ Зберажыце зарад батарэі і свой зрок з цёмным рэжымам.Аўтаматычна
@@ -1290,10 +1317,14 @@
Даведацца большСтандартная (прадвызначана)
+
+ Збалансаваная прыватнасць і прадукцыйнасць. Старонкі загружаюцца нармальна.Што блакуецца стандартнай аховай ад сачэнняСтрогая
+
+ Блакуе больш трэкераў, так што старонкі атрымліваюцца хутчэй, але частка іх функцый можа парушыцца.Што блакуецца строгай аховай ад сачэння
@@ -1537,6 +1568,47 @@
Меню сартавання лагінаў
+
+
+ Крэдытныя карты
+
+
+ Захоўваць і аўтаматычна запаўняць карты
+
+ Дадзеныя зашыфраваны
+
+ Сінхранізаваць карты паміж прыладамі
+
+ Дадаць крэдытную карту
+
+ Кіраванне захаванымі картамі
+
+
+ Дадаць карту
+
+ Змяніць карту
+
+
+ Нумар карты
+
+ Тэрмін дзеяння
+
+ Імя на карце
+
+ Псеўданім карты
+
+ Выдаліць карту
+
+ Выдаліць карту
+
+ Захаваць
+
+ Захаваць
+
+ Адмена
+
+ Захаваныя карты
+
Дадаць пашукавік
@@ -1702,6 +1774,9 @@
Адмена
+
+ Наладзьце аўтаматычна адкрываць спасылкі з сайтаў, пошты і паведамленняў у Firefox.
+
Выдаліць
diff --git a/app/src/main/res/values-br/strings.xml b/app/src/main/res/values-br/strings.xml
index 5dbe8b20e..470b8a8cd 100644
--- a/app/src/main/res/values-br/strings.xml
+++ b/app/src/main/res/values-br/strings.xml
@@ -17,10 +17,6 @@
Diskouezet e vo amañ hocʼh ivinelloù digoret.Diskouezet e vo amañ hocʼh ivinelloù prevez.
-
- Baidu
-
- JD1 ivinell digor. Stokit evit mont dʼun ivinell all.
@@ -177,6 +173,13 @@
Digeriñ en arloadNeuz
+
+ Personelaat ar mod lenn
+
+ Ouzhpennañ
+
+ Embann
+
Ne cʼhaller ket kennaskañ. Steuñv URL dianav.
@@ -407,6 +410,11 @@
and the third is the device model. -->
%1$s war %2$s %3$s
+
+ Kartennoù kred
+
+ Chomlecʼhioù
+
Ivinelloù degemeret
@@ -437,9 +445,6 @@
Gouzout hirocʼh
-
- Diweredekaet en holl. Kit en arventennoù evit e weredekaat.
-
Telemetry
@@ -450,6 +455,8 @@
Roadennoù MarketingRann ar roadennoù diwar-benn ar cʼheweriusterioù arveret ganeocʼh e %1$s gant Leanplim, hor cʼheveler marketing hezoug.
+
+ Rannañ titouroù implijoù diazez gant Adjust, hor cʼheveler marketing hezougStudioù
@@ -596,12 +603,16 @@
Estez prevezIvinelloù prevez
+
+ Ivinelloù goubredetOuzhpennañ un ivinellOuzhpennañ un ivinell prevezPrevez
+
+ GoubredañIvinelloù digor
@@ -612,6 +623,8 @@
Rannañ an holl ivinelloùIvinelloù serret nevez zo
+
+ Arventennoù ar gontArventennoù an ivinelloù
@@ -780,8 +793,6 @@
Dilamet %1$sOuzhnenañ an teuliad
-
- Sined krouet.Sined enrollet!
@@ -965,6 +976,8 @@
An holl oberennoùArveret da ziwezhañ
+
+ Kennaskañ evit goubredañKennaskañ ouzh Sync
@@ -1160,9 +1173,6 @@
Donemat war %s!Ur gont hoc’h eus dija?
-
- Grit anaoudegezh gant %sGwelout pezh a zo nevezAmañ emañ ar respontoù
-
- Krogit da c’houbredañ ho sinedoù, ho kerioù-tremen, ha muioc’h c’hoazh gant ho kont FIrefox.
+
+ Goubredañ Firefox etre an trevnadoù
- Gouzout hiroc’h
+ Degasit sinedoù, ur roll-istor ha gerioù-tremen evit Firefox war an trevnad-mañ.
@@ -1182,8 +1192,8 @@
Ya, kennaskit ac’hanonO kennaskañ…
-
- Kennaskañ ouzh FIrefox
+
+ En em rollañChom kennasket
@@ -1191,26 +1201,23 @@
C’hwitadenn war ar c’hennaskañ
- Prevezded emgefreek
-
- Ar arventennoù prevezded ha diogelroez a stank an heulierien, ar meziantoù touellus hag an embregerezhioù a heuilh ac’hanoc’h.
+ Prevezded gweredekaet bepred
+
+ Firefox a harz en un doare emgefreek ouzh an embregerezhioù d’ho heuliañ dre guzh war ar web.Skoueriek (dre ziouer)
- Na stank ket kement a heulier. Ar pajennoù a gargo evel boaz.
+ Kempouezet evit prevezded hag efedusted. Ar pajennoù a garg en un doare ordinal.Strizh (erbedet)Strizh
- Stankañ a raio muioc’h a heulierien, bruderezhioù ha diflugelloù. Buanoc’h e kargo ar pajennoù, met gallout a ra traoù ’zo chom hep mont en-dro.
-
- Dibabit un tu
+ Stankañ muioc’h a heulierien hag ar pajennoù da gargañ buanoc’h, met arc’hweladurioù zo a c’hallo mont a-dreuz.
+
+ Dibabit e pelec’h lakaat ho parrenn ostilhoù
- Klaskit ar merdeiñ gant un dorn a-drugarez d’ar varrenn-ostilhoù en traoñ, pe laoskit anezhi en nec’h.
+ Lakait ar varrenn ostilhoù en ul lec’h aes da dizhout. Dalc’hit hi en traoñ pe diblasit anezhi e-krec’h.Merdeiñ prevezHo puhez prevez
+ The first parameter is the name of the app (e.g. Firefox Preview) Substitute %s for long browser name. -->
Savet hon eus %s evit ma c’hallfec’h reoliñ ar pezh a rannit enlinenn hag ar pezh a rannit ganeomp.Lennit hon evezhiadenn a-get buhez prevez
@@ -1238,7 +1245,7 @@
Dibabit an neuz
- Espernit ho patiri hag ho taoulagad gant ar mod teñval.
+ Espernit batiri hag ho sell gant ar mod teñval.Emgefreek
@@ -1293,13 +1300,13 @@
Skoueriek (dre ziouer)
- Na stank ket kement a heulier. Ar pajennoù a gargo evel boaz.
+ Kempouezet evit prevezded hag efedusted. Ar pajennoù a garg en un doare ordinal.Ar pezh a zo stanket gant ar gwarez heuliañ skoueriekStrizh
- Stankañ a raio muioc’h a heulierien, bruderezhioù ha diflugelloù. Buanoc’h e kargo ar pajennoù, met gallout a ra traoù ’zo chom hep mont en-dro.
+ Stankañ muioc’h a heulierien hag ar pajennoù da gargañ buanoc’h, met arc’hweladurioù zo a c’hallo mont a-dreuz.Ar pezh a zo stanket gant ar gwarez heuliañ strizh
@@ -1439,10 +1446,8 @@
Leuniañ ent emgefreekGoubredañ an titouroù kennaskañ
-
- YA
-
- KET
+
+ Goubredañ an titouroù kennaskañ dre an trevnadoùAdkennaskañ
@@ -1533,6 +1538,50 @@
Lañser rummañ an titouroù kennaskañ
+
+
+ Kartennoù kred
+
+ Enrollañ ha leuniañ ar c’hartennoù en un doare emgefreek
+
+ Rineget eo ar roadennoù
+
+ Goubredañ ar c’hartennoù kred etre ho trevnadoù
+
+ Goubredañ ar c\'hartennoù
+
+ Ouzhpennañ ur gartenn gred
+
+ Merañ ar c’hartennoù enrollet
+
+ Ouzhpennañ ur gartenn
+
+ Kemmañ ar gartenn
+
+ Niverenn ar gartenn
+
+
+ Deiziad termen
+
+ Anv war ar gartenn
+
+ Lesanv ar gartenn
+
+ Dilemel ar gartenn
+
+ Dilemel ar gartenn
+
+ Enrollañ
+
+ Enrollañ
+
+ Nullañ
+
+ Kartennoù enrollet
+
+
+ Biziatait un niverenn kartenn gred talvoudek mar plij.
+
Ouzhpennañ ul lusker klask
@@ -1691,23 +1740,19 @@
Nullañ
+
+ Digeriñ liammoù al lec’hiennoù, posteloù ha kemennadennoù e Firefox en un doare emgefreek.
+
Dilemel
-
- Tennit gounid eus %s.
-
Klikit da gaout muioc’h a vunudoù
-
- Dastumit ar pezh a zo pouezus evidoc‘h
-
- Strollit ar cʼhlaskoù, al lecʼhiennoù hag an ivinelloù heñvel evit mont daveto buanocʼh.
-
- Kennasket hoc’h evel %s war ur merdeer Firefox all war ar pellgomzer-mañ. Fellout a ra deoc’h kennaskañ gant ar gont-mañ?
-
- Gallout a rit ouzhpennañ al lec’hienn d’ho pennbajenn en un doare aes evit gallout he haeziñ ha merdeiñ buanoc’h, evel un arload.
+
+ Adpignat
+
+
+ Serriñ
-
+
diff --git a/app/src/main/res/values-cak/strings.xml b/app/src/main/res/values-cak/strings.xml
index 5eefaee35..d39f31f59 100644
--- a/app/src/main/res/values-cak/strings.xml
+++ b/app/src/main/res/values-cak/strings.xml
@@ -22,10 +22,6 @@
Wawe\' xkeq\'alajin pe ri ichinan taq ruwi\'.
-
- Baidu
-
- JD1 ruwi\' jaqon. Tachapa\' richin nak\'ëx ruwi\'.
@@ -58,7 +54,7 @@
Xcha\'
- %1$s b\'anon ruma @fork-maintainers.
+ %1$s b\'anon ruma Mozilla.
@@ -189,6 +185,12 @@
Ke\'ichinäx rutz\'etoj sik\'inem
+
+ Titz\'aqatisäx
+
+
+ Tinuk\'
+
Man nok ta. Man netamäx ta ruwa ruch\'akulal URL.
@@ -427,6 +429,11 @@
and the third is the device model. -->
%1$s pa %2$s %3$s
+
+ Rutarjeta\' kre\'ito\'
+
+ Taq ochochib\'äl
+
Taq ruwi\' ek\'ulun
@@ -459,9 +466,6 @@
Tetamäx ch\'aqa\' chik
-
- Tachupu\' chijun, jät pa Nuk\'ulem richin natzïj.
-
Etataqonel
@@ -472,6 +476,8 @@
Marketin tzijKe\'akomonij taq tzij chi rij ri taq samaj nawokisaj pa %1$s rik\'in Leanplum, ri ya\'öl qamarketin richin oyonib\'äl.
+
+ Ke\'ichinäx nab\'ey taq tzij ye\'okisäx pa Adjust, ya\'öl qamarkentin richin oyonib\'älTaq tijonïk
@@ -624,12 +630,16 @@
Ichinan molojri\'ïlIchinan taq ruwi\'
+
+ Ximon taq ruwi\'Titz\'aqatisäx ruwi\'Titz\'aqatisäx ichinan ruwi\'Ichinan
+
+ TiximKejaq taq Ruwi\'
@@ -640,6 +650,8 @@
Kekomonïx ronojel taq ruwi\'Taq ruwi\' nimakol ketz\'apïx
+
+ Kinuk\'ulem rub\'i\' taqoya\'lKinuk\'ulem ruwi\'
@@ -812,8 +824,6 @@
Xyuj %1$sTitz\'aqatisäx Yakwuj
-
- Xtz\'ukutäj ri yaketal.¡Xyak yaketal!
@@ -1001,6 +1011,8 @@
Ronojel b\'anojK\'a b\'a ke\'okisäx
+
+ Tatikirisaj molojri\'ïl richin niximTatikirisaj molojri\'ïl pa Sync
@@ -1198,9 +1210,6 @@
¡Niya\' rutzil awäch pa %s!¿La k\'o chik jun rub\'i\' ataqoya\'l?
-
- Tawetamaj ruwa %sTatz\'eta\' ri k\'ak\'a\'
@@ -1209,10 +1218,10 @@
¿La k\'o ak\'utunik chi rij ri xwachib\'ëx chik chi rij ri %s? ¿La nawajo\' nawetamaj achike ri xjalatäj?Wawe\' e k\'o ri tzolin taq tzij
-
- Tachapa\' kiximik taq yaketal, ewan taq tzij chuqa\' ch\'aqa\' chik rik\'in ri rub\'i\' ataqoya\'l richin Firefox.
+
+ Taxima\' Firefox pa taq awokisab\'al
- Tetamäx ch\'aqa\' chik
+ Kek\'am pe taq yaketal, natab\'äl chuqa\' ewan taq tzij pa Firefox pa re okisab\'äl re\'.
@@ -1221,8 +1230,8 @@
Ja\', titikirisäx molojri\'ïlNitikirisäx molojri\'ïl…
-
- Titikirisäx molojri\'ïl pa Firefox
+
+ Tatz\'ib\'aj ab\'i\'Jantape\' chupül
@@ -1231,26 +1240,23 @@
Xsach toq xtikirisäx molojri\'ïl
- Ichinanem pa ruyonil
-
- Ri runuk\'ulem ichinanem chuqa\' jikomal yekiq\'ät ri ojqanela\', itzel taq kema\' chuqa\' ri ajk\'aymoloj yatkojqaj.
+ Junelïk tzijïl ichinanem
+
+ Junaman richin ichinanem chuqa\' rub\'eyal nisamäj. Yesamajïx ri taq ruxaq pa relik rub\'eyal.Junaman (k\'o wi)
- Yeruq\'ät jub\'a\' ojqanela\'. Ütz xkesamajïx ri taq ruxaq.
+ Junaman richin ichinanem chuqa\' rub\'eyal nisamäj. Yesamajïx ri taq ruxaq pa relik rub\'eyal.K\'atzinel (chilab\'en)Nimaläj
- Ke\'aq\'ata\' k\'ïy ojqanela\', taq rutzijol chuqa\' elenel taq tzuwäch. Anin xkesamajitäj ri taq ruxaq, xa xe chi yatikïr ye\'asäch jujun taq rusamajib\'al.
-
- Tajikib\'a\' atzij
+ Yeruq\'ät mas ojqanela\' richin anin yesamäj ri taq ruxaq, po rik\'in jub\'a\' jujun taq rusamaj re ruxaq xkesachiyaj.
+
+ Tacha\' ri ruk\'ojlib\'al rukajtz\'ik samajib\'äl
- Tatojtob\'ej ri okem pa k\'amaya\'l rik\'in jun aq\'a\' pa ri ikim rukajtz\'ik samajib\'äl o tasiloj ajsik.
+ Taya\' pan aq\'a\' ri rukajtz\'ik samajib\'äl. Taya\' ikim o tasiloj pa jotöl.Tawichinaj ri awokem
+ The first parameter is the name of the app (e.g. Firefox Preview) Substitute %s for long browser name. -->
Xqawachib\'ej %s richin nachajij ri nakomonij pa k\'amab\'ey chuqa\' ri nakomonij qik\'in.Tasik\'ij ri rutzijol qichinanem
@@ -1279,7 +1285,7 @@
Tacha\' ri awachinel
- Takolo\' jub\'a\' awuchuq\'ab\'al chuqa\' tachajij ri atzub\'al: Tatzija\' ri q\'equ\'m rub\'anikil.
+ Takolo\' jub\'a\' ruwateriya\' chuqa\' tachajij ri runaq\' awäch, tacha\' ri q\'ëq rub\'anikil.Yonil
@@ -1336,14 +1342,14 @@
Junaman (k\'o wi)
- Yeruq\'ät jub\'a\' ojqanela\'. Ütz xkesamajïx ri taq ruxaq.
+ Junaman richin ichinanem chuqa\' rub\'eyal nisamäj. Yesamajïx ri taq ruxaq pa relik rub\'eyal.Achike ri q\'aton ruma ri relik ruchajixik ojqanemNimaläj
- Ke\'aq\'ata\' k\'ïy ojqanela\', taq rutzijol chuqa\' elenel taq tzuwäch. Anin xkesamajitäj ri taq ruxaq, xa xe chi yatikïr ye\'asäch jujun taq rusamajib\'al.
+ Yeruq\'ät mas ojqanela\' richin anin yesamäj ri taq ruxaq, po rik\'in jub\'a\' jujun taq rusamaj re ruxaq xkesachiyaj.Achike ri q\'aton ruma ri ruchajixik ojqanem k\'o
@@ -1488,10 +1494,8 @@
Nunojisaj pa ruyonilSync tikirisanïk molojri\'ïl
-
- Tzijïl
-
- Chupül
+
+ Sync rutikirib\'al molojri\'ïl rik\'in okisaxelTok chik
@@ -1596,9 +1600,43 @@
Ewan kisik\'ixik ri taq tzijSync tarjeta\' chi kikojol okisab\'äl
+
+ Sync taq tarjeta\'Titz\'aqatisäx rutarjeta\' kre\'ito\'
+
+ Kenuk\'samajïx yakon taq tarjeta\'
+
+ Titz\'aqatisäx tarjeta\'
+
+ Tinuk\' tarjeta\'
+
+ Rajilab\'al Tarjeta\'
+
+
+ Q\'in Nik\'is Ruq\'ijul
+
+ B\'i\'aj pa Tarjeta\'
+
+ Tz\'ukun Rub\'i\' Tarjeta\'
+
+
+ Tiyuj tarjeta\'
+
+ Tiyuj tarjeta\'
+
+ Tiyak
+
+ Tiyak
+
+ Tiq\'at
+
+ Keyak tarjeta\'
+
+
+ Titz\'ib\'äx jun okel rajilab\'al rutarjeta\' kre\'ito\'
+
Titz\'aqatisäx kanob\'äl
@@ -1760,23 +1798,19 @@ Achi\'el: \nhttps://www.google.com/search?q=%sTiq\'at
+
+ Nub\'än kinuk\'ulem ximonel ajk\'amaya\'l taq ruxaq, taq taqoya\'l chuqa\' taq rutzijol richin yejaq pa ruyonil pa Firefox.
+
Tiyuj
-
- Ütz tawokisaj ri %s.
-
Tipitz\' richin ch\'aqa\' rub\'anikil
-
- Ke\'amolo\' ri taq wachinäq niqa chawa
-
- Ketzob\'ajïx taq kanoxïk, taq ruxaq chuqa\' taq ruwi\' ejunam richin ye\'okisäx na.
-
- Xatikirisaj molojri\'ïl pa jun chik rokik\'amaya\'l Firefox achi\'el %s pa re oyonib\'äl re\'. ¿La nawajo\' natikirisaj molojri\'ïl rik\'in re rub\'i\' taqoya\'l re\'?
-
- Anin yatikïr naya\' re ajk\'amaya\'l pa ri Rutikirib\'al ruxaq awoyonib\'al richin aninäq nawokisaj chuqa\' aninäq yatok pa k\'amaya\'l, achi\'el ta xa jun chokoy.
+
+ Tib\'an okem ajsik
+
+
+ Titz\'apïx
-
+
diff --git a/app/src/main/res/values-co/strings.xml b/app/src/main/res/values-co/strings.xml
index a8b76440e..32445af8a 100644
--- a/app/src/main/res/values-co/strings.xml
+++ b/app/src/main/res/values-co/strings.xml
@@ -20,10 +20,6 @@
E vostre unghjette private seranu affissate quì.
-
- Baidu
-
- JD1 unghjetta aperta. Picchichjà per cambià d’unghjetta.
@@ -187,9 +183,14 @@
Persunalizà u modu di lettura
+
+ Aghjunghje
+
+ Mudificà
+
- Impussibule di cunnettassi. Schema d’URL scunnisciutu.
+ Impussibule di cunnettesi. Schema d’URL scunnisciutu.
@@ -217,7 +218,7 @@
Ùn permette micca
- Permette e suggestioni di ricerca in e sessioni private ?
+ Permette e sugestioni di ricerca in e sessioni private ?%s manderà tuttu ciò chì vò scriverete in a barra di ricerca à u vostru mutore di ricerca predefinitu.
@@ -301,7 +302,7 @@
Contu
- Cunnettassi
+ CunnettesiBarra d’attrezzi
@@ -317,7 +318,7 @@
Contu Firefox
- Ricunnettatevi per cuntinuà a sincrunizazione
+ Ricunnittetevi per cuntinuà a sincrunizazioneLingua
@@ -333,13 +334,13 @@
Affissà i mutori di ricerca
- Affissà e suggestioni di ricerca
+ Affissà e sugestioni di ricercaAffissà a ricerca vucaleAffissà durante e sessioni di navigazione privata
- Affissà e suggestioni di u preme’papei
+ Affissà e sugestioni di u preme’papeiRicercà in a cronolugia di navigazione
@@ -398,7 +399,7 @@
Apre l’unghjette
- Scunnettassi
+ ScunnettesiNome di l’apparechju
@@ -419,6 +420,12 @@
and the third is the device model. -->
%1$s nant’à %2$s %3$s
+
+ Carte bancarie
+
+
+ Indirizzi
+
Unghjette ricevute
@@ -449,9 +456,6 @@
Sapene di più
-
- Disattivata di manera glubale, andate in e Preferenze per attivalla.
-
Telemetria
@@ -459,9 +463,11 @@
Sparte cù Mozilla i dati d’andamentu, d’impiegu, di materiale è di persunalizazione di u vostru navigatore per aiutacci à amendà %1$s
- Dati di cummercializazione
+ Dati cummerciali
- Scumparte i dati nant’à e funzioni di %1$s chì voi impiegate cù Leanplum, u nostru furnidore di cummercializazione mubile.
+ Scumparte i dati nant’à e funzioni di %1$s chì voi impiegate cù Leanplum, u nostru venditore di dati cummerciali d’apparechji mubili.
+
+ Spartera di i dati d’adopru basichi cù Adjust, u nostru venditore di dati cummerciali d’apparechji mubiliStudii
@@ -483,9 +489,9 @@
Numerizà u codice d’associu nant’à Firefox per l’urdinatore
- Cunnettassi
+ Cunnettesi
- Autenticassi per ricunnettassi
+ Autenticassi per ricunnettesiCaccià u contu
@@ -611,12 +617,16 @@
Sessione privataUnghjette private
+
+ Unghjette sincrunizateAghjunghje un’unghjettaApre una nova unghjetta privataUnghjetta privata
+
+ SincrunizazioneUnghjette aperte
@@ -627,6 +637,8 @@
Sparte tutte l’unghjetteUnghjette chjose pocu fà
+
+ Preferenze di u contuPreferenze di l’unghjette
@@ -796,8 +808,6 @@
%1$s squassatuAghjunghje un cartulare
-
- Indetta aghjunta.Indetta arregistrata
@@ -826,7 +836,7 @@
Mudificà u cartulare
- Cunnettatevi per fighjà l’indette sincrunizate
+ Cunnittetevi per fighjà l’indette sincrunizateURL
@@ -985,18 +995,20 @@
Tutte l’azzioniImpiegate pocu fà
+
+ Cunnettesi per sincrunizà
- Cunnettassi à Sync
+ Cunnettesi à SyncMandà à tutti l’apparechji
- Ricunnettassi à Sync
+ Ricunnettesi à SyncFora di cunnessione
- Cunnettà un altru apparechju
+ Cunnette un altru apparechju
- Per mandà un’unghjetta, cunnettatevi à u vostru contu Firefox nant’à omancu un altru apparechju.
+ Per mandà un’unghjetta, cunnittetevi à u vostru contu Firefox nant’à omancu un altru apparechju.Ahju capitu
@@ -1004,11 +1016,11 @@
Mandà à un apparechju
- Alcunu apparechju cunnettu
+ Alcunu apparechju cunnessuSapene di più nant’à u mandà d’unghjette…
- Cunnettà un altru apparechju…
+ Cunnette un altru apparechju…
@@ -1179,9 +1191,6 @@
Benvenuta in %s !Avete dighjà un contu ?
-
- Scuprite %sFighjate ciò chì hè novuTruvate risposte quì
-
- Sincrunizate l’indette, e parolle d’entrata è ancu di più cù u vostru contu Firefox.
+
+ Sincrunizate Firefox trà i vostri apparechji
- Sapene di più
+ Impurtate e vostre indette, cronolugia è parolle d’entrata in Firefox nant’à st’apparechju.
- Site cunnettu cum’è %s nant’à un altru navigatore Firefox cù st’apparechju. Vulete cunnettavvi cù stu contu ?
+ Site cunnessi cum’è %s nant’à un altru navigatore Firefox cù st’apparechju. Vulete cunnettevi cù stu contu ?
- Iè, cunnettatemi
+ Iè, cunnittetemiCunnessione…
-
- Cunnettatevvi à u vostru contu Firefox
+
+ ArregistrassiStà disghjuntu
@@ -1210,26 +1219,23 @@
Fiascu di cunnessione
- Rispettu autumaticu di a vita privata
-
- E preferenze di vita privata è di sicurità bloccanu i perseguitatori, prugrammi animosi è sucietà chì vo seguitanu.
+ Cunfidenzialità sempre attiva
+
+ Firefox impedisce autumaticamente l’imprese di seguitavvi da manera sicreta nant’à u Web.Classica (predefinita)
- Bluccheghja menu di perseguitatori. E pagine si caricanu nurmalmente.
+ Equilibratu trà a cunfidenzialità è a prestazione. E pagine si caricanu nurmalmente.Severa (ricumandata)Severa
- Bluccheghja più perseguitatori, publicità è annunzii micca richiesti. E pagine si caricanu più prestu, ma certi siti puderianu ùn micca funziunà currettamente.
-
- Piglià pusizione
+ Bluccheghja più di perseguitatori è cusì e pagine si caricanu piu prestu, ma certi siti puderianu ùn micca funziunà currettamente.
+
+ Sceglie a pusizione di a vostra barra d’attrezzi
- Pruvate a navigazione à una manu sola cù a barra d’attrezzi inghjò o dispiazzatella insù.
+ Piazzate a barra d’attrezzi à purtata di manu. Lasciatela quaghjò o dispiazzatela quassù.Navigazione privataA vostra vita privata
+ The first parameter is the name of the app (e.g. Firefox Preview) Substitute %s for long browser name. -->
Avemu cuncivitu %s per davvi u cuntrollu nant’à ciò chì vò spartite in linea è nant’à ciò chì vò spartite cù noi.Leghjite a nostra pulitica di cunfidenzialità
@@ -1258,7 +1264,7 @@
Sciglite u vostru tema
- Ecunumizate a vostra batteria è i vostri ochji grazia à u modu scuru.
+ Ecunumizate a vostra batteria è i vostri ochji grazia à u modu scuru.Autumaticu
@@ -1283,7 +1289,7 @@
Prontu à numerizà
- Cunnettatevi cù u vostru apparechju-fotò
+ Cunnittetevi cù u vostru apparechju-fotòImpiegà piuttostu un messaghju elettronicu
@@ -1313,13 +1319,13 @@
Classica (predefinita)
- Bluccheghja menu perseguitatori. E pagine si caricanu nurmalmente.
+ Equilibratu trà a cunfidenzialità è a prestazione. E pagine si caricanu nurmalmente.Ciò chì hè bluccatu da a prutezzione classica contr’à u spiunagiuSevera
- Bluccheghja più perseguitatori, publicità è annunzii micca richiesti. E pagine si caricanu più prestu, ma certi siti puderianu ùn micca funziunà currettamente.
+ Bluccheghja più di perseguitatori è cusì e pagine si caricanu piu prestu, ma certi siti puderianu ùn micca funziunà currettamente.Ciò chì hè bluccatu da a prutezzione severa contr’à u spiunagiu
@@ -1460,14 +1466,12 @@
Riempiimentu autumaticuSincrunizà l’identificazioni
-
- Attivatu
-
- Disattivatu
+
+ Sincrunizà l’identificazioni di cunnessione trà tutti l’apparechji
- Ricunnettassi
+ Ricunnettesi
- Cunnettassi à Sync
+ Cunnettesi à SyncIdentificazioni di cunnessione arregistrate
@@ -1557,6 +1561,50 @@
Listinu di classificazione di l’identificazioni
+
+
+ Carte bancarie
+
+ Arregistrà è riempie autumaticamente e carte
+
+ I dati sò cifrati
+
+ Sincrunizate e carte trà i vostri apparechji
+
+ Sincrunizà e carte
+
+ Aghjunghje una carta bancaria
+
+ Amministrà e carte arregistrate
+
+ Aghjunghje una carta
+
+ Mudificà a carta
+
+ Numeru di carta
+
+
+ Data di scadenza
+
+ Nome di u titulare
+
+ Nome di sta carta
+
+ Squassà a carta
+
+ Squassà a carta
+
+ Arregistrà
+
+ Arregistrà
+
+ Abbandunà
+
+ Carte arregistrate
+
+
+ Ci vole à scrive un numeru accettevule di carta bancaria
+
Aghjunghje un mutore di ricerca
@@ -1682,7 +1730,7 @@
- Cunnettate un altru apparechju.
+ Cunnittete un altru apparechju.Ci vole à autenticassi torna.
@@ -1694,7 +1742,7 @@
Affissate a lista di l’unghjette di i vostri altri apparechji.
- Cunnettassi à Sync
+ Cunnettesi per sincrunizàAlcuna unghjetta aperta
@@ -1719,23 +1767,19 @@
Abbandunà
+
+ Definisce chì e leie di i siti web, i currieri elettronichi è i messaghji s’aprinu autumaticamente in Firefox.
+
Caccià
-
- Ottene u più bellu da %s.
-
Cliccu per sapene di più
-
- Racuglite ciò chì conta per voi
-
- Gruppate inseme ricerche simile, i siti è l’unghjette per un accessu future più prestu.
-
- Site cunnettu cum’è %s nant’à un altru navigatore Firefox cù stu telefonu. Vulete cunnettavvi cù stu contu ?
-
- Hè faciule d’aghjunghje stu situ nant’à u screnu d’accolta di u vostru telefonu per accedeci direttamente è navigà più prestu cum’è s’ella fussi un’appiecazione.
+
+ Navigà insù
+
+
+ Chjode
-
+
diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml
index ebbda1641..33d4a557f 100644
--- a/app/src/main/res/values-cs/strings.xml
+++ b/app/src/main/res/values-cs/strings.xml
@@ -419,6 +419,11 @@
and the third is the device model. -->
%1$s na %2$s %3$s
+
+ Platební karty
+
+ Adresy
+
Přijaté panely
@@ -451,9 +456,6 @@
Zjistit více
-
- Ochranu proti sledování můžete opět zapnout v nastavení aplikace.
-
Telemetrie
@@ -464,6 +466,8 @@
Marketingová dataSdílí data o funkcích, které v aplikaci %1$s používáte, se společností Leanplum, naším partnerem pro mobilní marketing.
+
+ Sdílí základní údaje a používání a technické údaje se společností Adjust, naším partnerem pro marketingStudie
@@ -613,6 +617,8 @@
Anonymní panelyAnonymní panely
+
+ Synchronizované panelyPřidat panel
@@ -631,6 +637,8 @@
Sdílet všechny panelyNedávno zavřené panely
+
+ Nastavení účtuNastavení panelů
@@ -807,8 +815,6 @@
Složka %1$s smazánaPřidat složku
-
- Záložka vytvořena.Záložka uložena
@@ -996,8 +1002,6 @@
Všechny akceNaposledy použité
-
- Jste přihlášeni jako %1$sPřihlásit se k synchronizaci
@@ -1467,10 +1471,8 @@
Automatické vyplňováníSynchronizovat přihlašovací údaje
-
- Zapnuto
-
- Vypnuto
+
+ Synchronizovat přihlašovací údaje mezi zařízenímiZnovu připojit
@@ -1572,6 +1574,8 @@
Data jsou šifrovánaSynchronizovat karty napříč zařízeními
+
+ Synchronizovat platební kartyPřidat platební kartu
@@ -1579,6 +1583,8 @@
Správa uložených karetPřidat kartu
+
+ Upravit kartuČíslo karty
@@ -1587,6 +1593,8 @@
Jméno na kartěVaše označení karty
+
+ Odstranit kartuOdstranit kartu
@@ -1599,6 +1607,9 @@
Uložené karty
+
+ Zadejte prosím platné číslo platební karty
+
Přidat vyhledávač
@@ -1760,23 +1771,19 @@
Zrušit
+
+ Nastavte si automatické otevírání odkazů, e-mailů a zpráv ve Firefoxu.
+
Odstranit
-
- Využijte aplikaci %s naplno.
-
Pro více informací klepněte zde
-
- Uložte si důležité věci do sbírek
-
- Podobná vyhledávání, stránky a panely si můžete seskupit a poté se k nim snadno vracet.
-
- V dalším prohlížeči Firefox na tomto telefonu už jste přihlášení jako %s. Chcete se přihlásit tímto účtem?
-
- Tuto stránku si můžete snadno přidat na domovskou obrazovku svého telefonu. Budete k ní mít okamžitý přístup a prohlížení bude rychlejší se zážitkem jako v aplikaci.
+
+ Přejít nahoru
+
+
+ Zavřít
-
+
diff --git a/app/src/main/res/values-cy/strings.xml b/app/src/main/res/values-cy/strings.xml
index 64ac77826..92cad6812 100644
--- a/app/src/main/res/values-cy/strings.xml
+++ b/app/src/main/res/values-cy/strings.xml
@@ -413,6 +413,11 @@
and the third is the device model. -->
%1$s o %2$s %3$s
+
+ Cardiau credyd
+
+ Cyfeiriadau
+
Tabiau derbyniwyd
@@ -444,9 +449,6 @@
Dysgu rhagor
-
- Wedi’i ddiffodd yn eang, ewch i Gosodiadau i’w droi ymlaen.
-
Telemetreg
@@ -457,6 +459,8 @@
Data marchnataYn rhannu data am ba nodweddion rydych chi’n eu defnyddio yn %1$s gyda Leanplum, ein gwerthwr marchnata symudol.
+
+ Yn rhannu data defnydd sylfaenol gydag Adjust, ein gwerthwr marchnata symudolAstudiaethau
@@ -603,6 +607,8 @@
Sesiwn breifatTabiau preifat
+
+ Tabiau wedi’u cydwedduYchwanegu tab
@@ -621,6 +627,8 @@
Rhannu pob tabTabiau wedi’u cau’n ddiweddar
+
+ Gosodiadau cyfrifGosodiadau tabiau
@@ -791,8 +799,6 @@
Wedi dileu %1$sYchwanegu ffolder
-
- Nod tudalen wedi’i greu.Nod tudalen wedi’i gadw!
@@ -903,7 +909,7 @@
Rhwystro sain a fideo ar ddata cellol yn unig
- Bydd sain a fideo yn chwarae ar ddiwifr
+ Bydd sain a fideo yn chwarae ar Wi-FiRhwystro sain yn unig
@@ -977,8 +983,6 @@
Pob gweithredDefnyddiwyd yn ddiweddar
-
- Mewngofnodwyd fel %1$sMewngofnodi i gydweddu
@@ -1448,10 +1452,8 @@ Fodd bynnag, gall fod yn llai sefydlog. Llwythwch ein porwr Beta i gael profiad
AwtolanwCydweddu mewngofnodion
-
- Ymlaen
-
- Diffodd
+
+ Cydweddu mewngofnodion ar draws dyfeisiauAilgysylltu
@@ -1552,6 +1554,8 @@ Fodd bynnag, gall fod yn llai sefydlog. Llwythwch ein porwr Beta i gael profiad
Mae data wedi’i amgryptioCydweddu cardiau ar draws dyfeisiau
+
+ Cydweddu cardiauYchwanegu cerdyn credyd
@@ -1559,6 +1563,8 @@ Fodd bynnag, gall fod yn llai sefydlog. Llwythwch ein porwr Beta i gael profiad
Rheoli cardiau wedi’u cadwYchwanegu cerdyn
+
+ Golygu cerdynRhif y Cerdyn
@@ -1567,6 +1573,8 @@ Fodd bynnag, gall fod yn llai sefydlog. Llwythwch ein porwr Beta i gael profiad
Enw ar y CerdynLlysenw Cerdyn
+
+ Dileu cerdynDileu cerdyn
@@ -1579,6 +1587,9 @@ Fodd bynnag, gall fod yn llai sefydlog. Llwythwch ein porwr Beta i gael profiad
Cardiau wedi’u cadw
+
+ Rhowch rif cerdyn credyd dilys
+
Ychwanegu peiriant chwilio
@@ -1738,23 +1749,19 @@ Fodd bynnag, gall fod yn llai sefydlog. Llwythwch ein porwr Beta i gael profiad
Diddymu
+
+ Gosod dolenni o wefannau, e-byst, a negeseuon i agor yn awtomatig yn Firefox.
+
Tynnu
-
- Cael y gorau o %s.
-
Cliciwch am ragor o fanylion
-
- Casglwch y pethau sy’n bwysig i chi
-
- Grwpiwch chwiliadau, gwefannau a thabiau tebyg ar gyfer mynediad cyflym yn y dyfodol.
-
- Rydych wedi eich mewngofnodi fel %s ar borwr Firefox arall ar y ffôn hwn. Hoffech chi fewngofnodi gyda’r cyfrif hwn?
-
- Gallwch ychwanegu’r wefan hon yn hawdd i sgrin Cartref eich ffôn. Byddwch yn cael mynediad ar unwaith a phori’n gyflymach gyda phrofiad tebyg i ap.
+
+ Llywio i fyny
+
+
+ Cau
-
+
diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml
index 0a9c77933..2edd89c05 100644
--- a/app/src/main/res/values-da/strings.xml
+++ b/app/src/main/res/values-da/strings.xml
@@ -600,12 +600,16 @@
Privat sessionPrivate faneblade
+
+ Synkroniserede fanebladeTilføj fanebladTilføj privat fanebladPrivat
+
+ SynkroniserÅbne faneblade
@@ -616,6 +620,8 @@
Del alle fanebladeNyligt lukkede faneblade
+
+ KontoindstillingerFanebladsindstillinger
@@ -968,6 +974,8 @@
Alle handlingerBrugt for nyligt
+
+ Log ind for at synkronisereLog ind på Sync
@@ -1186,6 +1194,8 @@
Synkronisering er slået tilKunne ikke logge ind
+
+ Altid aktiveret privatlivsbeskyttelseFirefox forhindrer automatisk virksomheder i at følge dig i smug på nettet.
@@ -1198,6 +1208,10 @@
StriksBlokerer flere sporings-mekanismer. Sider indlæses hurtigere, men noget funktionalitet virker måske ikke.
+
+ Vælg placeringen af din værktøjslinje
+
+ Placer værktøjslinjen indenfor rækkevidde. Behold den nederst, eller flyt den til toppen.Privat browsingTilføj betalingskort
+
+ Håndter gemte kortTilføj kort
+
+ Rediger kortKortnummerUdløbsdatoNavn på kort
+
+ Kortets kaldenavn
+
+ Slet kortSlet kort
@@ -1550,6 +1572,9 @@
Annuller
+
+ Gemte kort
+
Tilføj søgetjeneste
@@ -1707,6 +1732,9 @@
Annuller
+
+ Indstil links fra websteder, mails og beskeder til automatisk at blive åbnet i Firefox.
+
Fjern
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index ba945e101..6df4e993a 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -420,6 +420,11 @@
and the third is the device model. -->
%1$s auf %2$s %3$s
+
+ Kreditkarten
+
+ Adressen
+
Empfangene Tabs
@@ -453,9 +458,6 @@
Weitere Informationen
-
- Global ausgeschaltet. Öffnen Sie die Einstellungen, um diesen einzuschalten.
-
Telemetrie
@@ -466,6 +468,8 @@
Marketing-DatenGibt Daten darüber, welche Funktionen Sie in %1$s verwenden, an Leanplum weiter, unseren Anbieter für mobiles Marketing.
+
+ Teilt grundlegende Nutzungsdaten mit Adjust, unserem mobilen Marketing-AnbieterStudien
@@ -614,6 +618,8 @@
Private SitzungPrivate Tabs
+
+ Synchronisierte TabsTab hinzufügen
@@ -632,6 +638,8 @@
Alle Tabs teilenKürzlich geschlossene Tabs
+
+ KontoeinstellungenEinstellungen zu Tabs
@@ -803,8 +811,6 @@
%1$s gelöschtOrdner hinzufügen
-
- Lesezeichen erstelltLesezeichen gespeichert
@@ -1004,8 +1010,6 @@
Alle AktionenKürzlich verwendet
-
- Angemeldet als %1$sZum Synchronisieren anmelden
@@ -1481,10 +1485,8 @@
Automatisch ausfüllenZugangsdaten synchronisieren
-
- Ein
-
- Aus
+
+ Zugangsdaten zwischen Geräten synchronisierenVerbindung wiederherstellen
@@ -1585,6 +1587,8 @@
Daten sind verschlüsseltKarten zwischen Geräten synchronisieren
+
+ Kreditkarten synchronisierenKreditkarte hinzufügen
@@ -1592,6 +1596,8 @@
Gespeicherte Karten verwaltenKarte hinzufügen
+
+ Karte bearbeitenKartennummer
@@ -1600,6 +1606,8 @@
Name auf KarteInterner Name für die Karte
+
+ Karte löschenKarte löschen
@@ -1612,6 +1620,9 @@
Gespeicherte Karten
+
+ Bitte geben Sie eine gültige Kreditkartennummer ein
+
Suchmaschine hinzufügen
@@ -1770,23 +1781,19 @@
Abbrechen
+
+ Stellen Sie Links von Websites, E-Mails und Nachrichten so ein, dass sie in Firefox automatisch geöffnet werden.
+
Entfernen
-
- Machen Sie das Beste aus %s.
-
Klicken Sie hier für weitere Details
-
- Sammeln Sie die Dinge, die Ihnen wichtig sind
-
- Gruppieren Sie ähnliche Suchanfragen, Websites und Tabs, um später schnell darauf zugreifen zu können.
-
- Sie sind in einem anderen Firefox-Browser auf diesem Handy als %s angemeldet. Möchten Sie sich mit diesem Konto anmelden?
-
- Sie können diese Website einfach zum Startbildschirm Ihres Handys hinzufügen, um unmittelbaren Zugriff darauf zu haben und sie wie eine App zu nutzen.
+
+ Nach oben navigieren
+
+
+ Schließen
-
+
diff --git a/app/src/main/res/values-dsb/strings.xml b/app/src/main/res/values-dsb/strings.xml
index 70fce6b96..5bc928a5b 100644
--- a/app/src/main/res/values-dsb/strings.xml
+++ b/app/src/main/res/values-dsb/strings.xml
@@ -412,6 +412,11 @@
and the third is the device model. -->
%1$s wót %2$s %3$s
+
+ Kreditowe kórty
+
+ Adrese
+
Dostane rejtariki
@@ -442,9 +447,6 @@
Dalšne informacije
-
- Globalnje znjemóžnjony, źiśo do nastajenjow, aby jen zmóžnił.
-
Telemetrija
@@ -455,6 +457,8 @@
Marketingowe datyŹěli daty wó tom, kótare funkcije w %1$s wužywaśo, z Leanplum, našym póbitowarjom za mobilny marketing.
+
+ Źěli zakładne wužywańske daty z Adjust, našym mobilnym marketingowym póbitowarjomStudije
@@ -623,6 +627,8 @@
Wšykne rejtariki źěliśRowno zacynjone rejtariki
+
+ Kontowe nastajenjaNastajenja rejtarikow
@@ -793,8 +799,6 @@
%1$s jo se wulašowałZarědnik pśidaś
-
- Cytańske znamje jo se napórało.Cytańske znamje jo se składło!
@@ -979,8 +983,6 @@
Wšykne akcijeNjedawno wužyte
-
- Pśizjawjony ako %1$sPla Sync pśizjawiś
@@ -1451,10 +1453,8 @@
Awtomatiski wupołniśPśizjawjenja synchronizěrowaś
-
- Zašaltowany
-
- Wušaltowany
+
+ Pśizjawjenja mjazy rědami synchronizěrowaśZnowego zwězaś
@@ -1556,6 +1556,8 @@
Daty su skoděrowaneKórty pśez rědy synchronizěrowaś
+
+ Kórty synchronizěrowaśKreditowu kórtu pśidaś
@@ -1563,6 +1565,8 @@
Skłaźone kórty zastojaśKórtu pśidaś
+
+ Kórtu wobźěłaśKórtowy numer
@@ -1571,6 +1575,8 @@
Mě na kórśeKórtowe pśimě
+
+ Kórtu wulašowaśKórtu wulašowaś
@@ -1583,6 +1589,9 @@
Skłaźone kórty
+
+ Pšosym zapódajśo płaśiwy numer kreditoweje kórty
+
Pytnicu pśidaś
@@ -1742,23 +1751,19 @@
Pśetergnuś
+
+ Nastajśo wótkaze z websedłow, mejlkow a powěsćow, aby se awtomatiski we Firefox wócynili.
+
Wótwónoźeś
-
- Wuwónoźćo nejlěpše z %s.
-
Klikniśo za dalšne drobnostki
-
- Gromaźćo wěcy, kótarež su wam wažne
-
- Zrědujśo pódobne pytanja, sedła a rejtariki za póznjejšy malsny pśistup.
-
- Sćo se pśizjawił ako %s w drugem wobglědowaku Firefox na toś tom telefonje. Cośo se z toś tym kontom pśizjawiś?
-
- Móžośo startowej wobrazowce swójogo telefona toś to websedło lažko pśidaś, aby direktny pśistup měł a malsnjej z dožywjenim nałoženja pśeglědował.
+
+ Górjej
+
+
+ Zacyniś
-
+
diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml
index dbb30218e..c7411e2ef 100644
--- a/app/src/main/res/values-el/strings.xml
+++ b/app/src/main/res/values-el/strings.xml
@@ -333,7 +333,7 @@
Εργαλεία προγραμματιστή
- Απομακρυσμένος εντοπισμός σφαλμάτων μέσω USB
+ Απομακρυσμένος έλεγχος σφαλμάτων μέσω USBΕμφάνιση μηχανών αναζήτησης
@@ -420,6 +420,11 @@
and the third is the device model. -->
%1$s στο %2$s %3$s
+
+ Πιστωτικές κάρτες
+
+ Διευθύνσεις
+
Ληφθείσες καρτέλες
@@ -450,9 +455,6 @@
Μάθετε περισσότερα
-
- Γενικά ανενεργή, μεταβείτε στις Ρυθμίσεις για ενεργοποίηση.
-
Τηλεμετρία
@@ -464,6 +466,8 @@
Δεδομένα μάρκετινγκΑποστέλλει δεδομένα σχετικά με τις λειτουργίες που χρησιμοποιείτε στο %1$s με το Leanplum, την εταιρεία μάρκετινγκ για κινητές συσκευές.
+
+ Κοινή χρήση βασικών δεδομένων χρήσης με το Adjust, την υπηρεσία μας για μάρκετινγκ κινητώνΜελέτες
@@ -614,6 +618,8 @@
Ιδιωτική συνεδρίαΙδιωτικές καρτέλες
+
+ Συγχρονισμένες καρτέλεςΠροσθήκη καρτέλας
@@ -632,6 +638,8 @@
Κοινή χρήση όλων των καρτελώνΠρόσφατα κλεισμένες καρτέλες
+
+ Ρυθμίσεις λογαριασμούΡυθμίσεις καρτελών
@@ -802,8 +810,6 @@
Ο φάκελος "%1$s" διαγράφηκεΠροσθήκη φακέλου
-
- Ο σελιδοδείκτης δημιουργήθηκε.Ο σελιδοδείκτης αποθηκεύτηκε!
@@ -987,8 +993,6 @@
Όλες οι ενέργειεςΠρόσφατη χρήση
-
- Έγινε σύνδεση ως %1$sΣύνδεση για συγχρονισμό
@@ -1469,10 +1473,8 @@
Αυτόματη συμπλήρωσηΣυγχρονισμός συνδέσεων
-
- Ενεργό
-
- Ανενεργό
+
+ Συγχρονισμός συνδέσεων με άλλες συσκευέςΕπανασύνδεση
@@ -1572,6 +1574,8 @@
Τα δεδομένα είναι κρυπτογραφημέναΣυγχρονισμός καρτών μεταξύ των συσκευών
+
+ Συγχρονισμός καρτώνΠροσθήκη πιστωτικής κάρτας
@@ -1579,6 +1583,8 @@
Διαχείριση αποθηκευμένων καρτώνΠροσθήκη κάρτας
+
+ Επεξεργασία κάρταςΑριθμός κάρτας
@@ -1587,6 +1593,8 @@
Όνομα στην κάρταΨευδώνυμο κάρτας
+
+ Διαγραφή κάρταςΔιαγραφή κάρτας
@@ -1599,6 +1607,9 @@
Αποθηκευμένες κάρτες
+
+ Παρακαλώ εισαγάγετε έναν έγκυρο αριθμό πιστωτικής κάρτας
+
Προσθήκη μηχανής αναζήτησης
@@ -1761,23 +1772,19 @@
Ακύρωση
+
+ Ρύθμιση συνδέσμων ιστοσελίδων, emails και μηνυμάτων ώστε να ανοίγουν στο Firefox.
+
Αφαίρεση
-
- Αξιοποιήστε στο έπακρο το %s.
-
Πατήστε για περισσότερες λεπτομέρειες
-
- Συλλέξτε όλα όσα έχουν σημασία για εσάς
-
- Ομαδοποιήστε παρόμοιες αναζητήσεις, σελίδες και καρτέλες για γρήγορη πρόσβαση αργότερα.
-
- Έχετε συνδεθεί ως %s σε άλλο πρόγραμμα περιήγησης Firefox σε αυτό το τηλέφωνο. Θέλετε να συνδεθείτε με αυτό τον λογαριασμό;
-
- Μπορείτε εύκολα να προσθέσετε αυτή την ιστοσελίδα στην αρχική οθόνη για άμεση πρόσβαση και ταχύτερη περιήγηση, σαν να ήταν εφαρμογή.
+
+ Πλοήγηση προς τα πάνω
+
+
+ Κλείσιμο
-
+
diff --git a/app/src/main/res/values-en-rCA/strings.xml b/app/src/main/res/values-en-rCA/strings.xml
index 8418570c5..2b61a8116 100644
--- a/app/src/main/res/values-en-rCA/strings.xml
+++ b/app/src/main/res/values-en-rCA/strings.xml
@@ -411,6 +411,11 @@
and the third is the device model. -->
%1$s on %2$s %3$s
+
+ Credit cards
+
+ Addresses
+
Received tabs
@@ -441,9 +446,6 @@
Learn more
-
- Turned off globally, go to Settings to turn it on.
-
Telemetry
@@ -454,6 +456,8 @@
Marketing dataShares data about what features you use in %1$s with Leanplum, our mobile marketing vendor.
+
+ Shares basic usage data with Adjust, our mobile marketing vendorStudies
@@ -600,6 +604,8 @@
Private sessionPrivate tabs
+
+ Synced tabsAdd tab
@@ -618,6 +624,8 @@
Share all tabsRecently closed tabs
+
+ Account settingsTab settings
@@ -785,8 +793,6 @@
Deleted %1$sAdd folder
-
- Bookmark created.Bookmark saved!
@@ -970,8 +976,6 @@
All actionsRecently used
-
- Signed in as %1$sSign in to sync
@@ -1437,10 +1441,8 @@
AutofillSync logins
-
- On
-
- Off
+
+ Sync logins across devicesReconnect
@@ -1540,6 +1542,8 @@
Data is encryptedSync cards across devices
+
+ Sync cardsAdd credit card
@@ -1547,6 +1551,8 @@
Manage saved cardsAdd card
+
+ Edit cardCard Number
@@ -1555,6 +1561,8 @@
Name on CardCard Nickname
+
+ Delete cardDelete card
@@ -1567,6 +1575,9 @@
Saved cards
+
+ Please enter a valid credit card number
+
Add search engine
@@ -1725,23 +1736,19 @@
Cancel
+
+ Set links from websites, emails, and messages to open automatically in Firefox.
+
Remove
-
- Get the most out of %s.
-
Click for more details
-
- Collect the things that matter to you
-
- Group together similar searches, sites, and tabs for quick access later.
-
- You are signed in as %s on another Firefox browser on this phone. Would you like to sign in with this account?
-
- You can easily add this website to your phone’s Home screen to have instant access and browse faster with an app-like experience.
+
+ Navigate up
+
+
+ Close
-
+
diff --git a/app/src/main/res/values-en-rGB/strings.xml b/app/src/main/res/values-en-rGB/strings.xml
index 71f62d245..ae6b39c13 100644
--- a/app/src/main/res/values-en-rGB/strings.xml
+++ b/app/src/main/res/values-en-rGB/strings.xml
@@ -411,6 +411,11 @@
and the third is the device model. -->
%1$s on %2$s %3$s
+
+ Credit cards
+
+ Addresses
+
Received tabs
@@ -441,9 +446,6 @@
Learn more
-
- Turned off globally, go to Settings to turn it on.
-
Telemetry
@@ -454,6 +456,8 @@
Marketing dataShares data about what features you use in %1$s with Leanplum, our mobile marketing vendor.
+
+ Shares basic usage data with Adjust, our mobile marketing vendorStudies
@@ -601,6 +605,8 @@
Private sessionPrivate tabs
+
+ Synchronised tabsAdd tab
@@ -619,6 +625,8 @@
Share all tabsRecently closed tabs
+
+ Account settingsTab settings
@@ -787,8 +795,6 @@
Deleted %1$sAdd folder
-
- Bookmark created.Bookmark saved!
@@ -973,8 +979,6 @@
All actionsRecently used
-
- Signed in as %1$sSign in to synchronise
@@ -1440,10 +1444,8 @@
AutofillSynchronise logins
-
- On
-
- Off
+
+ Synchronise logins across devicesReconnect
@@ -1543,6 +1545,8 @@
Data is encryptedSynchronise cards across devices
+
+ Synchronise cardsAdd credit card
@@ -1550,6 +1554,8 @@
Manage saved cardsAdd card
+
+ Edit cardCard Number
@@ -1558,6 +1564,8 @@
Name on CardCard Nickname
+
+ Delete cardDelete card
@@ -1570,6 +1578,9 @@
Saved cards
+
+ Please enter a valid credit card number
+
Add search engine
@@ -1728,23 +1739,19 @@
Cancel
+
+ Set links from web sites, emails, and messages to open automatically in Firefox.
+
Remove
-
- Get the most out of %s.
-
Click for more details
-
- Collect the things that matter to you
-
- Group together similar searches, sites, and tabs for quick access later.
-
- You are signed in as %s on another Firefox browser on this phone. Would you like to sign in with this account?
-
- You can easily add this web site to your phone’s Home screen to have instant access and browse faster with an app-like experience.
+
+ Navigate up
+
+
+ Close
-
+
diff --git a/app/src/main/res/values-eo/strings.xml b/app/src/main/res/values-eo/strings.xml
index f0bde9b86..a7afea4e8 100644
--- a/app/src/main/res/values-eo/strings.xml
+++ b/app/src/main/res/values-eo/strings.xml
@@ -19,10 +19,6 @@
Viaj privataj langetoj aperos ĉi tie.
-
- Baidu
-
- JD1 malfermita langeto. Tuŝetu por ŝanĝi langeton.
@@ -181,6 +177,11 @@
Personecigi legilan vidon
+
+ Aldoni
+
+ Modifi
+
Konekto neebla. Nerekonita skemo de URL.
@@ -410,6 +411,11 @@
and the third is the device model. -->
%1$s en %2$s %3$s
+
+ Kreditkartoj
+
+ Adresoj
+
Ricevitaj langetoj
@@ -441,9 +447,6 @@
Pli da informo
-
- Malŝaltita ĝenerale, iru al Agordoj por ŝalti ĝin.
-
Telemezuro
@@ -454,6 +457,8 @@
Merkatikaj datumojDivido de datumoj pri la trajtoj, kiujn vi uzas en %1$s kun Leanplum, nia poŝaparata merkatika provizanto.
+
+ Divido de informoj kun Adjust, nia provizanto de marketiko poŝaparataStudoj
@@ -603,12 +608,16 @@
Privata seancoPrivataj langetoj
+
+ Spegulitaj langetojAldoni langetonAldoni privatan langetonPrivata
+
+ SpeguladoMalfermi langetojn
@@ -619,6 +628,8 @@
Dividi ĉiujn langetojnĴuse fermitaj langetoj
+
+ Agordoj de kontoAgordoj de langetoj
@@ -791,8 +802,6 @@
%1$s forigitaAldoni dosierujon
-
- Legosigno kreita.Legosigno konservita!
@@ -980,6 +989,8 @@
Ĉiuj agojĴuse uzitaj
+
+ Komenci seancon por speguliKomenci seancon en Speguli
@@ -1174,9 +1185,6 @@
Bonvenon al %s!Ĉu vi jam havas konton?
-
- Malkovru %sVidi la novaĵojnTrovu respondojn ĉi tie
-
- Komenci speguli legosignojn, pasvortojn kaj aliajn aferojn per via konto de Firefox.
+
+ Speguli Firefox inter aparatoj
- Pli da informo
+ Porti legosignojn, historion kaj pasvortojn al Firefox en tiu ĉi aparato.
@@ -1196,8 +1204,8 @@
Jes, komenci seanconKomenco de seanco…
-
- Komenci seancon en Firefox
+
+ RegistriĝiNe komenci seancon
@@ -1205,26 +1213,23 @@
Malsukcesa komenco de seanco
- Aŭtomata privateco
-
- La agordoj privatecaj kaj sekurecaj blokas spurilojn, malicajn programojn kaj kompaniojn kiuj sekvas vin.
+ Privateco ĉiam aktiva
+
+ Firefox aŭtomate evitas ke entreprenoj sekrete sekvu vin tra la teksaĵo.Norma
- Malpli da spuriloj blokitaj. Paĝoj ŝargiĝos normale.
+ Ekvilibrita por privateco kaj efikeco. Paĝoj ŝargiĝos normale.Rigora (rekomendita)Rigora
- Pli da spuriloj, reklamoj kaj ŝprucaĵoj blokitaj. Paĝoj ŝargiĝos pli rapide, sed kelkaj aferoj povus ne bone funkcii.
-
- Preni pozicion
+ Pli da spuriloj blokitaj signifas ke paĝoj ŝargiĝos pli rapide, sed kelkaj misfunkcioj povus okazi.
+
+ Elektu lokon por la ilaro
- Provu la unumanan retumon per la malsupra ilaro, aŭ movu ĝin al la supro.
+ Metu la ilaron ĉemanen. Lasu ĝin malsupre aŭ movu ĝin supren.Retumi privateVia privateco
+ The first parameter is the name of the app (e.g. Firefox Preview) Substitute %s for long browser name. -->
Ni kreis %s por doni al vi la eblon plene regi kion vi dividas en la reto kaj kion vi dividas kun ni.Legu nian rimarkon pri privateco
@@ -1253,7 +1258,7 @@
Elektu vian etoson
- Ŝparu iom da baterio kaj protektu viajn okulojn per malhela reĝimo.
+ Ŝparu iom da baterio kaj protektu viajn okulojn per malhela reĝimo.Aŭtomate
@@ -1309,13 +1314,13 @@
Norma
- Malpli da spuriloj blokitaj. Paĝoj ŝargiĝos normale.
+ Ekvilibrita por privateco kaj efikeco. Paĝoj ŝargiĝos normale.Aferoj blokitaj de la norma protekto kontraŭ spuradoRigora
- Pli da spuriloj, reklamoj kaj ŝprucaĵoj blokitaj. Paĝoj ŝargiĝos pli rapide, sed kelkaj aferoj povus ne bone funkcii.
+ Pli da spuriloj blokitaj signifas ke paĝoj ŝargiĝos pli rapide, sed kelkaj misfunkcioj povus okazi.Aferoj blokitaj de la rigora protekto kontraŭ spurado
@@ -1457,10 +1462,8 @@
Aŭtomata plenigoSpeguli legitimilojn
-
- Ŝaltita
-
- Malŝaltita
+
+ Speguli legitimilojn inter aparatojRekonekti
@@ -1553,6 +1556,50 @@
Ordigi menuon de legitimiloj
+
+
+ Kreditkartoj
+
+ Konservi kaj aŭtomate plenigi kreditkartojn
+
+ La datumoj estas ĉifritaj
+
+ Speguli kreditkartojn inter aparatoj
+
+ Speguli kreditkartojn
+
+ Aldoni kreditkarton
+
+ Administri konservitajn kreditkartojn
+
+ Aldoni kreditkarton
+
+ Modifi kreditkarton
+
+
+ Numero de kreditkarto
+
+ Dato de senvalidiĝo
+
+ Nomo sur kreditkarto
+
+ Kromnomo por kreditkarto
+
+ Forigi kreditkarton
+
+ Forigi kreditkarton
+
+ Konservi
+
+ Konservi
+
+ Nuligi
+
+ Konservitaj kreditkartoj
+
+
+ Bonvolu tajpi validan kreditkaran numeron
+
Aldoni serĉilon
@@ -1713,23 +1760,19 @@
Nuligi
+
+ Aŭtomate malfermi ligilon en retejoj, retpoŝtoj kaj mesaĝoj per Firefox.
+
Forigi
-
- Eltiru la maksimumon el %s.
-
Alklaku por havi pli da informo
-
- Kolekti la aferojn kiu gravas por vi
-
- Grupigi similajn serĉojn, retejojn kaj langetojn por pli rapida posta aliro.
-
- Vi komencis seancon kiel %s en alia retumilo Firefox en tiu ĉi poŝaparato. Ĉu vi ŝatus komenci seancon per tiu konto?
-
- Vi povas facile aldoni tiun ĉi retejon al la hejmpaĝo de via telefono, por havi tujan aliron kaj retumi pli rapide per kvazaŭprograma sperto.
+
+ Iri supren
+
+
+ Fermi
-
+
diff --git a/app/src/main/res/values-es-rAR/strings.xml b/app/src/main/res/values-es-rAR/strings.xml
index c0a860f64..f212f8ea5 100644
--- a/app/src/main/res/values-es-rAR/strings.xml
+++ b/app/src/main/res/values-es-rAR/strings.xml
@@ -18,10 +18,10 @@
Tus pestañas abiertas se mostrarán aquí.
- Sus pestañas privadas se van a mostrar aquí.
+ Tus pestañas privadas se mostrarán aquí.
- 1 abrir pestaña. Tocá para cambiar de pestaña.
+ 1 pestaña abierta. Tocá para cambiar de pestaña.%1$s pestañas abiertas. Tocá para cambiar de pestaña.
@@ -42,9 +42,9 @@
Se eliminó la selección %1$s
- Salió del modo de selección múltiple
+ Saliste del modo de selección múltiple
- Ingresó al modo de selección múltiple, seleccione pestañas para guardar en una colección
+ Ingresaste al modo de selección múltiple, seleccioná pestañas para guardar en una colecciónSeleccionadas
@@ -164,7 +164,7 @@
Abrir en %1$s
- PATROCINADO POR %1$s
+ DESARROLLADO POR %1$s
@@ -420,6 +420,11 @@
and the third is the device model. -->
%1$s en %2$s %3$s
+
+ Tarjetas de crédito
+
+ Direcciones
+
Pestañas recibidas
@@ -452,9 +457,6 @@
Conocer más
-
- Deshabilitado globalmente, ir a Ajustes para habilitarlo.
-
Telemetría
@@ -465,6 +467,8 @@
Datos de marketingComparte datos acerca de las funciones que usás en %1$s con Leanplum, nuestro proveedor de marketing para móviles.
+
+ Comparte datos de uso básicos con Adjust, nuestro proveedor de marketing móvilEstudios
@@ -617,6 +621,8 @@
Sesión privadaPestañas privadas
+
+ Pestañas sincronizadasAgregar pestaña
@@ -626,7 +632,7 @@
Sincronizar
- Abrir pestañas
+ Pestañas abiertasGuardar en colección
@@ -635,6 +641,8 @@
Compartir todas las pestañasPestañas recientemente cerradas
+
+ Configuración de la cuentaConfiguración de pestañas
@@ -807,8 +815,6 @@
Se eliminó %1$sAgregar carpeta
-
- Marcador creado.¡Marcador guardado!
@@ -997,8 +1003,6 @@
Todas las accionesUsado recientemente
-
- Iniciaste sesión como %1$sIniciá sesión para sincronizar
@@ -1469,10 +1473,8 @@
AutocompletarSincronizar inicios de sesión
-
- Habilitado
-
- Deshabilitado
+
+ Sincronizar inicios de sesión entre dispositivosReconectar
@@ -1572,6 +1574,8 @@
Los datos están cifradosSincronizar tarjetas entre dispositivos
+
+ Sincronizar tarjetasAgregar tarjeta de crédito
@@ -1579,6 +1583,8 @@
Administrar tarjetas guardadasAgregar tarjeta
+
+ Editar tarjetaNúmero de tarjeta
@@ -1587,6 +1593,8 @@
Nombre en la tarjetaApodo de la tarjeta
+
+ Borrar tarjetaEliminar tarjeta
@@ -1599,6 +1607,9 @@
Tarjetas guardadas
+
+ Ingresá un número de tarjeta de crédito válido
+
Agregar buscador
@@ -1760,23 +1771,19 @@
Cancelar
+
+ Configurar enlaces de sitios web, correos electrónicos y mensajes para que se abran automáticamente en Firefox.
+
Eliminar
-
- Aprovechá %s al máximo.
-
Hacer clic aquí para más detalles
-
- Recolectá lo que te importa
-
- Agrupar búsquedas, sitios y pestañas similares para acceder rápidamente más tarde.
-
- Iniciaste sesión como %s en otro navegador Firefox en este teléfono. ¿Querés iniciar sesión con esta cuenta?
-
- Podés agregar fácilmente este sitio web a la pantalla de inicio de tu teléfono para tener acceso instantáneo y navegar más rápido con una experiencia similar a la de una aplicación.
+
+ Navegar hacia arriba
+
+
+ Cerrar
-
+
diff --git a/app/src/main/res/values-es-rCL/strings.xml b/app/src/main/res/values-es-rCL/strings.xml
index 7e520ab3c..e95a4a3cf 100644
--- a/app/src/main/res/values-es-rCL/strings.xml
+++ b/app/src/main/res/values-es-rCL/strings.xml
@@ -48,7 +48,7 @@
Seleccionadas
- %1$s es producido por @fork-maintainers.
+ %1$s es producido por Mozilla.
@@ -411,6 +411,11 @@
and the third is the device model. -->
%1$s en %2$s %3$s
+
+ Tarjetas de crédito
+
+ Direcciones
+
Pestañas recibidas
@@ -442,9 +447,6 @@
Aprender más
-
- Desactivado de forma global, ve a Ajustes para activarla.
-
Telemetría
@@ -455,6 +457,8 @@
Datos de marketingComparte datos acerca de las funcionalidades que usas en %1$s con Leanplum, nuestro proveedor de marketing para móviles.
+
+ Comparte datos de uso básico con Adjust, nuestro proveedor de marketing móvilEstudios
@@ -602,12 +606,16 @@
Sesión privadaPestañas privadas
+
+ Pestañas sincronizadasAñadir pestañaAñadir pestaña privadaPrivada
+
+ SincronizarPestañas abiertas
@@ -618,6 +626,8 @@
Compartir todas las pestañasPestañas cerradas recientemente
+
+ Ajustes de la cuentaAjustes de pestañas
@@ -787,8 +797,6 @@
%1$s eliminadoAñadir carpeta
-
- Marcador creado.¡Marcador guardado!
@@ -972,8 +980,6 @@
Todas las accionesPor uso reciente
-
- Conectado como %1$sConectarse para sincronizar
@@ -1443,10 +1449,8 @@
AutollenadoSincronizar credenciales
-
- Sí
-
- No
+
+ Sincronizar credenciales en todos los dispositivosReconectar
@@ -1548,11 +1552,17 @@
Los datos están encriptadosSincronizar tarjetas entre dispositivos
+
+ Sincronizar tarjetasAñadir tarjeta de crédito
+
+ Gestionar tarjetas guardadasAñadir tarjeta
+
+ Editar tarjetaNúmero de tarjeta
@@ -1561,6 +1571,8 @@
Nombre en la tarjetaApodo de la tarjeta
+
+ Eliminar tarjetaEliminar tarjeta
@@ -1570,6 +1582,12 @@
Cancelar
+
+ Tarjetas guardadas
+
+
+ Por favor, ingresa un número de tarjeta de crédito válido
+
Añadir motor de búsqueda
@@ -1728,23 +1746,19 @@
Cancelar
+
+ Configura enlaces de sitios web, correos electrónicos y mensajes para que se abran automáticamente en Firefox.
+
Eliminar
-
- Saca el máximo provecho a %s.
-
Clic para más detalles
-
- Recolecta lo que te importa
-
- Agrupa búsquedas, sitios y pestañas similares para acceder a ellos rápidamente.
-
- Te has conectado como %s en otro navegador Firefox en este teléfono. ¿Quieres conectarte con esta cuenta?
-
- Puedes añadir fácilmente este sitio web a tu pantalla de inicio de tu teléfono para tener acceso instantáneo y navegar rápidamente, consiguiendo una experiencia similar a la de una aplicación real.
+
+ Navegar hacia arriba
+
+
+ Cerrar
-
+
diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml
index f32b5c700..3932e3c3f 100644
--- a/app/src/main/res/values-es-rES/strings.xml
+++ b/app/src/main/res/values-es-rES/strings.xml
@@ -419,6 +419,11 @@
and the third is the device model. -->
%1$s en %2$s %3$s
+
+ Tarjetas de crédito
+
+ Direcciones
+
Pestañas recibidas
@@ -454,9 +459,6 @@
Saber más
-
- Se desactivó en general, ve a Ajustes para activarlo.
-
Telemetry
@@ -467,6 +469,8 @@
Datos de marketingComparte datos acerca de las funcionalidades que usas en %1$s con Leanplum, nuestro proveedor de marketing para móviles.
+
+ Comparte datos básicos de uso con Adjust, nuestro proveedor de marketing móvilEstudios
@@ -614,12 +618,16 @@
Sesión privadaPestañas privadas
+
+ Pestañas sincronizadasAgregar pestañaAñadir pestaña privadaPrivada
+
+ SincronizarPestañas abiertas
@@ -630,6 +638,8 @@
Compartir todas las pestañasPestañas cerradas recientemente
+
+ Ajustes de la cuentaAjustes de pestañas
@@ -801,8 +811,6 @@
Se ha eliminado %1$sAgregar carpeta
-
- Se ha creado el marcador.¡Marcador guardado!
@@ -1000,6 +1008,8 @@
Todas las accionesUsado recientemente
+
+ Inicia sesión para sincronizarIniciar sesión en Sync
@@ -1477,10 +1487,8 @@
Rellenar automáticamenteInicios de sesión sincronizados
-
- Activado
-
- Desactivado
+
+ Sincronizar inicios de sesión entre dispositivosVolver a conectar
@@ -1583,11 +1591,17 @@
Sincronizar tarjetas entre dispositivos
+
+ Sincronizar tarjetasAñadir tarjeta de crédito
+
+ Administrar tarjetas guardadasAñadir tarjeta
+
+ Editar tarjetaNúmero de tarjeta
@@ -1596,6 +1610,8 @@
Nombre en la tarjetaDescripción de la tarjeta
+
+ Eliminar tarjetaEliminar tarjeta
@@ -1605,6 +1621,12 @@
Cancelar
+
+ Tarjetas guardadas
+
+
+ Por favor, escriba un número válido de tarjeta de crédito
+
Añadir buscador
@@ -1766,23 +1788,19 @@
Cancelar
+
+ Configura enlaces de sitios web, correos electrónicos y mensajes para que se abran automáticamente en Firefox.
+
Eliminar
-
- Sácale el mejor provecho a %s.
-
Clic para más detalles
-
- Colecciona las cosas que te importan
-
- Agrupa búsquedas, sitios y pestañas similares para un acceso rápido más tarde.
-
- Ya has iniciado sesión como %s en otro navegador Firefox de este teléfono. ¿Quieres iniciar sesión con esta cuenta?
-
- Puedes añadir fácilmente este sitio web a tu página de inicio para tener acceso instantáneo y navegar rápidamente como si fuera una aplicación.
+
+ Ir arriba
+
+
+ Cerrar
-
+
diff --git a/app/src/main/res/values-es-rMX/strings.xml b/app/src/main/res/values-es-rMX/strings.xml
index 8e7d02b7e..e7e21b488 100644
--- a/app/src/main/res/values-es-rMX/strings.xml
+++ b/app/src/main/res/values-es-rMX/strings.xml
@@ -19,10 +19,6 @@
Tus pestañas privadas aparecerán aquí.
-
- Baidu
-
- JD1 pestaña abierta. Tocar para cambiar de pestaña.
@@ -127,6 +123,8 @@
Editar marcadorComplementos
+
+ ExtensionesNo hay complementos aquí
@@ -177,6 +175,13 @@
Apariencia
+
+ Personalizar vista de lectura
+
+ Agregar
+
+ Editar
+
No se puede conectar. Esquema de URL irreconocible.
@@ -214,6 +219,11 @@
Saber más
+
+ Buscar %s
+
+ Buscar directamente desde la barra de direcciones
+
Abrir una nueva pestaña de Firefox
@@ -402,6 +412,11 @@
and the third is the device model. -->
%1$s en %2$s %3$s
+
+ Tarjetas de crédito
+
+ Direcciones
+
Pestañas recibidas
@@ -432,9 +447,6 @@
Más información
-
- Se desactivó en general, ve a Ajustes para activarlo.
-
Telemetría
@@ -445,6 +457,8 @@
Datos de marketingComparte datos acerca de las funcionalidades que usas en %1$s con Leanplum, nuestro proveedor de marketing para móviles.
+
+ Comparte datos de uso básicos con Adjust, nuestro proveedor de marketing móvilEstudios
@@ -526,6 +540,10 @@
Otros marcadoresHistorial
+
+ Nueva pestaña
+
+ Buscar en la páginaPestañas sincronizadas
@@ -588,12 +606,16 @@
Sesión privadaPestañas privadas
+
+ Pestañas sincronizadasAgregar pestañaAgregar pestaña privadaPrivada
+
+ SincronizarPestañas abiertas
@@ -604,6 +626,8 @@
Compartir todas las pestañasPestañas recientemente cerradas
+
+ Ajustes de la cuentaAjustes de pestañas
@@ -770,8 +794,6 @@
Se eliminó %1$sAgregar carpeta
-
- Marcador creado.¡Marcador guardado!
@@ -875,16 +897,22 @@
ActivadaDesactivada
-
+
Permitir audio y video
+
+ Permitir audio y videoBloquear audio y video solo con datos móvilesEl audio y el video se reproducirán con Wi-Fi
-
+
Bloquear solo audio
-
+
+ Bloquear solo audio
+
Bloquear audio y video
+
+ Bloquear audio y videoActivado
@@ -950,6 +978,8 @@
Todas las accionesUsado recientemente
+
+ Iniciar sesión para sincronizarIniciar sesión en Sync
@@ -1081,6 +1111,8 @@
Libera espacio de almacenamientoPermisos del sitio
+
+ DescargasEliminar datos de navegación
@@ -1140,9 +1172,6 @@
¡Te damos la bienvenida a %s!¿Ya tienes una cuenta?
-
- Descubre %sVer las novedadesObtén respuestas aquí
-
- Empieza a sincronizar marcadores, contraseñas y más con tu cuenta de Firefox.
+
+ Sincronizar Firefox entre dispositivos
- Saber más
+ Traer marcadores, historial y contraseñas al Firefox de este dispositivo.
@@ -1162,8 +1191,8 @@
Sí, iniciar sesiónIniciando sesión…
-
- Iniciar sesión en Firefox
+
+ RegistrarseMantenerme desconectado
@@ -1171,27 +1200,24 @@
Error al iniciar sesión
- Privacidad automática
-
- La configuración de privacidad y seguridad bloquea los rastreadores, el malware y las compañías que te siguen.
+ Privacidad siempre activada
+
+ Firefox automáticamente prohibe que las compañías te sigan en secreto en la web.Estándar (predeterminado)
- Bloquea menos rastreadores. Las páginas se cargarán normalmente.
+ Equilibrado para privacidad y rendimiento. Las páginas se cargarán normalmente.Estricta (recomendada)Estricto
- Bloquear más rastreadores, publicidad y ventanas emergentes. Las páginas cargan más rápido, pero pueden tener problemas de funcionalidad.
-
- Toma una posición
+ Bloquea más rastreadores para que las páginas se carguen más rápido, pero pueden fallar algunas funcionalidades de la página.
+
+ Escoge la posición de la barra de herramientas
- Prueba la navegación con una sola mano con la barra de herramientas inferior o muévela a la parte superior.
+ Coloca la barra de herramientas al alcance de la mano. Mantenla en la parte inferior o muévela hacia arriba.Navegar de forma privadaTu privacidad
- Hemos diseñado %s para darte control sobre lo que compartes
- en línea y lo que compartes con nosotros.
+ The first parameter is the name of the app (e.g. Firefox Preview) Substitute %s for long browser name. -->
+ Hemos diseñado %s para darte el control sobre lo que compartes en línea y lo que compartes con nosotros.Leer nuestro aviso de privacidad
@@ -1220,7 +1245,7 @@
Elige tu tema
- Ahorra un poco de batería y descansa la vista activando el modo oscuro.
+ Ahorra batería y vista con el modo oscuro.Automático
@@ -1275,13 +1300,13 @@
Estándar (predeterminado)
- Bloquea menos rastreadores. Las páginas se cargarán normalmente.
+ Equilibrado para privacidad y rendimiento. Las páginas se cargarán normalmente.Qué es lo que está bloqueado por la protección estándar contra el rastreoEstricto
- Bloquear más rastreadores, publicidad y ventanas emergentes. Las páginas cargan más rápido, pero pueden tener problemas de funcionalidad.
+ Bloquea más rastreadores para que las páginas se carguen más rápido, pero pueden fallar algunas funcionalidades de la página.Qué es lo que está bloqueado por la protección estricta contra el rastreo
@@ -1424,10 +1449,8 @@
AutocompletarSincronizar inicios de sesión
-
- Habilitado
-
- Deshabilitado
+
+ Sincronizar inicios de sesión entre dispositivosReconectar
@@ -1519,6 +1542,49 @@
Ordenar menú de inicios de sesión
+
+
+ Tarjetas de crédito
+
+ Guardar y autocompletar tarjetas
+
+ Los datos están cifrados
+
+
+ Sincronizar tarjetas entre dispositivos
+
+ Sincronizar tarjetas
+
+ Agregar tarjeta de crédito
+
+ Administrar tarjetas guardadas
+
+ Agregar tarjeta
+
+ Editar tarjeta
+
+ Número de tarjeta
+
+ Fecha de caducidad
+
+ Nombre en la tarjeta
+
+ Apodo de la tarjeta
+
+ Eliminar tarjeta
+
+ Eliminar tarjeta
+
+ Guardar
+
+ Guardar
+
+ Cancelar
+
+ Tarjetas guardadas
+
+ Favor de ingresar un número de tarjeta de crédito válido
+
Agregar motor de búsqueda
@@ -1676,23 +1742,19 @@
Cancelar
+
+ Configura enlaces de sitios web, correos electrónicos y mensajes para que se abran automáticamente en Firefox.
+
Eliminar
-
- Saca el máximo provecho de %s.
-
Clic para más detalles
-
- Colecciona las cosas que te importan
-
- Agrupa búsquedas, sitios y pestañas similares para acceder a ellos rápidamente.
-
- Ya has iniciado sesión como %s en otro navegador Firefox de este teléfono. ¿Quieres iniciar sesión con esta cuenta?
-
- Puedes agregar fácilmente este sitio web a la página de inicio de tu teléfono para navegar más rápido con una experiencia similar a una app.
+
+ Navegar hacia arriba
+
+
+ Cerrar
-
+
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
index b44aff9e5..3c3cdaae9 100644
--- a/app/src/main/res/values-es/strings.xml
+++ b/app/src/main/res/values-es/strings.xml
@@ -22,10 +22,6 @@
Tus pestañas privadas se mostrarán aquí.
-
- Baidu
-
- JD1 pestaña abierta. Tocar para cambiar de pestaña.
@@ -189,6 +185,11 @@
Personalizar vista de lectura
+
+ Agregar
+
+ Editar
+
No se puede conectar. Esquema de URL irreconocible.
@@ -420,6 +421,11 @@
and the third is the device model. -->
%1$s en %2$s %3$s
+
+ Tarjetas de crédito
+
+ Direcciones
+
Pestañas recibidas
@@ -453,9 +459,6 @@
Más información
-
- Desactivado de forma general, ve a Ajustes para activarlo.
-
Telemetry
@@ -466,6 +469,8 @@
Datos de marketingComparte datos acerca de las funcionalidades que usas en %1$s con Leanplum, nuestro proveedor de marketing para móviles.
+
+ Comparte datos de uso básicos con Adjust, nuestro proveedor de marketing en dispositivos móvilesEstudios
@@ -613,12 +618,16 @@
Sesión privadaPestañas privadas
+
+ Pestañas sincronizadasAñadir pestañaAgregar pestaña privadaPrivada
+
+ SincronizarAbrir pestañas
@@ -629,6 +638,8 @@
Compartir todas las pestañasPestañas cerradas recientemente
+
+ Configuración de la cuentaAjustes de pestañas
@@ -801,8 +812,6 @@
Se eliminó %1$sAgregar carpeta
-
- Se creó el marcador.¡Marcador guardado!
@@ -1000,6 +1009,8 @@
Todas las accionesUsado recientemente
+
+ Iniciar sesión para sincronizarIniciar sesión en Sync
@@ -1199,9 +1210,6 @@
¡Te damos la bienvenida a %s!¿Ya tienes una cuenta?
-
- Descubre %sVer las novedadesObtén respuestas aquí
-
- Empieza a sincronizar marcadores, contraseñas y más con tu cuenta de Firefox.
+
+ Sincronizar Firefox entre dispositivos
- Aprender más
+ Traer marcadores, historial y contraseñas a Firefox en este dispositivo.
@@ -1221,8 +1229,8 @@
Sí, iniciar sesiónIniciando sesión…
-
- Inicia sesión en Firefox
+
+ RegistrarseMantenerme desconectado
@@ -1231,26 +1239,23 @@
Error al iniciar sesión
- Privacidad automática
-
- La configuración de privacidad y seguridad bloquea los rastreadores, los programas malignos y las compañías que te siguen.
+ Privacidad siempre activada
+
+ Firefox bloquea automáticamente a las compañías que te siguen en secreto por la web.Estándar (predeterminado)
- Bloquea menos rastreadores. Las páginas se van a cargar normalmente.
+ Equilibrado para privacidad y rendimiento. Las páginas se cargarán normalmente.Estricta (recomendada)Estricto
- Bloquea más rastreadores, anuncios y ventanas emergentes. Las páginas se cargan más rápido, pero podés perder cierta funcionalidad.
-
- Toma una posición
+ Bloquea más rastreadores para que las páginas se carguen más rápido, pero pueden fallar algunas funcionalidades de la página.
+
+ Escoge la posición de la barra de herramientas
- Prueba la navegación con una sola mano con la barra de herramientas inferior o muévela a la parte superior.
+ Pon la barra de herramientas a tu alcance. Mantenla abajo, o muévela hacia arriba.Navegar de forma privada
@@ -1264,7 +1269,7 @@
Tu privacidad
+ The first parameter is the name of the app (e.g. Firefox Preview) Substitute %s for long browser name. -->
Hemos diseñado %s para darte el control sobre lo que compartes en línea y lo que compartes con nosotros.Lee nuestro aviso de privacidad
@@ -1279,7 +1284,7 @@
Elige tu tema
- Ahorra un poco de batería y descansa la vista activando el modo oscuro.
+ Ahorra un poco de batería y descansa la vista con el modo oscuro.Automático
@@ -1334,13 +1339,13 @@
Estándar (predeterminado)
- Bloquea menos rastreadores. Las páginas se van a cargar normalmente.
+ Equilibrado para privacidad y rendimiento. Las páginas se cargarán normalmente.Lo que bloquea la protección de seguimiento estándarEstricta
- Bloquea más rastreadores, anuncios y ventanas emergentes. Las páginas se cargan más rápido, pero podés perder cierta funcionalidad.
+ Bloquea más rastreadores para que las páginas se carguen más rápido, pero pueden fallar algunas funcionalidades de la página.Lo que bloquea la estricta protección de rastreo
@@ -1483,10 +1488,8 @@
AutocompletarSincronizar inicios de sesión
-
- Activado
-
- Desactivado
+
+ Sincronizar inicios de sesión entre dispositivosReconectar
@@ -1579,6 +1582,48 @@
Ordenar menú de inicios de sesión
+
+
+ Tarjetas de crédito
+
+ Guardar y autocompletar tarjetas
+
+ Los datos están cifrados
+
+ Sincronizar tarjetas entre dispositivos
+
+ Sincronizar tarjetas
+
+ Añadir tarjeta de crédito
+
+ Administrar tarjetas guardadas
+
+ Añadir tarjeta
+
+ Editar tarjeta
+
+ Número de tarjeta
+
+ Fecha de caducidad
+
+ Nombre en la tarjeta
+
+ Descripción de la tarjeta
+
+ Eliminar tarjeta
+
+ Eliminar tarjeta
+
+ Guardar
+
+ Guardar
+
+ Cancelar
+
+ Tarjetas guardadas
+
+ Por favor, ingresa un número de tarjeta de crédito válido
+
Agregar motor de búsqueda
@@ -1739,23 +1784,19 @@
Cancelar
+
+ Configura enlaces de sitios web, correos electrónicos y mensajes para abrir automáticamente en Firefox.
+
Eliminar
-
- Sácale el máximo provecho a %s.
-
Clic para más detalles
-
- Colecciona las cosas que te importan
-
- Agrupar búsquedas, sitios y pestañas similares para acceder rápidamente más tarde.
-
- Has iniciado sesión como %s en otro navegador Firefox en este teléfono. ¿Deseas iniciar sesión con esta cuenta?
-
- Puedes agregar fácilmente este sitio web a tu página de inicio para tener acceso instantáneo y navegar rápidamente como si fuera una aplicación.
+
+ Navegar hacia arriba
+
+
+ Cerrar
-
+
diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml
index 0c88ba6aa..08c8d2e64 100644
--- a/app/src/main/res/values-eu/strings.xml
+++ b/app/src/main/res/values-eu/strings.xml
@@ -20,10 +20,6 @@
Zure fitxa pribatuak hemen erakutsiko dira.
-
- Baidu
-
- JDIrekitako fitxa bat. Sakatu fitxaz aldatzeko.
@@ -54,7 +50,7 @@
Hautatuta
- %1$s @fork-maintainersk egina da.
+ %1$s Mozillak egina da.
@@ -187,6 +183,11 @@
Pertsonalizatu irakurtzeko ikuspegia
+
+ Gehitu
+
+ Editatu
+
Ezin da konektatu. URL eskema ezagutezina.
@@ -451,9 +452,6 @@
Argibide gehiago
-
- Modu globalean desgaituta; gaitzeko, joan ezarpenetara.
-
Telemetry
@@ -464,6 +462,8 @@
Marketing datuak%1$s(r)en erabiltzen dituzun eginbideei buruzko datuak partekatzen ditu Leanplum-ekin, mugikorrerako gure marketing hornitzailearekin.
+
+ Oinarrizko erabilpen-datuak partekatzen ditu Adjust-ekin, mugikorretarako gure marketing hornitzailearekinEsperimentuak
@@ -613,12 +613,16 @@
Saio pribatuaFitxa pribatuak
+
+ Sinkronizatutako fitxakGehitu fitxaGehitu fitxa pribatuaPribatua
+
+ SinkronizatuIrekitako fitxak
@@ -629,6 +633,8 @@
Partekatu fitxa guztiakItxitako azken fitxak
+
+ Kontu-ezarpenakFitxen ezarpenak
@@ -799,8 +805,6 @@
%1$s ezabatutaGehitu karpeta
-
- Laster-marka sortuta.Laster-marka gordeta!
@@ -987,6 +991,8 @@
Ekintza guztiakErabilitako azkenak
+
+ Hasi saioa sinkronizatzekoHasi saioa Sync-en
@@ -1180,9 +1186,6 @@
Ongi etorri %s(e)ra!Dagoeneko baduzu kontu bat?
-
- Ezagutu %sIkusi nobedadeakEskuratu erantzunak hemen
-
- Hasi sinkronizatzen laster-markak, historia eta gehiago zure Firefox kontua erabiliz.
+
+ Sinkronizatu Firefox gailuen artean
- Argibide gehiago
+ Ekarri laster-markak, historia eta pasahitzak gailu honetako Firefoxera.
@@ -1202,9 +1205,9 @@
Bai, hasi saioaSaioa hasten…
-
- Hasi saioa Firefoxen
+
+ Eman izenaMantendu saiotik kanpo
@@ -1212,26 +1215,23 @@
Huts egin du saioa hastean
- Pribatutasun automatikoa
-
- Pribatutasun- eta segurtasun-ezarpenek jarraipen-elementuak, malwarea eta zure jarraipena egiten duten enpresak blokeatzen dituzte.
+ Pribatutasuna beti aktibo
+
+ Firefoxek automatikoki eragozten du konpainiek sekretuki zu webean zehar jarraitzea.Oinarrizkoa (lehenetsia)
- Jarraipen-elementu gutxiago blokeatzen ditu. Orriak ohi bezala kargatuko dira.
+ Pribatutasunerako eta errendimendurako orekatua. Orriak ohi bezala kargatuko dira.Zorrotza (gomendatua)Zorrotza
- Jarraipen-elementu, iragarki eta popup gehiago blokeatzen ditu. Orriak azkarrago kargatzen dira baina zenbait eginbide agian ez dira ondo ibiliko.
-
- Aukeratu tokia
+ Jarraipen-elementu gehiago blokeatzen ditu orriak azkarrago karga daitezen baina orriko zenbait eginbide hauts litezke.
+
+ Hautatu tresna-barraren kokapena
- Probatu esku bakarreko nabigazioa behealdeko tresna-barrarekin edo jar ezazu goian.
+ Izan tresna-barra esku-eskura. Manten ezazu behean edo eraman ezazu gora.Nabigatu modu pribatuanZure pribatutasuna
+ The first parameter is the name of the app (e.g. Firefox Preview) Substitute %s for long browser name. -->
Online partekatzen duzunaren eta gurekin partekatzen duzunaren inguruko kontrola emateko diseinatu dugu %s.Irakurri gure pribatutasun-oharra
@@ -1259,7 +1259,7 @@
Aukeratu itxura
- Aurreztu bateria pixka bat eta zaindu ikusmena modu iluna gaituz.
+ Aurreztu bateria pixka bat eta zaindu ikusmena modu ilunarekin.Automatikoa
@@ -1314,13 +1314,13 @@
Oinarrizkoa (lehenetsia)
- Jarraipen-elementu gutxiago blokeatzen ditu. Orriak ohi bezala kargatuko dira.
+ Pribatutasunerako eta errendimendurako orekatua. Orriak ohi bezala kargatuko dira.Jarraipenaren oinarrizko babesak blokeatzen duenaZorrotza
- Jarraipen-elementu, iragarki eta popup gehiago blokeatzen ditu. Orriak azkarrago kargatzen dira baina zenbait eginbide agian ez dira ondo ibiliko.
+ Jarraipen-elementu gehiago blokeatzen ditu orriak azkarrago karga daitezen baina orriko zenbait eginbide hauts litezke.Jarraipenaren babes zorrotzak blokeatzen duena
@@ -1569,6 +1569,33 @@
Gehitu kreditu-txartela
+
+ Kudeatu gordetako txartelak
+
+ Gehitu txartela
+
+ Editatu txartela
+
+ Txartelaren zenbakia
+
+ Iraungitze-data
+
+ Txarteleko izena
+
+ Txartelaren ezizena
+
+ Ezabatu txartela
+
+ Ezabatu txartela
+
+ Gorde
+
+ Gorde
+
+ Utzi
+
+ Gordetako txartelak
+
Gehitu bilaketa-motorra
@@ -1729,23 +1756,13 @@
Utzi
+
+ Ireki webgune, posta elektroniko eta mezuetako loturak Firefoxen automatikoki.
+
Kendu
-
- Atera %s(r)i ahalik eta zuku gehiena.
-
Egin klik xehetasun gehiagorako
-
- Bildu zuretzat garrantzizkoa dena
-
- Multzokatu antzerako bilaketak, guneak eta fitxak sarbide azkarrago baterako.
-
- %s bezala saioa hasita duzu telefono honetako beste Firefox nabigatzaile batean. Kontu honekin saioa hasi nahi duzu?
-
- Modu errazean gehi dezakezu webgune hau zure telefonoaren hasierako pantailan berehalako sarbidea izan eta aplikazio-moduko esperientziarekin azkarrago nabigatzeko.
-
diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml
index 59b9d908c..0f0419aa4 100644
--- a/app/src/main/res/values-fa/strings.xml
+++ b/app/src/main/res/values-fa/strings.xml
@@ -19,10 +19,6 @@
زبانه های خصوصی شما در اینجا نشان داده می شود.
-
- بایدو
-
- JD1 زبانهي باز. برای تغییر زبانه ها ضربه بزنید.
@@ -178,6 +174,11 @@
ظاهر
+
+ افزودن
+
+ ویرایش
+
اتصال امکان پذیر نیست. طرح آدرس غیرقابل تشخیص است.
@@ -436,9 +437,6 @@
بیشتر بدانید
-
- در سطح جهان خاموش است ، برای روشن کردن آن به تنظیمات بروید.
-
دور سنجی
@@ -595,12 +593,16 @@
جلسه خصوصیبرگه خصوصی
+
+ زبانههای همگامسازی شدهافزودن زبانهافزودن زبانه خصوصیخصوصی
+
+ همگامسازیزبانههای باز
@@ -781,8 +783,6 @@
%1$s حذف شدافزودن پوشه
-
- نشانک ایجاد شد.نشانک ذخیره شد!
@@ -1158,9 +1158,6 @@
به %s خوش آمدید!پیش از این حساب داشتهاید؟
-
- با %s آشنا شویدموارد جدید را ببینیدپاسخ را از اینجا دریافت کنید
-
- شروع به همگام سازی نشانکها، گذرواژه ها و چیزهای بیشتر با حساب Firefox شما.
-
- اطلاعات بیشتر
@@ -1180,36 +1173,21 @@
بله، من را وارد کندر حال ورود به…
-
- واردشدن به فایرفاکس
+
+ ثبت نام کردناز سیستم خارج شویدهمگام سازی روشن استورود شکست خورد
-
- حریم خصوصی خودکار
-
- تنظیمات حریم خصوصی و امنیتی ردیاب ها ، بدافزارها و شرکت هایی که شما را ردیابی می کنند را مسدود می کند.استاندارد (پیش فرض)
-
- تعداد کمتر ردیاب را مسدود می کند. صفحات به صورت عادی بارگیری می شوند.سختگیرانه (توصیه میشود)شدید
-
- ردگیرها ، تبلیغات و پنجره های بیشتر را مسدود می کند. صفحات سریعتر بارگیری می شوند ، اما برخی از عملکردها ممکن است کار نکند.
-
- یک موضع بگیرید
-
-
- مرور یک دست را با نوار ابزار پایین امتحان کنید یا آن را به بالا منتقل کنید.
+
مرور ناشناسحریم خصوصی شما
+ The first parameter is the name of the app (e.g. Firefox Preview) Substitute %s for long browser name. -->
ما%s طراحی کرده ایم تا بتوانیم بر آنچه به صورت انلاین یا با ما اشتراک می گذارید کنترل داشته باشید.اعلامیه حریم خصوصی ما را بخوانید
@@ -1236,8 +1214,6 @@
انتخاب زمینه
-
- با فعال کردن حالت تاریک مقداری از باتری و بینایی خود را ذخیره کنید.خودکار
@@ -1291,14 +1267,10 @@
بیشتر بدانیداستاندارد (پیش فرض)
-
- تعداد کمتر ردیاب را مسدود می کند. صفحات به صورت عادی بارگیری می شوند.آنچه توسط حفاظت ردیابی استاندارد مسدود شده استسخت گیرانه
-
- ردگیرها ، تبلیغات و پنجره های بیشتر را مسدود می کند. صفحات سریعتر بارگیری می شوند ، اما برخی از عملکردها ممکن است کار نکند.آنچه توسط حفاظت ردیابی سختگیرانه مسدود شده است
@@ -1439,10 +1411,6 @@
تکمیل خودکارهمگامسازی ورودها
-
- روشن
-
- خاموشاتصال مجدد
@@ -1535,6 +1503,13 @@
مرتب سازی بر روی فهرست ورود
+
+ ذخیره
+
+ ذخیره
+
+ انصراف
+
افزودن موتور جستوجو
@@ -1696,20 +1671,7 @@
حذف
-
- نهایت استفاده از %s ببرید.
-
برای جزئیات بیشتر کلیک کنید
-
- ذخیره کردن چیزهایی که برای شما اهمیت دارد
-
- برای دسترسی سریع در آینده، جستجوها ، سایتها و برگه های مشابه را با هم جمع کنید.
-
- شما به عنوان%s در یک مرورگر دیگر Firefox در این تلفن وارد شده اید. آیا می خواهید با این حساب وارد شوید؟
-
- شما به راحتی میتوانید این پایگاه اینترنتی را به صفحه خانگی تلفن خود اضافه کنید تا دسترسی مستقیم به آن داشته باشید و مرور سریعتی را مانند تجربه کردن یک برنامه داشته باشید.
-
diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml
index 1f11179e1..360d9d33e 100644
--- a/app/src/main/res/values-fi/strings.xml
+++ b/app/src/main/res/values-fi/strings.xml
@@ -419,6 +419,11 @@
and the third is the device model. -->
%1$s laitteella %2$s %3$s
+
+ Luottokortit
+
+ Osoitteet
+
Vastaanotetut välilehdet
@@ -451,9 +456,6 @@
Lue lisää
-
- Poistettu käytöstä yleisesti. Voit ottaa sen käyttöön asetuksista.
-
Telemetria
@@ -464,6 +466,8 @@
MarkkinointitiedotJakaa tietoa %1$sissa käyttämistäsi ominaisuuksista mobiilimarkkinointia meille toteuttavan Leanplumin kanssa.
+
+ Jakaa perustiedot käytöstä mobiilimarkkinointitoimittajamme Adjustin kanssaTutkimukset
@@ -613,6 +617,8 @@
Yksityinen istuntoYksityiset välilehdet
+
+ Synkronoidut välilehdetLisää välilehti
@@ -631,6 +637,8 @@
Jaa kaikki välilehdetViimeksi suljetut välilehdet
+
+ Tilin asetuksetVälilehtiasetukset
@@ -801,8 +809,6 @@
%1$s poistettuLisää kansio
-
- Kirjanmerkki luotu.Kirjanmerkki tallennettu!
@@ -989,8 +995,6 @@
Kaikki toiminnotÄskettäin käytetty
-
- Kirjautuneena tilillä %1$sKirjaudu Sync-palveluun
@@ -1228,6 +1232,8 @@
Tiukka (suositeltu)Tiukka
+
+ Estää enemmän seuraimia, joten sivut latautuvat nopeammin, mutta jotkin sivujen toiminnot saattavat rikkoutua.Valitse työkalupalkin sijoitus
@@ -1463,10 +1469,8 @@
Automaattinen täyttöSynkronoi kirjautumistiedot
-
- Päällä
-
- Pois
+
+ Synkronoi kirjautumistiedot laitteiden välilläYhdistä uudelleen
@@ -1567,6 +1571,8 @@
Tiedot on salattuSynkronoi kortit laitteiden välillä
+
+ Synkronoi kortitLisää luottokortti
@@ -1574,6 +1580,8 @@
Hallinnoi tallennettuja korttejaLisää kortti
+
+ Muokkaa korttiaKortin numero
@@ -1582,6 +1590,8 @@
Nimi kortissaKortin kutsumanimi
+
+ Poista korttiPoista kortti
@@ -1594,6 +1604,9 @@
Tallennetut kortit
+
+ Kirjoita kelvollinen luottokortin numero
+
Lisää hakukone
@@ -1753,23 +1766,19 @@
Peruuta
+
+ Aseta verkkosivustojen, sähköpostien ja viestien linkit avautumaan automaattisesti Firefoxissa.
+
Poista
-
- Ota kaikki irti %sista.
-
Napsauta saadaksesi lisätietoja
-
- Kerää yhteen sinulle tärkeät asiat
-
- Kokoa yhteen samanlaiset haut, sivustot ja välilehdet nopeaa käyttöä varten.
-
- Olet kirjautunut tilillä %s toiseen Firefox-selaimeen tällä puhelimessa. Haluatko kirjautua sisään tällä tilillä?
-
- Voit lisätä tämän sivuston puhelimesi aloitusnäytölle, jolloin sivuston käyttö onnistuu nopeasti ja tarjoaa sovelluksen kaltaisen kokemuksen.
+
+ Liiku ylöspäin
+
+
+ Sulje
-
+
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index 6afe7530d..45f995d73 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -451,9 +451,6 @@
En savoir plus
-
- Désactivée globalement, allez dans les paramètres pour l’activer.
-
Télémétrie
@@ -464,6 +461,8 @@
Données marketingPartage des données sur les fonctionnalités que vous utilisez dans %1$s avec Leanplum, notre fournisseur de marketing mobile.
+
+ Partage des données d’utilisation de base avec Adjust, notre fournisseur de marketing mobileÉtudes
@@ -613,6 +612,8 @@
Session privéeOnglets privés
+
+ Onglets synchronisésAjouter un onglet
@@ -631,6 +632,8 @@
Partager tous les ongletsOnglets récemment fermés
+
+ Paramètres du compteParamètres des onglets
@@ -802,8 +805,6 @@
%1$s suppriméAjouter un dossier
-
- Marque-page ajouté.Marque-page ajouté
@@ -1002,8 +1003,6 @@
Toutes les actionsRécemment utilisés
-
- Connecté·e en tant que %1$sSe connecter pour synchroniser
@@ -1483,10 +1482,8 @@ Cependant, il peut être moins stable. Téléchargez la version bêta de notre n
Remplissage automatiqueSynchroniser les identifiants
-
- Activé
-
- Désactivé
+
+ Synchroniser les identifiants entre vos appareilsSe reconnecter
@@ -1586,6 +1583,8 @@ Cependant, il peut être moins stable. Téléchargez la version bêta de notre n
Les données sont chiffréesSynchroniser les cartes entre vos appareils
+
+ Synchroniser les cartesAjouter une carte de paiement
@@ -1593,6 +1592,8 @@ Cependant, il peut être moins stable. Téléchargez la version bêta de notre n
Gérer les cartes enregistréesAjouter une carte
+
+ Modifier la carteNuméro de carte
@@ -1601,6 +1602,8 @@ Cependant, il peut être moins stable. Téléchargez la version bêta de notre n
Nom du titulaireNom de cette carte
+
+ Supprimer cette carteSupprimer la carte
@@ -1613,6 +1616,9 @@ Cependant, il peut être moins stable. Téléchargez la version bêta de notre n
Cartes enregistrées
+
+ Veuillez saisir un numéro de carte bancaire valide
+
Ajouter un moteur de recherche
@@ -1771,23 +1777,13 @@ Cependant, il peut être moins stable. Téléchargez la version bêta de notre n
Annuler
+
+ Faites en sorte que les liens des sites web, des courriels et des messages s’ouvrent automatiquement dans Firefox.
+
Supprimer
-
- Tirez le meilleur parti de %s.
-
Cliquez pour plus de précisions
-
- Rassemblez ce qui compte pour vous
-
- Regroupez des recherches, des sites et des onglets similaires pour y accéder rapidement plus tard.
-
- Vous êtes connecté·e en tant que %s sur un autre navigateur Firefox avec ce téléphone. Voulez-vous vous connecter avec ce compte ?
-
- Vous pouvez facilement ajouter ce site à l’écran d’accueil de votre téléphone pour y avoir accès directement et naviguer plus rapidement, comme si vous utilisiez une application.
-
diff --git a/app/src/main/res/values-fy-rNL/strings.xml b/app/src/main/res/values-fy-rNL/strings.xml
index ed0de474e..4d1d621ec 100644
--- a/app/src/main/res/values-fy-rNL/strings.xml
+++ b/app/src/main/res/values-fy-rNL/strings.xml
@@ -49,7 +49,7 @@
Selektearre
- %1$s is makke troch @fork-maintainers.
+ %1$s is makke troch Mozilla.
@@ -416,6 +416,11 @@
and the third is the device model. -->
%1$s op %2$s %3$s
+
+ Creditcards
+
+ Adressen
+
Untfongen ljepblêden
@@ -446,9 +451,6 @@
Mear ynfo
-
- Globaal útskeakele, gean nei Ynstellingen om it yn te skeakeljen.
-
Telemetry
@@ -459,6 +461,8 @@
MarketinggegevensDielt gegevens oer hokker funksjes jo yn %1$s brûke mei Leanplum, ús leveransier fan mobile marketing.
+
+ Dielt basale gebrûksgegevens mei Adjust, ús leveransier fan mobile marketingUndersiken
@@ -605,6 +609,8 @@
PriveesesjePriveeljepblêden
+
+ Syngronisearre ljepblêdenLjepblêd tafoegje
@@ -623,6 +629,8 @@
Alle ljepblêden dieleKoartlyn sluten ljepblêden
+
+ AccountynstellingenLjepblêdynstellingen
@@ -791,8 +799,6 @@
%1$s fuortsmitenMap tafoegje
-
- Blêdwizer oanmakke.Blêdwizer bewarre!
@@ -976,8 +982,6 @@
Alle aksjesKoartlyn brûkt
-
- Oanmeld as %1$sOanmelde om te syngronisearjen
@@ -1449,10 +1453,8 @@
Automatysk ynfoljeOanmeldingen syngronisearje
-
- Oan
-
- Ut
+
+ Oanmeldingen op apparaten syngronisearjeOpnij ferbine
@@ -1552,6 +1554,8 @@
Gegevens binne fersifereKaarten syngronisearje tusken apparaten
+
+ Kaarten syngronisearjeCreditcard tafoegje
@@ -1560,6 +1564,8 @@
Kaart tafoegje
+
+ Kaart bewurkjeKaartnûmer
@@ -1568,6 +1574,8 @@
Namme op kaartKaart-bynamme
+
+ Kaart fuortsmiteKaart fuortsmite
@@ -1580,6 +1588,9 @@
Bewarre kaarten
+
+ Fier in jildich creditkaartnûmer yn
+
Sykmasine tafoegje
@@ -1740,23 +1751,19 @@
Annulearje
+
+ Keppelingen fan websites, e-mail en berjochten automatysk yn Firefox iepenje.
+
Fuortsmite
-
- Helje it measte út %s.
-
Klik foar mear details
-
- Sammelje de dingen dy\'t wichtich foar jo binne
-
- Groepearje fergelykbere sykopdrachten, websites en ljepblêden foar flugge tagong letter.
-
- Jo binne op in oare Firefox-browser op dizze telefoan oanmeld as %s . Wolle jo oanmelde mei dizze account?
-
- Jo kinne dizze website ienfâldichwei oan it startskerm fan jo telefoan tafoegje foar daliks tagong en flugger surfe mei in app-eftige ûnderfining.
+
+ Omheech
+
+
+ Slute
-
+
diff --git a/app/src/main/res/values-gn/strings.xml b/app/src/main/res/values-gn/strings.xml
index ef1812cca..5960304b5 100644
--- a/app/src/main/res/values-gn/strings.xml
+++ b/app/src/main/res/values-gn/strings.xml
@@ -449,9 +449,6 @@
Kuaave
-
- Ojepe’apáma, tereho Pytyvõhápe emyandyjey hag̃ua.
-
Telemetry
@@ -463,6 +460,8 @@
Mba’ekuaarã jehepyme’ẽrãEmoherakuã mba’ekuaarã umi tembiapoite eipurúva %1$s rehegua Leanplum ndive, ore me’ẽhára pumbyry popeguáva.
+
+ Emoherakuã mba’ekuaarã ojepurúva Adjust ndive, ore marketing me’ẽháraÑembokatupyry
@@ -614,6 +613,8 @@
Tendayke ñemiguáva
+
+ Tendayke mbojuehepyreEmbojuaju tendayke
@@ -632,6 +633,8 @@
Emoherakuã opaite tendaykeTendayke oñemboty ramóva
+
+ Mba’ete ñembohekoTendayke ñemboheko
@@ -804,8 +807,6 @@
Mboguepyre %1$sEmbojuaju ñongatuha
-
- Oñemoheñói techaukaha.¡Techaukaha ñongatupyre!
@@ -995,8 +996,6 @@
Opaite tembiaporãOjepururamovéva
-
- Eike %1$s-ramoEike embojuehe hag̃ua
@@ -1477,10 +1476,8 @@
Myanyhẽ jeheguiEmbojuehe tembiapo ñepyrũ
-
- Hendypyre
-
- Mboguepyre
+
+ Embojuehe tembiapo ñepyrũ mba’e’oka pa’ũmeEikejey
@@ -1583,6 +1580,8 @@
Mba’ekuaarã ipapapypaEmbojuehe kuatia’atã mba’e’oka pa’ũme
+
+ Embojuehe kuatia’atãEmbojuaju kuatia’atã ñemurã
@@ -1590,6 +1589,8 @@
Eñangareko kuatia’atã ñongatupyréreEmbojuaju kuatia’atã
+
+ Embosako’i kuatia’atãKuatia’atã papy
@@ -1598,6 +1599,8 @@
Kuatia’atã réraKuatia’atã je’erã
+
+ Emboguete kuatia’atãEmboguete kuatia’atã
@@ -1610,6 +1613,9 @@
Kuatia’atã ñongatupyre
+
+ Ikatúpiko ehai kuatia’atã ñemurã papapy oikóva
+
Embojuaju hekaha
@@ -1771,23 +1777,13 @@
Heja
+
+ Emboheko ñanduti renda juajuha, ñanduti veve ha ñe’ẽmondo ijuruja hag̃ua ijehegui Firefox-pe.
+
Mboguete
-
- Eguenohẽ %s-gui eikotevẽva.
-
Eikutu ápe eikuaave hag̃ua
-
- Embyaty umi mba’ekuéra ehayhu añetéva
-
- Emboaty hekaha, tenda ha tendayke ojueheguáva eike pya’eve hag̃ua ag̃ave.
-
- Eñepyrũ tembiapo %s ramo ambue Firefox kundahára ko pumbyry pegua. ¿Eñepyrũse tembiapo ko mba’ete ndive?
-
- Ikatu embojuaju ko ñanduti renda ne mba’erechaha ñepyrũgua rehe eike hag̃ua ha eikundaha pya’eve rekávo tembipuru’ícharamo.
-
diff --git a/app/src/main/res/values-hi-rIN/strings.xml b/app/src/main/res/values-hi-rIN/strings.xml
index f14d093bc..b2966adbe 100644
--- a/app/src/main/res/values-hi-rIN/strings.xml
+++ b/app/src/main/res/values-hi-rIN/strings.xml
@@ -528,6 +528,13 @@
एक महीने बाद
+
+ एक दिन के बाद बंद करें
+
+ एक हफ्ते के बाद बंद करें
+
+ एक महीने के बाद बंद करें
+
खुले टैब
@@ -549,6 +556,8 @@
सभी टैब साझा करेंहाल ही में बंद किए गए टैब
+
+ खाता सेटिंगटैब सेटिंग
@@ -563,8 +572,12 @@
बुकमार्कबंद करें
+
+ चयनित टैब साझा करेंसंग्रहण से टैब हटायें
+
+ टैब चुनेंटैब बंद करें
@@ -636,6 +649,8 @@
%1$d चीज़ों को मिटाएं
+
+ आजपिछले 24 घंटे
@@ -862,6 +877,8 @@
सभी क्रियाएंहाल ही में उपयोग किया गया
+
+ सिंक करने के लिए साइन इन करेंसिंक करने के लिए साइन इन करें
@@ -1064,6 +1081,8 @@
हां, मुझे साइन इन करेंसाइन इन हो रहा है…
+
+ साइन अपसाइन आउट रहें
@@ -1389,6 +1408,32 @@
लॉगिन मेन्यू को क्रमबद्ध करें
+
+
+ क्रेडिट कार्ड
+
+ क्रेडिट कार्ड जोड़ें
+
+ कार्ड जोड़ें
+
+ कार्ड संपादित करें
+
+ कार्ड नंबर
+
+ कार्ड पर नाम
+
+ कार्ड हटाएं
+
+ कार्ड हटाएं
+
+ सहेजें
+
+ सहेजें
+
+ रद्द करें
+
+ सहेजे गए कार्ड
+
खोज ईंजन जोड़ें
diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml
index 9218f5bb1..db9d819ff 100644
--- a/app/src/main/res/values-hr/strings.xml
+++ b/app/src/main/res/values-hr/strings.xml
@@ -413,6 +413,11 @@
and the third is the device model. -->
%1$s na %2$s %3$s
+
+ Kreditne kartice
+
+ Adrese
+
Primljene kartice
@@ -443,9 +448,6 @@
Saznaj više
-
- Isključeno je globalno, za uključivanje, idi na postavke.
-
Telemetrija
@@ -456,6 +458,8 @@
Marketinški podaciDijeli podatke o funkcijama koje upotrebljavaš u %1$s s Leanplumom, našim pružateljem usluga mobilnog marketinga.
+
+ Dijeli osnovne podatke o korištenju s Adjustom, našim pružateljem mobilnog marketingaIstraživanja
@@ -606,12 +610,16 @@
Privatna sesijaPrivatne kartice
+
+ Sinkronizirane karticeDodaj karticuDodaj privatnu karticuPrivatno
+
+ SinkronizirajOtvorene kartice
@@ -622,6 +630,8 @@
Podijeli sve karticeNedavno zatvorene kartice
+
+ Postavke računaPostavke kartica
@@ -794,8 +804,6 @@
Izbrisano: %1$sDodaj mapu
-
- Zabilješka stvorena.Zabilješka spremljena!
@@ -982,8 +990,6 @@
Sve radnjeNedavno korišteni
-
- Prijavljeni kao %1$sPrijavi se za sinkronizaciju
@@ -1456,10 +1462,8 @@
Automatsko ispunjavanjeSinkroniziraj prijave
-
- Uključeno
-
- Isključeno
+
+ Sinkroniziraj prijave na svim uređajimaPonovo poveži
@@ -1562,11 +1566,17 @@
Podaci su šifriraniSinkroniziraj kartice na uređajima
+
+ Sinkroniziraj karticeDodaj kreditnu karticu
+
+ Upravljaj spremljenim karticamaDodaj karticu
+
+ Uredi karticuBroj kartice
@@ -1575,6 +1585,8 @@
Ime na karticiNaziv kartice
+
+ Izbriši karticuIzbriši karticu
@@ -1584,6 +1596,12 @@
Odustani
+
+ Spremljene kartice
+
+
+ Unesi važeći broj kreditne kartice
+
Dodaj tražilicu
@@ -1746,23 +1764,19 @@
Odustani
+
+ Postavi automatsko otvaranje poveznica web stranica, e-pošte i poruka u Firefoxu.
+
Ukloni
-
- Iskoristi sve prednosti aplikacije %s.
-
Klikni za više informacija
-
- Skupi stvari koje su ti bitne
-
- Grupiraj slične pretrage, stranice i kartice za brži pristup kasnije.
-
- Prijavljen/a si kao %s na jednom drugom Firefox pregledniku na ovom telefonu. Želiš li se prijaviti s ovim računom?
-
- Možete jednostavno dodati ovu web stranicu na početni zaslon uređaja kako biste imali brz pristup i pretraživali brže, kao da ste u aplikaciji.
+
+ Navigiraj gore
+
+
+ Zatvori
-
+
diff --git a/app/src/main/res/values-hsb/strings.xml b/app/src/main/res/values-hsb/strings.xml
index bca5993c9..8d459bf19 100644
--- a/app/src/main/res/values-hsb/strings.xml
+++ b/app/src/main/res/values-hsb/strings.xml
@@ -414,6 +414,11 @@
and the third is the device model. -->
%1$s wot %2$s %3$s
+
+ Kreditne karty
+
+ Adresy
+
Přijate rajtarki
@@ -445,9 +450,6 @@
Dalše informacije
-
- Globalnje znjemóžnjeny, dźiće do nastajenjow, zo by jón zmóžnił.
-
Telemetrija
@@ -458,6 +460,8 @@
Marketingowe datyDźěli daty wo tym, kotre funkcije w %1$s wužiwaće, z Leanplum, našim poskićowarjom za mobilny marketing.
+
+ Dźěli zakładne wužiwanske daty z Adjust, našim mobilnym marketingowym poskićowarjomStudije
@@ -627,6 +631,8 @@
Wšě rajtarki dźělićRunje začinjene rajtarki
+
+ Kontowe nastajenjaNastajenja rajtarkow
@@ -796,8 +802,6 @@
%1$s je so zhašałRjadowak přidać
-
- Zapołožka je so wutworiła.Zapołožka je so składowała!
@@ -982,8 +986,6 @@
Wšě akcijeNjedawno wužite
-
- Přizjewjeny jako %1$sPola Sync přizjewić
@@ -1453,10 +1455,8 @@
Awtomatisce wupjelnićPřizjewjenja synchronizować
-
- Zapinjeny
-
- Wupinjeny
+
+ Přizjewjenja mjez gratami synchronizowaćZnowa zwjazać
@@ -1558,6 +1558,8 @@
Daty su zaklučowaneKarty přez graty synchronizować
+
+ Karty synchronizowaćKreditnu kartu přidać
@@ -1565,6 +1567,8 @@
Składowane karty rjadowaćKartu přidać
+
+ Kartu wobdźěłaćKartowe čisło
@@ -1573,6 +1577,8 @@
Mjeno na karćeKartowe přimjeno
+
+ Kartu zhašećKartu zhašeć
@@ -1585,6 +1591,9 @@
Składowane karty
+
+ Prošu zapodajće płaćiwe čisło kreditneje karty
+
Pytawu přidać
@@ -1744,23 +1753,19 @@
Přetorhnyć
+
+ Nastajće wotkazy z websydłow, mejlkow a powěsćow, zo bychu so awtomatisce we Firefox wočinili.
+
Wotstronić
-
- Wućehńće najlěpše z %s.
-
Klikńće za dalše podrobnosće
-
- Zběrajće wěcy, kotrež su wam wažne
-
- Zeskupće podobne pytanja, sydła a rajtarki za pozdźiši spěšny přistup.
-
- Sće so jako %s w druhim wobhladowaku Firefox na tutym telefonje přizjewił. Chceće so z tutym kontom přizjewić?
-
- Móžeće startowej wobrazowce swojeho telefona tute websydło lochko přidać, zo byšće direktny přistup měł a spěšnišo z dožiwjenjom nałoženja přehladował.
+
+ Horje
+
+
+ Začinić
-
+
diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml
index 329706753..68ca43cf1 100644
--- a/app/src/main/res/values-hu/strings.xml
+++ b/app/src/main/res/values-hu/strings.xml
@@ -415,6 +415,11 @@
and the third is the device model. -->
%1$s ezen: %2$s %3$s
+
+ Bankkártyák
+
+ Címek
+
Fogadott lapok
@@ -445,9 +450,6 @@
További tudnivalók
-
- Globálisan kikapcsolva, a bekapcsoláshoz ugorjon a Beállításokhoz.
-
Telemetria
@@ -458,6 +460,8 @@
Marketing adatokMegosztja a Leanplummal, a mobil marketing szolgáltatónkkal, hogy mely funkciókat használja a %1$s böngészőben.
+
+ Megosztja az alapvető használati adatokat az Adjusttal, a mobilos marketing szolgáltatónkkalTanulmányok
@@ -608,6 +612,8 @@
Privát munkamenetPrivát lapok
+
+ Szinkronizált lapokLap hozzáadása
@@ -626,6 +632,8 @@
Az összes lap megosztásaNemrég bezárt lapok
+
+ FiókbeállításokLapbeállítások
@@ -795,8 +803,6 @@
%1$s törölveMappa hozzáadása
-
- Könyvjelző létrehozva.Könyvjelző mentve.
@@ -982,8 +988,6 @@
Összes műveletNemrég használt
-
- Bejelentkezve mint %1$sJelentkezzen be a szinkronizálásba
@@ -1461,10 +1465,8 @@
Automatikus kitöltésBejelentkezések szinkronizálása
-
- Be
-
- Ki
+
+ Bejelentkezések szinkronizálása az eszközök közöttÚjracsatlakozás
@@ -1564,6 +1566,8 @@
Az adatok titkosítottakKártyák szinkronizálása az eszközök közt
+
+ Kártyák szinkronizálásaBankkártya hozzáadása
@@ -1571,6 +1575,8 @@
Mentett kártyák kezeléseKártya hozzáadása
+
+ Kártya szerkesztéseKártyaszám
@@ -1579,6 +1585,8 @@
Kártyán szereplő névKártya beceneve
+
+ Kártya törléseKártya törlése
@@ -1591,6 +1599,9 @@
Mentett kártyák
+
+ Adjon meg egy érvényes bankkártyaszámot
+
Keresőszolgáltatás hozzáadása
@@ -1751,23 +1762,19 @@
Mégse
+
+ Állítsa be a webhelyek, e-mailek és üzenetek hivatkozásait, hogy azok automatikusan a Firefoxban nyíljanak meg.
+
Eltávolítás
-
- Hozza ki a legtöbbet a %s böngészőből.
-
Kattintson a további részletekért
-
- Gyűjtse össze az Önnek fontos dolgokat
-
- Csoportosítsa a hasonló kereséseket, webhelyeket és lapokat a későbbi gyors elérés érdekében.
-
- A következőként van bejelentkezve egy másik Firefox böngészőben ezen a telefonon: %s. Szeretne bejelentkezni ezzel a fiókkal?
-
- Könnyedén hozzáadhatja ezt a weboldalt a telefonja Kezdőképernyőhöz, és azonnal elérheti azt, így gyorsabban böngészve, miközben alkalmazásszerű élményt kap.
+
+ Navigálás fel
+
+
+ Bezárás
-
+
diff --git a/app/src/main/res/values-hy-rAM/strings.xml b/app/src/main/res/values-hy-rAM/strings.xml
index 1998ab469..d9582c8ca 100644
--- a/app/src/main/res/values-hy-rAM/strings.xml
+++ b/app/src/main/res/values-hy-rAM/strings.xml
@@ -49,7 +49,7 @@
Ընտրված
- %1$s-ը մշակված է @fork-maintainers-ի կողմից:
+ %1$s-ը մշակված է Mozilla-ի կողմից:
@@ -413,6 +413,11 @@
and the third is the device model. -->
%1$s-ը %2$s %3$s-ում
+
+ Բանկային քարտեր
+
+ Հասցեներ
+
Ստացված ներդիրներ
@@ -443,9 +448,6 @@
Իմանալ ավելին
-
- Անջատված է գլոբալ առումով, անցեք Կարգավորումներ՝ միացնելու համար:
-
Telemetry
@@ -456,6 +458,8 @@
Շուկայավարման տվյալներՀամօգտագործում է տվյալներն այն գործառույթների վերաբերյալ, որոնք դուք օգտագործում եք %1$s-ում Leanplum-ի հետ, բջջային մարկետինգի մեր մատակարարի:
+
+ Օգտագործման հիմնական տվյալները կիսում է Adjust- ի՝ մեր բջջային շուկայավարման մատակարարի հետՈւսումնասիրություններ
@@ -603,12 +607,16 @@
Գաղտնի աշխատաշրջանԳաղտնի ներդիրներ
+
+ Համաժամեցված ներդիրներԱվելացնել ներդիրՀավելել մասնավոր ներդիրՄասնավոր
+
+ ՀամաժամեցումԲացել ներդիրները
@@ -619,6 +627,8 @@
Համօգտագործել ներդիրներըՎերջերս փակված ներդիրներ
+
+ Հաշվի կարգավորումներՆերդիրի կարգավորումներ
@@ -786,8 +796,6 @@
%1$s-ը ջնջվել էԱվելացնել պանակ
-
- Էջանիշը ստեղծվեց:Էջանիշը պահպանվեց:
@@ -975,6 +983,8 @@
Բոլոր գործողություններըՎերջերս փակված
+
+ Մուտք գործեք՝ համաժամեցնելու համարՄուտք գործել Համաժամեցում
@@ -1443,10 +1453,8 @@
ԻնքնալցնումՀամաժամեցնել մուտքանունները
-
- Միաց.
-
- Անջ.
+
+ Համաժամեցնել մուտքագրումները սարքերի միջևՎերակապակցել
@@ -1548,10 +1556,16 @@
Տվյալները գաղտնագրված ենՀամաժամեցնել քարտերը սարքերի միջև
+
+ Համաժամեցնել քարտերըԱվելացնել բանկային քարտ
+
+ Կառավարել պահված քարտերըԱվելացնել քարտ
+
+ Խմբագրել քարտըՔարտի համարը
@@ -1560,6 +1574,8 @@
Քարտի վրա ձեր անունըՔարտի անունը
+
+ Ջնջել քարտըՋնջել քարտը
@@ -1569,6 +1585,12 @@
Չեղարկել
+
+ Պահպանված քարտեր
+
+
+ Մուտքագրեք բանկային վավեր քարտի համար
+
Ավելացնել որոնիչ
@@ -1728,23 +1750,19 @@
Չեղարկել
+
+ Կայեք հղումներ կայքերից, էլ. նամակներից և հաղորդագրություններից, որոնք ինքնաբար կերպով կբացվեն Firefox-ում:
+
Հեռացնել
-
- Ստացեք առավելագույնը %s-ից:
-
Սեղմեք` մանրամասների համար
-
- Հավաքեք ձեզ համար կարևոր բաները
-
- Խմբավորեք համանման որոնումները, կայքերը և ներդիրները՝ հետո արագ մատչելու համար:
-
- Դուք մուտք եք գործել որպես %s այլ Firefox դիտարկիչում: Ցանկանո՞ւմ եք մուտք գործել այս հաշիվ:
-
- Հեշտությամբ կարող եք ավելացնել այս կայքը ձեր հեռախոսի Տնային էկրանին՝ ակնթարթորեն մատչելու և արագ դիտարկելու համար:
+
+ Նավարկել վերև
+
+
+ Փակել
-
+
diff --git a/app/src/main/res/values-ia/strings.xml b/app/src/main/res/values-ia/strings.xml
index 60b6765ca..7d3f87146 100644
--- a/app/src/main/res/values-ia/strings.xml
+++ b/app/src/main/res/values-ia/strings.xml
@@ -10,9 +10,70 @@
+
+ Activar le Navigation anonyme.
+
+ Disactivar le Navigation anonyme.
+
+ Insere un adresse o face un recerca
+
+ Tu schedas aperte essera monstrate hic.
+
+ Tu schedas anonyme essera monstrate hic.
+
+ 1 scheda aperte. Tocca pro mutar schedas.
+
+
+ %1$s schedas aperte. Tocca pro mutar schedas.
+
+ %1$d seligite
+
+ Adder nove collection
+
+ Nomine
+
+ Eliger un collection
+
+ Exir del modo multiselection
+
+ Salvar schedas eligite al collection
+
+ Selecte %1$s
+
+ %1$s deseligite
+
+ Exite del modo multiselection
+
+ Inserite modo multiselection, elige schedas a salvar a in un collection
+
+ Seligite
+
+
+ %1$s es producite per Mozilla.
+
+
+
+ Tu es in un session anonyme
+
+ %1$s clara tu chronologia de recerca e de navigation del schedas anonyme quando tu claude los o quita le app. Ben que isto non te rende anonyme al sitos web o a tu fornitor de servicio internet, isto rende plus facile mantener lo que tu face online reservatemente contra quicunque altere usa iste apparato.
+
+ Mythos commun re le navigation anonyme
+
+
+ Deler le session
+
+
+
+ Adde un via-breve pro aperir schedas anonyme de tu Pagina initial.
+
+ Adder via-breveNo, gratias
+
+
+ Tu pote configurar Firefox pro aperir automaticamente ligamines in apps.Ir a parametros
@@ -154,7 +215,7 @@
Non permitter
- Permitter suggestiones de recerca in sessiones anonyme?<
+ Permitter suggestiones de recerca in sessiones anonyme?
@@ -567,6 +628,8 @@
Schedas anonyme
+
+ Schedas synchronisateAdder un scheda
@@ -585,6 +648,8 @@
Compartir tote le schedasSchedas claudite recentemente
+
+ Parametros de contoParametros del schedas
@@ -950,8 +1015,6 @@
Usate recentemente
-
- Authenticate como %1$sAccede pro synchronisar
@@ -1131,12 +1194,23 @@
Firefox Nightly ha movite
+
+
+ Iste app non recipera plus actualisationes de securitate. Cessa de usar iste app e passa al nove Nightly.
+ \n\nPro transferer tu marcapaginas, credentiales e chronologia a un altere app, crea un conto Firefox.Passar al nove NightlyFirefox Nightly ha movite
+
+
+ Iste app non recipera plus actualisationes de securitate. Installa le nove Nightly e cessa de usar iste app.
+ \n\nPro transferer tu marcapaginas, credentiales e chronologia a un altere app, crea un conto Firefox.
+
+ Installa le nove Nightly
+
@@ -1145,12 +1219,31 @@
Ha tu ja un conto?Vide le novas
+
+ Ha tu questiones re le %s redesignate? Voler tu saper lo que es cambiate?
+
+ Obtene responsas hicSynchronisar Firefox inter apparatosApporta marcapaginas, chronologia e contrasignos de Firefox sur iste dispositivo.
+
+ Tu es authenticate como %s sur un altere navigator Firefox sur iste apparato. Vole tu acceder con iste conto?
+
+ Si, authenticar me
+
+ Apertura de session…Inscribe te
+
+ Restar disconnexe
+
+ Sync es activate
+
+ Authentication impossibileConfidentialitate sempre active
@@ -1160,14 +1253,32 @@
Equilibrio inter confidentialitate e prestation. Le paginas carga normalmente.
+ Stricte (recommendate)
+
StricteBloca plus traciatores assi que le paginas se carga plus veloce, ma alcun functionalitate sur le pagina pote corrumper se.
+
+ Elige le ubication de tu barra del instrumentos
+
+ Pone le barra del instrumentos pro attinger lo facilemente.
+
+ Naviga reservatemente
+
+ Aperi un scheda anonyme un sol vice: tocca le icone %s.
+
+ Aperi schedas anonyme cata vice: actualisa tu parametros de navigation anonyme.Aperir le parametrosTu confidentialitate
+
+ Nos ha designate %s pro te dar controlo super lo que tu comparti online e con nos.
+
+ Lege nostre aviso de confidentialitateClauder
@@ -1183,18 +1294,46 @@
Automatic
+
+ Se adapta al parametros de tu apparato
+
+ Thema obscur
+
+ Thema clar
+
+
+ Schedas inviate!Scheda inviate!
+
+ Schedas inviarRETENTAR
+
+ Scander le codice
+
+ https://firefox.com/pair]]>Preste a scander
+
+ Accede con tu camera
+
+ Alteremente usa un email
+
+ Crea unpro synchronisar Firefox inter apparatos.]]>
+
+ Firefox cessara synchronisar con tu conto, ma non delera ulle tu datos de navigation sur iste apparato.
+
+ %s cessara synchronisar con tu conto, ma non delera ulle tu datos de navigation sur iste apparato.DisconnecterCancellar
+
+ Impossibile rediger plicas predefinite
+
Parametros de protection
@@ -1202,24 +1341,49 @@
Protection antitraciamento reinfortiateNaviga sin lassar te sequer
+
+ Mantene tu datos pro te mesme. %s te protege contra multe del plus commun traciatores que seque lo que tu face online.
+Saper plusStandard (predefinite)Equilibrio inter confidentialitate e prestation. Le paginas carga normalmente.
+
+ Lo que es blocate per standard protection de traciamentoStricteBloca plus traciatores assi que le paginas se carga plus veloce, ma alcun functionalitate sur le pagina pote corrumper se.
+
+ Lo que es blocate per standard protection de traciamentoPersonalisate
+
+ Elige qual traciatores e scripts blocar.
+
+ Lo que es blocate per rigorose protection de traciamentoCookies
+
+ Traciatores inter sitos e de retes social
+
+ Cookie de sitos web non visitate
+
+ Tote le cookies de tertie-partes (pote causar que sitos web collabe)
+
+ Tote le cookies (causara que sitos web collabe)Contento traciante
+
+ In tote le schedas
+
+ Solo in Schedas anonyme
+
+ Solo in Schedas personalisateCryptominatores
@@ -1229,13 +1393,52 @@
PermittiteTraciatores de retes social
+
+ Limita le capacitate de retes social de traciar tu activitate de navigation circum le web.
+
+ Cookies de traciamento inter sitos
+
+ Bloca cookies que retes e companias de analyse datos publicitari usa pro compilar tu datos de navigation inter multe sitos.Cryptominatores
+
+ Impedi scripts maligne de ganiar accesso a tu apparato pro minar moneta digital.Dactylogrammatores
+
+ Impedi al datos identificative unic, que pote esser usate pro scopos de traciamento, de esser colligite re tu apparato.Contento traciante
+
+ Impedi cargar avisos publicitari, videos e altere contento externe que contine codification de traciamento. Pote interessar le functionalitate de alcun sito web.
+
+ Cata vice le escudo es purpuree, %s ha blocate traciatores sur un sito. Tocca pro altere informationes.
+
+ Le protectiones es ACTIVE pro iste sito
+
+ Protectiones es INACTIVE pro iste sito
+
+ Le protection contra-traciamento reinfortiate es inactive pro iste sitos web
+
+ Naviga a retro
+
+ Tu derectos
+
+ Le bibliothecas open-source que nos usa
+
+ Lo que es nove in %s
+
+ %s | Bibliothecas OSS
+
+
+ Traciatores de re-direction
+
+
+ Clara cookies definite per re-directiones a note sitos web de traciamento.
+
Supporto
@@ -1245,6 +1448,21 @@
Prende acto de tu derectos
+
+ Informationes de licentias
+
+
+ Bibliothecas que nos usa
+
+ Menu de depuration: %1$d clic(s) restate pro activar
+ Menu de depuration activate
+
+
+ 1 scheda
+
+ %d schedas
+
Copiar
@@ -1264,6 +1482,24 @@
Continuar al sito web
+
+ Nomine via-breve
+
+ Tu pote facilemente adder iste sito web al Pagina initial de tu apparato pro haber accesso instantanee e navigar plus veloce con un experientia simile al app.
+
+
+ Credentiales e contrasignos
+
+
+ Salvar credentiales e contrasignos
+
+ Demandar pro salvar
+
+ Jammais salvar
+
+ Autoplenar
+
+ Synchronisar credentialesActive
@@ -1274,40 +1510,96 @@
Authenticar se a SyncAuthenticationes salvate
+
+ Le credentiales que tu salva o synchronisa a in %s apparera hic.Apprender plus re Sync.Exceptiones
+
+ Credentiales e contrasignos que non es salvate essera monstrate hic.
+
+ Credentiales e contrasignos non sera salvate pro iste sitos.
+
+ Deler tote le exceptiones
+
+ Cercar credentiales
+
+ Alphabeticamente
+
+ Usate recentementeSitoNomine de usatorContrasigno
+
+ Reinsere tu PIN
+
+ Disbloca pro vider tu credentiales salvate
+
+ Iste connexion non es secur. Le credentiales inserite hic pote esser compromittite.Saper plus
+
+ Vole tu que %s salva iste credentiales?SalvarNon salvarContrasigno copiate in le area de transferentia
+
+ Nomine de usator copiate al area de transferentia
+
+ Sito copiate al area de transferentiaCopiar le contrasigno
+
+ Clarar contrasignoCopiar le nomine de usator
+
+ Clarar nomine de usator
+
+ Copiar sito
+
+ Aperir sito in navigatorMonstrar contrasignoCelar contrasigno
+
+ Disbloca pro vider tu credentiales salvate
+
+ Assecurar tu credentiales e contrasignos
+
+ Implementa un patrono de blocada apparato, PIN o contrasigno pro proteger tu credentiales e contrasignos salvate de esser accedite, si alcuno altere ha tu apparato.
+
+ Implementar ora
+
+ Disbloca tu apparato
+
+ Aggrandir tote le sitos web
+
+ Activar pro permitter le "prisa e zoom", mesmo sur sitos web que impedi iste gesto.Nomine (A-Z)
+
+ Ultimemente usate
+
+
+ Ordinar menu de credentiales
+
Cartas de credito
+
+ Gerer le cartas salvateDatos es cryptate
@@ -1315,8 +1607,12 @@
Adde un carta de credito
+
+ Gerer le cartas salvateAdder carta
+
+ Rediger cartaNumero del carta
@@ -1325,6 +1621,10 @@
Nomine sur le cartaPseudonymo del carta
+
+ Deler carta
+
+ Deler cartaSalvar
@@ -1332,6 +1632,13 @@
Cancellar
+
+ Cartas salvate
+
+
+ Adder motor de recerca
+
+ Rediger motor de recercaAdder
@@ -1346,12 +1653,28 @@
AltereNomine
+
+ Catena de recerca a usar
+
+ Replaciar le recerca con “%s”. Exemplo:\nhttps://www.google.com/search?q=%sSaper plus
+
+ Detalios del motor de recerca personalisateSaper plus ligamines
+
+ Insere nomine del motor de recerca
+
+ Un motor de recerca con nomine “%s” existe jam.
+
+ Insere un catena de recerca
+
+ Verifica que le catena de recerca concorda con le formato de exemplo
+
+ Error de connexion a “%s”Create %s
@@ -1359,21 +1682,68 @@
Delite %s
+
+ Benvenite in un %s tote nove
+
+ Un navigator completemente redesignate te attende, con prestation e functionalitates meliorate pro te adjutar a facer plus online.\n\nAttende durante que nos actualisa %s con tu:
+
+ Actualisation de %s...
+
+ Initiar %s
+
+ Migration completateContrasignos
+
+ Pro permitter lo:
+
+
+ 1. Va a Parametros de Android
+
+ Permissos]]>
+
+ %1$s a Active]]>
+
Connexion securConnexion non secur
+
+ Es tu secur que tu vole clarar tote le permissos sur tote le sitos?
+
+ Es tu secur que tu vole clarar tote le permissos pro iste sito?
+
+ Es tu secur que tu vole clarar iste permisso pro iste sito?
+
+ Nulle exceptiones sitoPrincipal articulos
+
+ Desira tu vermente deler iste marcapagina?
+
+ Adder a sitos principal
+
+ Verificate per: %1$sDelerRediger
+
+ Desira tu vermente deler iste credentiales?Deler
+
+ Optiones de apertura de session
+
+ Le campo de texto redigibile pro le adresse web del credentiales.
+
+ Le campo de texto redigibile pro le nomine de usator del accesso.
+
+ Le campo de texto redigibile pro le contrasigno del credentiales.
+
+ Salvar cambiamentos a credentiales.Refusar le modificationes
@@ -1386,14 +1756,62 @@
Parla ora
-
+
+ Un credential con ille nomine de usator existe jam
+
+
+
+ Connecter un altere apparato.
+
+ Per favor re-authentica te.
+
+ Activa le synchronisation del scheda.
+
+ Tu non ha schedas aperte in Firefox sur tu altere apparatos.
+
+ Vider un lista de schedas de tu altere apparatos.
+
+ Aperi session pro synchronisar
+
+ Nulle schedas aperite
+
+
+
+ Attingite limite del numero de sitos
+
+ Pro adder un altere sito principal, remove un. Tocca e retene le sito e selige remover.
+
+ De accordo
+
+ Monstrar le sitos plus visitate
+
Nomine
+
+ Nomine de sito principalOKCancellar
+
+ Stabilir qual ligamines de sitos web, e-mails e messages se aperi automaticamente in Firefox.
+
Remover
-
+
+ Obtene le maximo de %s.
+
+
+ Clicca pro altere detalios
+
+
+ Collige le objectos que te interessa
+
+ Gruppa insimul recercas simile, sitos e schedas pro acceder rapidemente plus tarde.
+
+ Tu es authenticate como %s sur un altere navigator Firefox sur iste telephono. Vole tu acceder con iste conto?
+
+ Tu pote facilemente adder iste sito web al pagina initial de tu telephono pro acceder instantaneemente e navigar plus velocemente, como in un app.
+
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index b9a781f22..d805839a4 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -421,6 +421,11 @@
and the third is the device model. -->
%1$s su %2$s %3$s
+
+ Carte di credito
+
+ Indirizzi
+
Schede ricevute
@@ -454,9 +459,6 @@
Ulteriori informazioni
-
- Disattivata a livello globale, aprire le impostazioni per attivarla.
-
Telemetria
@@ -468,6 +470,8 @@
Dati di marketingCondividi dati sulle funzioni utilizzate in %1$s con Leanplum, il nostro partner per il marketing su piattaforma mobile.
+
+ Condivide i dati di utilizzo di base con Adjust, il nostro fornitore di mobile marketingStudi
@@ -617,6 +621,8 @@
Sessione anonimaSchede anonime
+
+ Schede sincronizzateAggiungi scheda
@@ -635,6 +641,8 @@
Condividi tutte le schedeSchede chiuse di recente
+
+ Impostazioni accountImpostazioni schede
@@ -806,8 +814,6 @@
%1$s eliminataAggiungi cartella
-
- Segnalibro aggiunto.Segnalibro salvato
@@ -1005,8 +1011,6 @@
Tutte le azioniUtilizzate di recente
-
- Accesso effettuato come %1$sAccedi per sincronizzare
@@ -1486,10 +1490,8 @@
Compilazione automaticaSincronizza le credenziali
-
- Attiva
-
- Disattivata
+
+ Sincronizza credenziali tra dispositiviRiconnetti
@@ -1590,6 +1592,8 @@
I dati sono crittatiSincronizza le carte tra più dispositivi
+
+ Sincronizza carte di creditoAggiungi una carta di credito
@@ -1597,6 +1601,8 @@
Gestione carte salvateAggiungi carta
+
+ Modifica cartaNumero carta
@@ -1606,6 +1612,8 @@
Nickname della carta
+
+ Elimina cartaElimina carta
@@ -1618,6 +1626,9 @@
Carte salvate
+
+ Inserire un numero di carta di credito valido
+
Aggiungi motore di ricerca
@@ -1778,23 +1789,19 @@
Annulla
+
+ Apri link da siti web, email e messaggi in Firefox per impostazione predefinita.
+
Elimina
-
- Ottieni il massimo da %s.
-
Fare clic per ulteriori dettagli
-
- Raggruppa i tuoi interessi
-
- Raggruppa ricerche, siti e schede simili tra loro per ritrovarle più rapidamente.
-
- Sei già connesso come %s in un altro browser Firefox sul dispositivo in uso. Desideri accedere con questo account?
-
- È possibile aggiungere questo sito web alla schermata principale del telefono per accedervi più rapidamente, come se si trattasse di un’app.
+
+ Vai su
+
+
+ Chiudi
-
+
diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml
index 04c8d250d..078bd7a0d 100644
--- a/app/src/main/res/values-iw/strings.xml
+++ b/app/src/main/res/values-iw/strings.xml
@@ -415,6 +415,11 @@
and the third is the device model. -->
%1$s על %2$s %3$s
+
+ כרטיסי אשראי
+
+ כתובות
+
לשוניות שהתקבלו
@@ -445,9 +450,6 @@
מידע נוסף
-
- כבויה באופן גלובלי, יש לעבור להגדרות כדי להפעיל אותה.
-
Telemetry
@@ -459,6 +461,8 @@
נתוני שיווקשיתוף מידע על התכונות שהכי משמשות אותך ב־%1$s עם Leanplum, ספק השיווק שלנו למכשירים ניידים.
+
+ משתף נתוני שימוש בסיסיים עם Adjust, ספק השיווק שלנו בסלולרמחקרים
@@ -606,6 +610,8 @@
הפעלה פרטיתלשוניות פרטיות
+
+ לשוניות מסונכרנותהוספת לשונית
@@ -624,6 +630,8 @@
שיתוף כל הלשוניותלשוניות שנסגרו לאחרונה
+
+ הגדרות חשבוןהגדרות לשוניות
@@ -792,8 +800,6 @@
נמחק %1$sהוספת תיקייה
-
- סימנייה נוצרה.הסימנייה נשמרה!
@@ -980,8 +986,6 @@
כל הפעולותבשימוש לאחרונה
-
- מחובר בתור %1$sכניסה כדי לסנכרן
@@ -1453,10 +1457,8 @@
מילוי אוטומטיסנכרון כניסות
-
- פעיל
-
- כבוי
+
+ סנכרון כניסות בין מכשיריםחיבור מחדש
@@ -1557,6 +1559,8 @@
הנתונים מוצפניםסנכרון כרטיסים בין מכשירים
+
+ סנכרון כרטיסיםהוספת כרטיס אשראי
@@ -1564,6 +1568,8 @@
ניהול כרטיסים שמוריםהוספת כרטיס
+
+ עריכת כרטיסמספר כרטיס
@@ -1572,6 +1578,8 @@
שם שעל הכרטיסכינוי עבור הכרטיס
+
+ מחיקת כרטיסמחיקת כרטיס
@@ -1584,6 +1592,9 @@
כרטיסים שמורים
+
+ נא להכניס מספר כרטיס אשראי תקין
+
הוספת מנוע חיפוש
@@ -1743,23 +1754,19 @@
ביטול
+
+ הגדרת קישורים מאתרים, מהודעות דוא״ל ומהודעות לפתיחה אוטומטית ב־Firefox.
+
הסרה
-
- להוציא את המיטב מ־%s.
-
יש ללחוץ לפרטים נוספים
-
- לאסוף את הדברים החשובים לך
-
- ניתן לקבץ חיפושים, אתרים ולשוניות דומים יחד כדי לגשת אליהם מהר יותר בהמשך.
-
- דפדפן Firefox נוסף בטלפון זה מחובר כ־%s. האם ברצונך להתחבר עם חשבון זה?
-
- באפשרותך להוסיף בקלות אתר זה למסך הבית של הטלפון שלך כדי לקבל גישה מיידית ולגלוש מהר יותר עם חוויה שמדמה שימוש ביישומון.
+
+ ניווט למעלה
+
+
+ סגירה
-
+
diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml
index fa2a9f24a..54415a6d1 100644
--- a/app/src/main/res/values-ja/strings.xml
+++ b/app/src/main/res/values-ja/strings.xml
@@ -420,6 +420,11 @@
and the third is the device model. -->
%2$s %3$s 上の %1$s
+
+ クレジットカード情報
+
+ 住所
+
受信したタブ
@@ -453,9 +458,6 @@
詳細情報
-
- すべてのサイトで無効にしました。有効にするには設定から変更してください。
-
Telemetry
@@ -466,6 +468,8 @@
マーケティングデータ%1$s で使用した機能に関するデータをモバイルマーケティングベンダーの Leanplum と共有します。
+
+ 基本的な使用状況データをモバイルマーケティングベンダーの Adjust と共有します調査
@@ -613,12 +617,16 @@
プライベートセッションプライベートタブ
+
+ 同期したタブタブを追加プライベートタブを追加プライベート
+
+ 同期開いているタブ
@@ -629,6 +637,8 @@
すべてのタブを共有最近閉じたタブ
+
+ アカウント設定タブ設定
@@ -800,8 +810,6 @@
%1$s フォルダーを削除しましたフォルダー追加
-
- ブックマークを作成しました。ブックマークを保存しました!
@@ -991,8 +999,6 @@
すべての操作最近使用
-
- %1$s としてログインログインして同期
@@ -1466,10 +1472,8 @@
自動補完ログイン情報を同期
-
- オン
-
- オフ
+
+ 端末間でログイン情報を同期します再接続
@@ -1571,11 +1575,17 @@
データは暗号化されています端末間でカード情報を同期する
+
+ クレジットカード情報を同期クレジットカードを追加
+
+ 保存したカードを管理カードの追加
+
+ カードの編集カード番号
@@ -1584,6 +1594,8 @@
カード名義カードのニックネーム
+
+ カードを削除カードを削除
@@ -1593,6 +1605,12 @@
キャンセル
+
+ 保存したカード
+
+
+ 有効なクレジットカード番号を入力してください
+
検索エンジンの追加
@@ -1753,23 +1771,19 @@
キャンセル
+
+ ウェブサイトやメール、メッセージのリンクを自動的に Firefox で開きます。
+
削除
-
- %s を最大限に活用しよう。
-
詳細はこちら
-
- あなたにとって関心のある事柄を集めましょう
-
- 類似の検索結果、サイト、タブをグループ化して、後ですばやくアクセスできます。
-
- すでに、この端末の別の Firefox ブラウザーで %s としてログインしています。このアカウントでログインしますか?
-
- このウェブサイトを簡単な操作で端末のホーム画面に追加しましょう。アプリのような感覚で手軽にアクセスして素早く閲覧できます。
+
+ 上へ移動します
+
+
+ 閉じる
-
+
diff --git a/app/src/main/res/values-ka/strings.xml b/app/src/main/res/values-ka/strings.xml
index a0b13a824..7c4bdfc12 100644
--- a/app/src/main/res/values-ka/strings.xml
+++ b/app/src/main/res/values-ka/strings.xml
@@ -410,6 +410,11 @@
and the third is the device model. -->
%1$s მოწყობილობაზე %2$s %3$s
+
+ საკრედიტო ბარათები
+
+ მისამართები
+
მიღებული ჩანართები
@@ -441,9 +446,6 @@
ვრცლად
-
- გამორთულია სრულად, ჩასართავად გადადით პარამეტრებში.
-
გაზომვები
@@ -455,6 +457,8 @@
მარკეტინგული მონაცემებიგაუზიარებს მონაცემებს, თუ რომელი შესაძლებლობებით მოგწონთ %1$s ყველაზე მეტად, Leanplum-ს, მობილურის მარკეტინგის მომსახურების ჩვენს მომწოდებელს.
+
+ გამოყენების გამარტივებულ მონაცემებს გაეცნობა Adjust, რომელიც მობილურ მარკეტინგში გვიწევს მომსახურებასკვლევები
@@ -603,12 +607,16 @@
პირადი სეანსიპირადი ჩანართები
+
+ დასინქ. ჩანართებიჩანართის დამატებაპირადი ჩანართის დამატებაპირადი
+
+ დასინქრონებაგახსნილი ჩანართები
@@ -619,6 +627,8 @@
ყველა ჩანართის გაზიარებაბოლოს დახურული ჩანართები
+
+ ანგარიშის პარამეტრებიჩანართის პარამეტრები
@@ -787,8 +797,6 @@
წაიშალა %1$sსაქაღალდის დამატება
-
- სანიშნი შექმნილია.ჩანიშნულია!
@@ -974,6 +982,8 @@
ყველა მოქმედებაბოლოს გამოყენებული
+
+ სინქრონიზაციაში შესვლადასინქრონებაში შესვლა
@@ -1443,10 +1453,8 @@
თვითშევსებაანგარიშების დასინქრონება
-
- ჩართ.
-
- გამორთ.
+
+ ანგარიშების დასინქრონება მოწყობილობებზეხელახლა დაკავშირება
@@ -1549,11 +1557,17 @@
დაასინქრონეთ ბარათები სხვადასხვა მოწყობილობაზე
+
+ ბარათების დასინქრონებასაკრედიტო ბარათის დამატება
+
+ შენახული ბარათების მართვაბარათის დამატება
+
+ ბარათის ჩასწორებაბარათის ნომერი
@@ -1562,6 +1576,8 @@
მფლობელის სახელიბარათის ზედმეტსახელი
+
+ ბარათის წაშლაბარათის წაშლა
@@ -1571,6 +1587,12 @@
გაუქმება
+
+ შენახული ბარათები
+
+
+ გთხოვთ, შეიყვანოთ საკრედიტო ბარათის მართებული ნომერი
+
საძიებო სისტემის დამატება
@@ -1731,23 +1753,19 @@
გაუქმება
+
+ მითითება რომ ბმულები ვებსაიტებიდან, ელფოსტიდან და შეტყობინებებიდან, გაიხსნას პირდაპირ Firefox-ში.
+
მოცილება
-
- სრულყოფილად გამოიყენეთ %s.
-
დაწკაპეთ ვრცლად სანახავად
-
- შეაგროვეთ, რაც თქვენთვის ღირებულია
-
- თავი მოუყარეთ მოძიებულ მასალებს, საიტებს, ჩანართებს, სწრაფი წვდომისთვის.
-
- თქვენ შესული ხართ, როგორც %s სხვა Firefox-ბრაუზერზე ამ ტელეფონში. გსურთ ამ ანგარიშით შესვლა?
-
- მარტივად შეგიძლიათ დაამატოთ ეს საიტი ტელეფონის მთავარ ეკრანზე, სწრაფად წვდომისა და გახნისთვის, პროგრამის მსგავსად.
+
+ ზემოთ გადასვლა
+
+
+ დახურვა
-
+
diff --git a/app/src/main/res/values-kab/strings.xml b/app/src/main/res/values-kab/strings.xml
index 98ef02e1d..df083a661 100644
--- a/app/src/main/res/values-kab/strings.xml
+++ b/app/src/main/res/values-kab/strings.xml
@@ -419,6 +419,11 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara
and the third is the device model. -->
%1$s deg %2$s %3$s
+
+ Tikarḍiwin n usmad
+
+ Tansiwin
+
Accaren yettwaremsen
@@ -450,9 +455,6 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara
Issin ugar
-
- Yensa s wudem amatu, ddu ɣer yiɣewwaren akken ad yermed.
-
Tilisɣelt
@@ -463,6 +465,8 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara
Isefka n uzenziBḍu isefka ɣef timahilin i tseqdaceḍ deg %1$s s Leanplum, Imzenzi-nneɣ n izirazen.
+
+ Ibeṭṭu isefka n useqdec azadur akked Adjust, amsenzi-nneɣ n ssuq n uzirazStudies
@@ -614,12 +618,16 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara
Tiɣimit tusligtIccaren usligen
+
+ Iccaren yemtawinRnu iccerRnu iccer usligUslig
+
+ SyncLdi acarren
@@ -630,6 +638,8 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara
Bḍu akk accarenIccaren imedlen melmi kan
+
+ Iɣewwaṛen n umiḍanIɣewwaren n yiccer
@@ -800,8 +810,6 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara
%1$s4 yettwakkesRnu akaram
-
- Tacreṭ n usebter tettwarna.Tacreṭ n usebter tettwasekles!
@@ -988,6 +996,8 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara
Tigawin meṛṛaTtwasqedcen melmi kan
+
+ Kcem akken ad tsemtawiḍQqen ɣer Sync
@@ -1210,12 +1220,22 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara
Tuccḍa n tuqqnaTabaḍnit tezga tettwaḍmen
+
+ Firefox yessewḥal s wudem awurman tikebbaniyin ara ak-iḍefren deg web.Alugan (amezwer)
+
+ Yerked gar tbaḍnit akked temlellit. ISebtar ad d-alin s wudem amagnu.Uḥriṣ (yelha)Uḥris
+
+ Isewḥal ugar n ineḍfaren akken isebtar ad d-allin s wudem aurad, maca kra n tmahilin n usebter zemrent ad rẓent.
+
+ Fren adig n ufeggag-ik·im n yifecka
+
+ Eǧǧ afeggag n yifecka ɣef wafus. Eǧǧ-it ddaw neɣ err-it d asawen.Tunigin tusliginFren asentel
+
+ Ḥreze tabatrit d wallen-ik s uskar aberkan.Awurman
@@ -1297,10 +1319,14 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara
Issin ugarAlugan (amezwer)
+
+ Yerked gar tbaḍnit akked temlellit. ISebtar ad d-alin s wudem amagnu.Ayen iweḥlen s ummeten n tizeɣt mgal aḍfaṛUḥriṣ
+
+ Isewḥal ugar n ineḍfaren akken isebtar ad d-allin s wudem aurad, maca kra n tmahilin n usebter zemrent ad rẓent.Ayen iweḥlen s ummeten n uḥriṣ mgal aḍfaṛ
@@ -1443,10 +1469,8 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara
Taččart tawurmantMtawi inekcam
-
- Yermed
-
- Yensa
+
+ Mtawi inekcam gqr yibenkanAles tuqqna
@@ -1539,6 +1563,49 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara
Umuɣ n usemyizwer n yinekcam
+
+
+ Tikarḍiwin n usmad
+
+ Asekles d taččart tawurmant n tkarḍiwin
+
+ Isefka ttwawgelhen
+
+ Mtawi tikarḍiwin gar yibenkan
+
+ Mtawi tikarḍiwin
+
+ Rnu takarḍa n usmad
+
+ Sefrek tikerḍiwin yettwaskelsen
+
+ Rnu takarḍa
+
+ Ẓreg takarḍa
+
+ Uṭṭun n tkarḍa
+
+ Azemz n taggara
+
+ Isem ɣef tkarḍa
+
+ Mefferisemn tkarḍa
+
+ Kkes takarḍa
+
+ Kkes takarḍa
+
+ Sekles
+
+ Sekles
+
+ Sefsex
+
+ Tikerḍiwin yettwasekles
+
+
+ Ma ulac aɣilif sekcem uṭṭun ameɣtu n tkarḍa n usmad
+
Rnu amsedday n unadi
@@ -1700,23 +1767,19 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara
Sefsex
+
+ Sbadu iseɣwan seg yismal web, seg yimaylen d yiznan i twaledyawt s wudem awurman deg Firefox.
+
Kkes
-
- Awi %s amaynut akk.
-
Sit i wugar n telqayt
-
- Lqeḍ tiɣawsiwin i tḥemmleḍ
-
- Segrew akk akken inadiyen, ismal d waccaren yemṣadan akken ad yishil unekcum ɣer-sen mbeεd.
-
- Aql-ak teqqneḍ s %s ɣef yiminig-nniḍen Firefox s wugur-a. Tenɣiḍ ad teqqneḍ s umiḍan-a?
-
- Adtizmireḍ s tifses ad ternuḍ asmel-a web ɣer ugdil agejdan n tiliɣri-ik akken ad tesɛuḍ anekcum askudan daɣen ad tinigeḍ s zreb s termit icuban asnas.
+
+ Inig d asawen
+
+
+ Mdel
-
+
diff --git a/app/src/main/res/values-kk/strings.xml b/app/src/main/res/values-kk/strings.xml
index a065a0930..4574961a0 100644
--- a/app/src/main/res/values-kk/strings.xml
+++ b/app/src/main/res/values-kk/strings.xml
@@ -48,7 +48,7 @@
Таңдалған
- %1$s жасаған @fork-maintainers.
+ %1$s жасаған Mozilla.
@@ -406,6 +406,11 @@
and the third is the device model. -->
%1$s, %2$s %3$s
+
+ Несиелік карталар
+
+ Адрестер
+
Алынған беттер
@@ -437,9 +442,6 @@
Көбірек білу
-
- Толығымен сөндірілген, іске қосу үшін Баптауларға өтіңіз.
-
Телеметрия
@@ -450,6 +452,8 @@
Маркетингтік мәліметтерLeanplum, біздің мобильді маркетинг өндірушісімен, %1$s ішінде қандай мүмкіндіктерді пайдаланғаныңыз туралы деректермен бөліседі.
+
+ Біздің мобильді маркетинг вендоры Adjust-пен негізгі пайдалану деректерін бөліседіЗерттеулер
@@ -595,7 +599,9 @@
Жекелік сессиясы
- Жекелік бетьер
+ Жекелік беттер
+
+ Синхрондалған беттерЖаңа бетті қосу
@@ -614,6 +620,8 @@
Барлық беттермен бөлісуЖуырда жабылған беттер
+
+ Тіркелгі баптауларыБет баптаулары
@@ -782,8 +790,6 @@
%1$s өшірілдіБуманы қосу
-
- Бетбелгі жасалды.Бетбелгі сақталды!
@@ -968,8 +974,6 @@
Барлық әрекеттерЖуырда қолданылған
-
- %1$s ретінде кіргенСинхрондау ішіне кіру
@@ -1444,10 +1448,8 @@
Логиндерді синхрондау
-
- Іске қосылған
-
- Сөндірілген
+
+ Логиндерді құрылғылар арасында синхрондауҚайта байланысу
@@ -1547,6 +1549,8 @@
Деректер шифрленгенКарталарды құрылғылар арасында синхрондау
+
+ Карталарды синхрондауНесиелік картаны қосу
@@ -1554,6 +1558,8 @@
Сақталған карталарды басқаруКартаны қосу
+
+ Картаны түзетуКарта нөмірі
@@ -1562,6 +1568,8 @@
Картадағы атыКартаның бүркеншік аты
+
+ Картаны өшіруКартаны өшіру
@@ -1574,6 +1582,9 @@
Сақталған карталар
+
+ Несиелік картаның жарамды нөмірін енгізіңіз
+
Іздеу жүйесін қосу
@@ -1732,23 +1743,19 @@
Бас тарту
+
+ Веб-сайттар, эл. пошта хаттары және хабарламалардан сілтемелерді Firefox-та автоматты түрде ашылатындай етіп баптау.
+
Өшіру
-
- %s өнімін толықтай пайдаланыңыз.
-
Көбірек білу үшін шертіңіз
-
- Өзіңізге маңызды заттарды жинаңыз
-
- Кейінірек жылдам қатынау үшін ұқсас іздеулер, сайттар және беттерді топтастырыңыз.
-
- Сіз осы телефондағы басқа Firefox браузерінде %s ретінде кірдіңіз. Осы тіркелгімен кіргіңіз келе ме?
-
- Бұл веб-сайтты жылдам қатынау және қолданба тектес режимде жылдам шолу мақсатымен телефоныңыздың үй бетіңізге қосуға болады.
+
+ Жоғары жылжу
+
+
+ Жабу
-
+
diff --git a/app/src/main/res/values-kmr/strings.xml b/app/src/main/res/values-kmr/strings.xml
index 10e034fd7..5cc849d06 100644
--- a/app/src/main/res/values-kmr/strings.xml
+++ b/app/src/main/res/values-kmr/strings.xml
@@ -19,10 +19,6 @@
Hilpekînên te yên vekirî ew ê li vir bên nîşandan.Hilpekînên te yên veşartî ew ê li vir bên nîşandan.
-
- Baidu
-
- JD1 hilpekîna vekirî. Ji bo hilpekînê biguherînî, bitikîne.
@@ -180,6 +176,13 @@
Di sepanê de vekeXuyang
+
+ Moda xwînerê taybet bike
+
+ Lê zêde bike
+
+ Serast bike
+
Nayê girêdan. Şemaya URLyê nayê nasîn.
@@ -218,6 +221,11 @@
Zêdetir bizane
+
+ Bi %s bigere
+
+ Lêgerînê rasterast ji darikê navnîşanan bike
+
Hilpekîna nû ya Firefoxê veke
@@ -603,12 +611,16 @@
Rûniştina veşartîHilpekînên veşartî
+
+ Hilpekînên senkronîzekirîHilpekînê tevlî bikeHilpekîna veşartî tevlî bikeVeşartî
+
+ SenkronîzekirinHilpekînên vekirî
@@ -619,6 +631,8 @@
Hemû hilpekînan parve bikeHilpekînên herî dawî hatine girtin
+
+ Sazkariyên hesabîSazkariyên hilpekînê
@@ -970,6 +984,8 @@
Hemû çalakîBikaranînên herî dawî
+
+ Ji bo senkronîzekirinê têkevêTêkeve Sync’ê
@@ -1165,9 +1181,6 @@
Tu bi xêr hatî %s’ê!Jixwe hesabekî te heye?
-
- %s’ê nas bikeTiştên nû bibîneBersiv li vir in
-
- Bi hesabê xwe yê Firefoxê dest bi senkronîzekirina favoriyan, pêborînan û zêdetirî wan, bike.
+
+ Firefoxê di navbera amûran de senkronîze bike
- Zêdetir bizane
+ Favorî, raborî û şîfreyên xwe bîne Firefoxa vê amûrê.
@@ -1187,8 +1200,8 @@
Erê, têkeveDikevê…
-
- Têkeve Firefox’ê
+
+ Xwe tomar bikeTêketinê neke
@@ -1196,26 +1209,23 @@
Têketin bi ser neket
- Nihêniya otomatîk
-
- Sazkariyên nihênî û ewlekariyê şopîneran û sepanên niyetxerab û şirketênku dixwazin te bişopînin asteng dike.
+ Her car -nepen
+
+ Firefox bixweber rê li şirketan digire ku bi dizî li dora te tevnegerin.Standard (jixweber)
- Hindiktir şopîneran asteng dike. Rûpel ew ê bi awayê normal vebin.
+ Nepenî û performansa bi aheng. Rûpel bi asayî tên barkirin.Tund (tê pêşniyarkirin)Tund
- Zêdetir şopîneran, reklaman û pencereyên vebûnok asteng dike. Rûpel zûtir vedibin, lê dibe ku hin taybetî nexebitin.
-
- Aliyê xwe hilbijêre
+ Ji bo rûpel zûtir bên vekirin zêdetir şopîneran asteng dike lê dibe ku hin fonksiyonên rûpelan xira bibin.
+
+ Ciyê darikê amûran hilbijêre
- Darikê amûran ê jêrîn bi destekî biceribîne an jî wê bibe jorê tu bi kêfa xwe yî.
+ Darikê amûran dayne ciyekê bi hêsanî bigihîjiyê. Dikarî wê li xwarê bihêlî yan jî bibî jorê.Bi nihênî bigereNihêniya te
+ The first parameter is the name of the app (e.g. Firefox Preview) Substitute %s for long browser name. -->
Me %s ji bo hindê çêkiriye ku derbarê tiştên tu li ser înternetê û tiştên bi me re parve dikî de kontrol di destê te de be.Agahdariya me ya nihêniyê bixwîne
@@ -1242,7 +1252,7 @@
Rûkara xwe hilbijêre
- Bi çalakkirina moda tarî him ji bataryayê teserûf bike him jî çavên xwe rehet bike.
+ Bi moda tarî dikarî ji bataryayê teseruf bikî û çavên xwe vehesînî.Otomatîk
@@ -1298,13 +1308,13 @@
Standard (heyî)
- Hindiktir şopîneran asteng dike. Rûpel ew ê bi awayê normal vebin.
+ Hevsengiya ji bo nepenî û performansê. Rûpel asayî tên barkirin.Bi parastina ji şopandinê ya standard çi tên astengkirin?Tund
- Zêdetir şopîneran, reklaman û pencereyên vebûnok asteng dike. Rûpel zûtir vedibin, lê dibe ku hin taybetî nexebitin.
+ Ji bo rûpel zûtir bên vekirin zêdetir şopîneran asteng dike lê dibe ku hin fonksiyonên rûpelan xira bibin.Bi parastina ji şopandinê ya tund çi tên astengkirin?
@@ -1353,6 +1363,8 @@
Skrîptên xirab yên ji bo ku kankerên krîpto ji bo çêkirina pereyên krîpto xwe bigihînin amûra te asteng dike.Şoptilî
+
+ Rê li berhevkirina zanyariyên derbarê amûra te de yên pêkan e biawayekî bêmînak bên pênasekirin digire ku pêkan e ji bo şopandinê bên bikaranîn.Naverokên şopandinê
@@ -1381,6 +1393,9 @@
Şoînerên beralîkirinê
+
+ Kûkiyên ji aliyê beralîkirinên malperên tên naskirin ve hatine bicîkirin paqij dike.
+
Piştgirî
@@ -1515,6 +1530,8 @@
ji bo hesabên xwe yên tomarkirî bibînî, kilîdê vekeHesab û pêborînên xwe biparêze
+
+ Deseneke kilîdê, PIN yan jî şîfreyekê diyar bike da ku gava amûra te bikeve destê kesên din têketin û şîfreyên te yên tomarkirî parastî bin.Piştre
@@ -1523,6 +1540,8 @@
Kilîda cîhaza xwe rakeHemû malperan nêzîk bike
+
+ Ji bo destûrdana jidandin û nêzîkkirinê çalak bike, di wan malperan de jî ku vê tevgeriyanê asteng dikin.Nav (A-Z)
@@ -1531,6 +1550,45 @@
Menûya rêzkirina hesaban
+
+
+ Kartên krediyê
+
+ Kartên tomarkirî û xweber tên dagirtin
+
+ Dane şîfrekirî ne
+
+
+ Kartan di navbera amûran de senkronîze bike
+
+ Kredîkartekê lê zêde bike
+
+ Kartên tomarkirî bi rê ve bibe
+
+ Kartê lê zêde bike
+
+ Kartê serast bike
+
+ Hejmara kartê
+
+ Dema dawî ya bikaranînê
+
+ Navê li ser kartê
+
+ Navê kartê
+
+ Kartê jê bibe
+
+ Kartê jê bibe
+
+ Tomar bike
+
+ Tomar bike
+
+ Betal
+
+ Kartên tomarkirî
+
Motora lêgerînê tevlî bike
@@ -1688,6 +1746,9 @@
Betal bike
+
+ Bila lînkên ji malperan, e-posteyan û peyaman xweber di Firefoxê de bên vekirin.
+
Rake
@@ -1704,4 +1765,6 @@
Lêgerîn, malper û hilpekînên wekhev, bike kom da ku paşê tu karibî xwe zû bigihînî wan.Tu li ser vê telefonê di gerokeke din a Firefoxê de wekî %s têketî yê. Tu dixwazî bi vî hesabî têkevî?
-
+
+ Tu dikarî vê malperê bi hêsanî li ekrana Malê ya telefona xwe zêde bikî û dikarî vê malperê bilez û wekî ku bernamokek be bi kar bînî.
+
diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml
index c253bb28c..27b190017 100644
--- a/app/src/main/res/values-ko/strings.xml
+++ b/app/src/main/res/values-ko/strings.xml
@@ -424,6 +424,11 @@
and the third is the device model. -->
%2$s %3$s의 %1$s
+
+ 신용 카드
+
+ 주소
+
받은 탭
@@ -458,19 +463,18 @@
더 알아보기
-
- 꺼져 있습니다. 켜려면 설정에서 변경하세요.
-
데이터 수집
- 사용량과 기술 데이터
+ 사용 현황 및 기술 데이터
- %1$s의 개선을 위해 브라우저의 성능, 사용량, 하드웨어 및 설정 데이터를 Mozilla와 공유
+ %1$s의 개선을 위해 브라우저의 성능, 사용 현황, 하드웨어 및 설정 데이터를 Mozilla와 공유마케팅 데이터%1$s에서 사용하는 기능에 대한 데이터를 모바일 마케팅 공급업체인 Leanplum과 공유합니다.
+
+ 모바일 마케팅 공급업체인 Adjust와 기본 사용 현황 데이터를 공유연구
@@ -639,6 +643,8 @@
모든 탭 공유최근에 닫은 탭
+
+ 계정 설정탭 설정
@@ -812,8 +818,6 @@
%1$s 삭제됨폴더 추가
-
- 북마크 생성됨.북마크가 저장되었습니다!
@@ -1015,8 +1019,6 @@
모든 동작최근 사용
-
- %1$s(으)로 로그인됨Sync에 로그인
@@ -1418,7 +1420,7 @@
이 사이트에서 보호 꺼짐
- 이 웹 사이트에 대해 향상된 추적 방지 기능이 꺼져 있습니다
+ 이 웹 사이트에 향상된 추적 방지 기능이 꺼짐뒤로
@@ -1499,10 +1501,8 @@
자동 채우기Sync 로그인
-
- 켜짐
-
- 꺼짐
+
+ 기기 간에 로그인 동기화다시 연결
@@ -1603,6 +1603,8 @@
데이터가 암호화됨기기 간에 카드 동기화
+
+ 카드 동기화신용 카드 추가
@@ -1610,6 +1612,8 @@
저장된 카드 관리카드 추가
+
+ 카드 편집카드 번호
@@ -1619,6 +1623,8 @@
카드상의 이름카드 별명
+
+ 카드 삭제카드 삭제
@@ -1631,6 +1637,9 @@
저장된 카드
+
+ 유효한 신용 카드 번호를 입력해 주세요
+
검색 엔진 추가
@@ -1793,23 +1802,19 @@
취소
+
+ Firefox에서 자동으로 열리도록 웹 사이트, 이메일 및 메시지의 링크를 설정합니다.
+
삭제
-
- %s를 최대한 활용해 보세요.
-
상세 정보
-
- 중요한 것들 수집하기
-
- 나중에 빠르게 접근할 수 있도록 유사한 검색, 사이트 및 탭을 모아 보세요.
-
- 이 휴대폰의 다른 Firefox 브라우저에서 %s(으)로 로그인했습니다. 이 계정으로 로그인하시겠습니까?
-
- 이 웹 사이트를 휴대폰의 홈 화면에 쉽게 추가하여 앱과 같은 경험을 통해 즉시 액세스하고 더 빠르게 탐색 할 수 있습니다.
+
+ 위로 이동
+
+
+ 닫기
-
+
diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml
index 1d05457a5..4a0db67f6 100644
--- a/app/src/main/res/values-lt/strings.xml
+++ b/app/src/main/res/values-lt/strings.xml
@@ -442,9 +442,6 @@
Sužinoti daugiau
-
- Išjungta globaliai, eikite į „Nuostatas“, norėdami įjungti.
-
Telemetrija
@@ -456,6 +453,8 @@
Rinkodaros duomenysDuomenys apie jūsų naudojamas „%1$s“ funkcijas bus perduodami „Leanplum“, mūsų mobiliosios rinkodaros pardavėjui.
+
+ Pagrindiniai naudojimosi duomenys bus perduodami „Adjust“, mūsų mobiliosios rinkodaros pardavėjuiTyrimai
@@ -605,6 +604,8 @@
Privatusis seansasPrivačios kortelės
+
+ Sinchronizuotos kortelėsPridėti kortelę
@@ -623,6 +624,8 @@
Dalintis visomis kortelėmisPaskiausiai užvertos kortelės
+
+ Paskyros nuostatosKortelių nustatymai
@@ -794,8 +797,6 @@
Pašalintas „%1$s“Įtraukti aplanką
-
- Adresyno įrašas sukurtas.Adresyno įrašas išsaugotas!
@@ -981,8 +982,6 @@
Visi veiksmaiPaskiausiai naudota
-
- Prisijungėte kaip %1$sPrisijungti sinchronizavimui
@@ -1573,6 +1572,8 @@
Tvarkyti įrašytas kortelesPridėti kortelę
+
+ Redaguoti kortelęKortelės numeris
@@ -1581,6 +1582,8 @@
Vardas ant kortelėsKortelės pavadinimas
+
+ Pašalinti kortelęPašalinti kortelę
@@ -1755,23 +1758,13 @@
Atsisakyti
+
+ Leiskite saitams iš svetainių, el. laiškų, ir žinučių būti automatiškai atvertiems per „Firefox“.
+
Pašalinti
-
- išnaudokite daugiau „%s“ galimybių.
-
Spustelėkite čia, jeigu norite daugiau informacijos
-
- Kaupkite jums svarbius dalykus
-
- Sugrupuokite panašias paieškas, svetaines, ir korteles patogesniam pasiekimui.
-
- Esate prisijungę kaip %s kitoje „Firefox“ naršyklėje šiame įrenginyje. Ar norėtumėte prisijungti su šia paskyra?
-
- Galite lengvai pridėti šią svetainę į savo telefono pradžios ekraną, kad turėtumėte greitą priėjimą ir naršytumėte sparčiau, tarsi naudodamiesi programa.
-
diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml
index 977bdd219..2eba8af50 100644
--- a/app/src/main/res/values-nb-rNO/strings.xml
+++ b/app/src/main/res/values-nb-rNO/strings.xml
@@ -416,6 +416,11 @@
and the third is the device model. -->
%1$s på %2$s %3$s
+
+ Betalingskort
+
+ Adresser
+
Mottatte faner
@@ -447,9 +452,6 @@
Les mer
-
- Slått av globalt, gå til Innstillinger for å slå den på.
-
Telemetri
@@ -461,6 +463,8 @@
MarkedsføringsdataDeler data om hvilke funksjoner du bruker i %1$s med Leanplum, vår mobile markedsføringsleverandør.
+
+ Deler grunnleggende bruksdata med Adjust, vår mobile markedsføringsleverandørUndersøkelse
@@ -611,6 +615,8 @@
Privat øktPrivate faner
+
+ Synkroniserte fanerLegg til fane
@@ -630,6 +636,8 @@
Del alle fanerNylig lukkede faner
+
+ KontoinnstillingerFane-innstillinger
@@ -798,8 +806,6 @@
Slettet %1$sLegg til mappe
-
- Bokmerke opprettet.Bokmerke lagret!
@@ -987,8 +993,6 @@
Alle handlingerNylig brukt
-
- Logget inn som %1$sLogg inn for å synkronisere
@@ -1213,16 +1217,26 @@
Kunne ikke logge inn
+
+ Alltid med personvernFirefox stopper selskaper automatisk fra å spore aktivitetene dine på nettet det skjulte.Standard (standard)
+
+ Balansert mellom personvern og ytelse. Sider lastes normalt.Streng (anbefales)Streng
+
+ Blokkerer flere sporings-mekanismer. Sider lastes inn raskere, men enkelte sider fungerer kanskje ikke.
+
+ Velg plassering for verktøylinjen
+
+ Plassere verktøylinjen innenfor rekkevidde. Ha den på bunn eller flytt den til toppen.Surf privat
@@ -1251,6 +1265,8 @@
Velg tema
+
+ Spar litt batteri og dine øyne med mørk modus.Automatisk
@@ -1305,11 +1321,15 @@
Les merStandard (standard)
+
+ Balansert mellom personvern og ytelse. Sider lastes normalt.Hva er blokkert av standard sporingsbeskyttelseStreng
+
+ Blokkerer flere sporings-mekanismer. Sider lastes inn raskere, men enkelte sider fungerer kanskje ikke.Hva er blokkert av streng sporingsbeskyttelse
@@ -1452,10 +1472,8 @@
AutofyllSynkroniser innlogginger
-
- På
-
- Av
+
+ Synkroniser innlogginger på tvers av enheterKoble til på nytt
@@ -1560,6 +1578,8 @@
Data er kryptertSynkroniser kort på tvers av enheter
+
+ Synkroniser kortLegg til betalingskort
@@ -1567,6 +1587,8 @@
Behandle lagrede kortLegg til kort
+
+ Rediger kortKortnummer
@@ -1575,6 +1597,8 @@
Navn på kortKallenavn på kort
+
+ Slett kortSlett kort
@@ -1586,6 +1610,9 @@
Lagrede kort
+
+ Oppgi et gyldig betalingskortnummer
+
Legg til søkemotor
@@ -1746,23 +1773,19 @@
Avbryt
+
+ Angi at lenker fra nettsteder, e-postmeldinger og meldinger skal åpnes automatisk i Firefox.
+
Fjern
-
- Få mest mulig ut av %s.
-
Trykk her for mer informasjon
-
- Samle tingene som betyr noe for deg
-
- Grupper sammen lignende søk, nettsteder og faner for rask tilgang senere.
-
- Du er logget inn som %s på en annen Firefox-nettleser på denne telefonen. Vil du logge inn med denne kontoen?
-
- Du kan enkelt legge til dette nettstedet på telefonens startskjerm for å få øyeblikkelig tilgang og surfe raskere med en app-lignende opplevelse.
+
+ Naviger opp
+
+
+ Lukk
-
+
diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml
index be4f0f66a..c4cc6a4ae 100644
--- a/app/src/main/res/values-night/colors.xml
+++ b/app/src/main/res/values-night/colors.xml
@@ -53,6 +53,7 @@
@color/accent_high_contrast_dark_theme@color/preference_section_header_dark_theme@color/accent_high_contrast_dark_theme
+ @color/accent_high_contrast_dark_theme@color/mozac_widget_favicon_background_dark_theme@color/mozac_widget_favicon_border_dark_theme
@@ -79,7 +80,8 @@
@color/top_site_title_text_dark_theme#3A3944#5B5B66
- @color/violet_50
+ @color/photonViolet50
+ @color/dark_grey_90@color/synced_tabs_separator_dark_theme
diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml
index e43230a2d..87b3f33ef 100644
--- a/app/src/main/res/values-nl/strings.xml
+++ b/app/src/main/res/values-nl/strings.xml
@@ -420,6 +420,11 @@
and the third is the device model. -->
%1$s op %2$s %3$s
+
+ Creditcards
+
+ Adressen
+
Ontvangen tabbladen
@@ -450,9 +455,6 @@
Meer info
-
- Globaal uitgeschakeld, ga naar Instellingen om in te schakelen.
-
Telemetry
@@ -463,6 +465,8 @@
MarketinggegevensDeelt gegevens over welke functies u in %1$s gebruikt met Leanplum, onze leverancier van mobiele marketing.
+
+ Deelt basale gebruiksgegevens met Adjust, onze leverancier van mobiele marketingOnderzoeken
@@ -632,6 +636,8 @@
Alle tabbladen delenOnlangs gesloten tabbladen
+
+ AccountinstellingenTabbladinstellingen
@@ -801,8 +807,6 @@
%1$s verwijderdMap toevoegen
-
- Bladwijzer gemaakt.Bladwijzer opgeslagen!
@@ -987,8 +991,6 @@
Alle actiesOnlangs gebruikt
-
- Aangemeld als %1$sAanmelden om te synchroniseren
@@ -1461,10 +1463,8 @@
Automatisch invullenAanmeldingen synchroniseren
-
- Aan
-
- Uit
+
+ Aanmeldingen op apparaten synchroniserenOpnieuw verbinden
@@ -1564,6 +1564,8 @@
Gegevens zijn versleuteldKaarten synchroniseren tussen apparaten
+
+ Kaarten synchroniserenCreditcard toevoegen
@@ -1571,6 +1573,8 @@
Opgeslagen kaarten beherenKaart toevoegen
+
+ Kaart bewerkenKaartnummer
@@ -1579,6 +1583,8 @@
Naam op kaartKaart-nickname
+
+ Kaart verwijderenKaart verwijderen
@@ -1591,6 +1597,9 @@
Opgeslagen kaarten
+
+ Voer een geldig creditcardnummer in
+
Zoekmachine toevoegen
@@ -1750,23 +1759,19 @@
Annuleren
+
+ Koppelingen van websites, e-mail en berichten automatisch in Firefox openen.
+
Verwijderen
-
- Haal het meeste uit %s.
-
Klik voor meer details
-
- Verzamel de dingen die belangrijk voor u zijn
-
- Groepeer vergelijkbare zoekopdrachten, websites en tabbladen voor snelle toegang later.
-
- U bent op een andere Firefox-browser op deze telefoon aangemeld als %s. Wilt u zich aanmelden met deze account?
-
- U kunt deze website eenvoudig aan het startscherm van uw telefoon toevoegen voor directe toegang en sneller surfen met een app-achtige ervaring.
+
+ Omhoog
+
+
+ Sluiten
-
+
diff --git a/app/src/main/res/values-nn-rNO/strings.xml b/app/src/main/res/values-nn-rNO/strings.xml
index 418d11f45..be6fc13ca 100644
--- a/app/src/main/res/values-nn-rNO/strings.xml
+++ b/app/src/main/res/values-nn-rNO/strings.xml
@@ -416,6 +416,11 @@
and the third is the device model. -->
%1$s på %2$s %3$s
+
+ Betalingskort
+
+ Adresser
+
Mottekne faner
@@ -447,9 +452,6 @@
Les meir
-
- Slått av globalt, gå til Innstillingar for å slå på vern.
-
Telemetri
@@ -460,6 +462,8 @@
MarknadsføringsdataDeler data om kva for funksjonar du brukar i %1$s med Leanplum, den mobile marknadsføringsleverandøren vår.
+
+ Deler grunnleggjande bruksdata med Adjust, leverandøren vår av mobil marknadsføringundersøking
@@ -517,7 +521,7 @@
Trekk for å oppdatere
- Bla for å skjule verktøylinja
+ Bla for å gøyme verktøylinjaSveip verktøylinja sidelengs for å byte fane
@@ -611,6 +615,8 @@
Privat øktPrivate faner
+
+ Synkroniserte fanerLegg til fane
@@ -629,6 +635,8 @@
Del alle fanerNyleg attlatne faner
+
+ KontoinnstillingarFane-innstillinger
@@ -801,8 +809,6 @@
Sletta %1$sLegg til mappe
-
- Bokmerke laga.Bokmerke lagra!
@@ -989,8 +995,6 @@
Alle handlingarNyleg brukt
-
- Logga inn som %1$sLogg inn for å synkronisere
@@ -1465,10 +1469,8 @@
AutofyllSynkroniser innloggingar
-
- På
-
- Av
+
+ Synkroniser innloggingar på tvers av einingarKople til på nytt
@@ -1569,6 +1571,8 @@
Data er kryptertSynkroniser kort på tvers av einingar
+
+ Synkroniser kortLegg til betalingskort
@@ -1576,6 +1580,8 @@
Handter lagra kortLegg til betalingskort
+
+ Rediger kortKortnummer
@@ -1584,6 +1590,8 @@
Namn på kortKallenamn på kort
+
+ Slett kortSlett kort
@@ -1596,6 +1604,9 @@
Lagra bankkort
+
+ Skriv inn eit gyldig betalingskortnummer
+
Legg til søkjemotor
@@ -1758,23 +1769,19 @@
Avbryt
+
+ Vel at lenker frå nettstadar, e-postmeldingar og meldingar skal opnast automatisk i Firefox.
+
Fjern
-
- Få mest muleg ut av %s.
-
Trykk her for meir informasjon
-
- Samle dei tinga som betyr noko for deg
-
- Grupper saman liknande søk, nettstadar og faner for rask tilgang seinare.
-
- Du er logga inn som %s på ein annan Firefox-nettlesar på denne telefonen. Vil du logge inn med denne kontoen?
-
- Du kan enkelt leggje til denne nettstaden på startskjermen til telefonen for å få direkte tilgang, og surfe raskare med ei app-liknande oppleving.
+
+ Naviger opp
+
+
+ Lat att
-
+
diff --git a/app/src/main/res/values-oc/strings.xml b/app/src/main/res/values-oc/strings.xml
index bd2351469..3b881459b 100644
--- a/app/src/main/res/values-oc/strings.xml
+++ b/app/src/main/res/values-oc/strings.xml
@@ -416,6 +416,11 @@
and the third is the device model. -->
%1$s sus %2$s %3$s
+
+ Cartas de crèdit
+
+ Adreças
+
Onglets recebuts
@@ -446,9 +451,6 @@
Ne saber mai
-
- Desactivada globalament, anatz als Paramètres per l’activar.
-
Telemetria
@@ -459,6 +461,8 @@
Donadas marketingParteja de donadas sus las foncionalitats qu’utilizatz amb Leanplum, nòstre provesidor de marketing mobil.
+
+ Parteja de donadas basicas d’utilizacion amb Adjust, nòstre prestatari de marketing mobilEstudis
@@ -609,6 +613,8 @@
Session privadaOnglets privats
+
+ Onglets sincronizatsApondre un onglet
@@ -627,6 +633,8 @@
Partejar totes los ongletsOnglets tampats recentament
+
+ Paramètres del compteParamètres dels onglets
@@ -797,8 +805,6 @@
%1$s suprimitApondre un dossièr
-
- Marcapagina creat.Marcapagina apondut !
@@ -988,8 +994,6 @@
Totas las accionsUtilizats fa res
-
- Identificat coma %1$sSe connectar per sincronizar
@@ -1159,17 +1163,29 @@
Firefox Preview ara es Firefox Nightly
+
+
+ Firefox Nightly es actualizat cada nuèch e prepausa de foncionalitats novèlas experimentalas.
+ Pasmens es mens estable. Telecargatz la version beta de nòstre navegador per una experiéncia mai estabala.Installar Firefox per Android BetaFirefox Nightly a mudat los catons
+
+
+ Aquesta aplicacion aurà pas mai de mesa a jorn de seguretat. Deuriatz quitar de l’utilizar e utilizar puslèu lo Nightly novèl a la plaça.
+ \n\nPer transferir vòstres marcapaginas, vòstres identificants e vòstre istoric cap a una autra aplicacion, creatz un compte Firefox.Passar la Nightly nòuFirefox Nightly a mudat los catons
+
+
+ Aquesta aplicacion aurà pas mai de mesa a jorn de seguretat. Deuriatz quitar de l’utilizar e utilizar puslèu lo Nightly novèl a la plaça..
+ \n\nPer transferir vòstres marcapaginas, vòstres identificants e vòstre istoric cap a una autra aplicacion, creatz un compte Firefox.Obténer lo novèl Nightly
@@ -1186,6 +1202,10 @@
Avètz de question tocant lo nòu %s ? Volètz saber çò que cambièt ?Trobatz de responsas aquí
+
+ Sincronizar Firefox entre vòstres aparelhs
+
+ Importatz vòstres marcapaginas, vòstre istoric e vòstres senhals dins Firefox sus aqueste aparelh.
@@ -1194,18 +1214,32 @@
Òc, connectatz-meConnexion…
+
+ Se connectarDemorar desconnectatSincro. activadaFracàs de connexion
+
+ Confidencialitat totjorn renfortida
+
+ Firefox empacha automaticament las entrepresas de vos pistar secrètament pel web.Estandard (per defaut)
+
+ Equilibri entre confidencialitat e performança. Las pagina cargaràn normalament.Estricte (recomandat)Estricte
+
+ Bloca mai de traçadors, accelèra la cargament de las paginas mas d’unas pòdon quitar de foncionar.
+
+ Causissètz ont conhar la barra d’aisinas
+
+ Plaçatz la barra d’aisinas a portada de man. Daissatz-la enbàs o ennaut.Navegatz d’un biais privatCausissètz vòstre tèma
+
+ Estalviatz la batariá e vòstra vista en activant lo mòde fosc.Automatic
@@ -1285,10 +1321,14 @@
Ne saber maiEstandarda (per defaut)
+
+ Equilibri entre confidencialitat e performança. Las pagina cargaràn normalament.Çò que la proteccion contra lo seguiment estandarda blocaEstricte
+
+ Bloca mai de traçadors, accelèra la cargament de las paginas mas d’unas pòdon quitar de foncionar.Çò que la proteccion contra lo seguiment estricta bloca
@@ -1436,10 +1476,8 @@
Emplenament automaticSincronizar los identificants
-
- Activat
-
- Desactivat
+
+ Sincronizar los identificants entre totes los perifericsSe reconnectar
@@ -1540,6 +1578,8 @@
Donadas chifradasSincronizar las cartas entre los periferics
+
+ Sincronizar las cartasApondre una cartas de crèdit
@@ -1547,6 +1587,8 @@
Gerir las cartas enregistradasApondre una carta
+
+ Modificar la cartaNumèro de carta
@@ -1555,6 +1597,8 @@
TitularNom de la carta
+
+ Suprimir la cartaSuprimir la carta
@@ -1567,6 +1611,9 @@
Cartas enregistradas
+
+ Picatz un numèro de carta de crèdit valid
+
Apondre un motor de recèrca
@@ -1727,22 +1774,16 @@
Anullar
+
+ Causir de dobrir los sites web, los corrièls e messatges automaticament dins Firefox.
+
Suprimir
-
- Ne fasètz mai amb %s.
-
Clicatz per aver de detalhs
-
- Amassatz çò que compta per vos
-
- Agropatz de recèrcas, de sites e d’onglets similaris per i accedir rapidament mai tard.
-
- Sètz connectat coma %s sus un autre navegador Firefox sus aqueste telefòn. Volètz vos connectar amb aqueste compte ?
-
- Podètz facilament apondre aqueste site a vòstre ecran d’acuèlh de telefòn per i accedir e navigar mai rapidament coma se foguèssetz dins l’aplicacion.
+
+ Tampar
+
diff --git a/app/src/main/res/values-pa-rIN/strings.xml b/app/src/main/res/values-pa-rIN/strings.xml
index e73a5effd..1fd1bcb57 100644
--- a/app/src/main/res/values-pa-rIN/strings.xml
+++ b/app/src/main/res/values-pa-rIN/strings.xml
@@ -423,6 +423,11 @@
and the third is the device model. -->
%2$s %3$s ਉੱਤੇ %1$s
+
+ ਕਰੈਡਿਟ ਕਾਰਡ
+
+ ਸਿਰਨਾਵੇਂ
+
ਮਿਲੀਆਂ ਟੈਬਾਂ
@@ -455,9 +460,6 @@
ਹੋਰ ਸਿੱਖੋ
-
- ਪੂਰੇ ਤੌਰ ‘ਤੇ ਬੰਦ ਹੈ, ਇਸ ਨੂੰ ਚਾਲੂ ਕਰਨ ਲਈ ਸੈਟਿੰਗਾਂ ‘ਤੇ ਜਾਓ।
-
ਟੈਲੀਮੈਟਰੀ
@@ -468,6 +470,8 @@
ਮਾਰਕੀਟਿੰਗ ਡਾਟਾ%1$s ਵਿੱਚ ਕਿਹੜੇ ਫੀਚਰ ਤੁਸੀਂ ਵਰਤਦੇ ਹੋ, ਬਾਰੇ ਡਾਟੇ ਨੂੰ Leanplum, ਸਾਡੇ ਮਾਰਕੀਟਿੰਗ ਵੇਂਡਰ ਨਾਲ ਸਾਂਝਾ ਕਰਦਾ ਹੈ।
+
+ ਕੁਝ ਅਨੁਕੂਲ ਬਣਾ ਕੇ ਮੁੱਢਲਾ ਵਰਤੋਂ ਡਾਟਾ ਸਾਡੇ ਮੋਬਾਈਲ ਮਾਰਕੀਟਿੰਗ ਵੇਂਡਰ ਨਾਲ ਸਾਂਝਾ ਕਰਦਾ ਹੈਅਧਿਐਨ
@@ -619,6 +623,8 @@
ਪ੍ਰਾਈਵੇਟ ਸ਼ੈਸ਼ਨਪ੍ਰਾਈਵੇਟ ਟੈਬਾਂ
+
+ ਸਿੰਕ ਕੀਤੀਆਂ ਟੈਬਾਂਟੈਬ ਜੋੜੋ
@@ -637,6 +643,8 @@
ਸਾਰੀਆਂ ਟੈਬਾਂ ਸਾਂਝੀਆਂ ਕਰੋਤਾਜ਼ਾ ਬੰਦ ਕੀਤੀਆਂ ਟੈਬਾਂ
+
+ ਖਾਤਾ ਸੈਟਿੰਗਾਂਟੈਬ ਸੈਟਿੰਗਾਂ
@@ -807,8 +815,6 @@
%1$s ਨੂੰ ਹਟਾਇਆਫੋਲਡਰ ਜੋੜੋ
-
- ਬੁੱਕਮਾਰਕ ਬਣਾਇਆ।ਬੁੱਕਮਾਰਕ ਸੰਭਾਲਿਆ!
@@ -994,8 +1000,6 @@
ਸਾਰੀਆਂ ਕਾਰਵਾਈਆਂਤਾਜ਼ਾ ਵਰਤੇ
-
- %1$s ਵਜੋਂ ਸਾਈਨ ਇਨ ਕੀਤਾਸਿੰਕ ਕਰਨ ਲਈ ਸਾਈਨ ਇਨ ਕਰੋ
@@ -1471,10 +1475,8 @@
ਆਪੇ-ਭਰੋਲਾਗਇਨ ਸਿੰਕ ਕਰੋ
-
- ਚਾਲੂ
-
- ਬੰਦ
+
+ ਡਿਵਾਈਸਾਂ ਵਿਚਾਲੇ ਲਾਗਇਨਾਂ ਨੂੰ ਸਿੰਕ ਕਰੋਮੁੜ-ਕਨੈਕਟ ਕਰੋ
@@ -1575,12 +1577,16 @@
ਡਾਟਾ ਇੰਕ੍ਰਿਪਟ ਕੀਤਾ ਹੈਡਿਵਾਈਸਾਂ ਵਿਚਾਲੇ ਕਾਰਡਾਂ ਨੂੰ ਸਿੰਕ ਕਰੋ
+
+ ਕਾਰਡ ਸਿੰਕ ਕਰੋਕਰੈਡਿਟ ਕਾਰਡ ਜੋੜੋਸੰਭਾਲੇ ਹੋਏ ਕਾਰਡਾਂ ਦਾ ਇੰਤਜ਼ਾਮ ਕਰੋਕਾਰਡ ਜੋੜੋ
+
+ ਕਾਰਡ ਨੂੰ ਸੋਧੋਕਾਰਡ ਦਾ ਨੰਬਰ
@@ -1589,6 +1595,8 @@
ਕਾਰਡ ਉੱਤੇ ਨਾਂਕਾਰਡ ਦਾ ਆਮ ਨਾਂ
+
+ ਕਾਰਡ ਹਟਾਓਕਾਰਡ ਨੂੰ ਹਟਾਓ
@@ -1601,6 +1609,9 @@
ਸੰਭਾਲੇ ਹੋਏ ਕਾਰਡ
+
+ ਵਾਜਬ ਕਰੈਡਿਟ ਕਾਰਡ ਨੰਬਰ ਦਿਓ ਜੀ
+
ਖੋਜ ਇੰਜਣ ਜੋੜੋ
@@ -1759,23 +1770,19 @@
ਰੱਦ ਕਰੋ
+
+ ਵੈੱਬਸਾਈਟਾਂ, ਈਮੇਲਾਂ ਅਤੇ ਸੁਨੇਹਿਆਂ ਨੂੰ Firefox ਵਿੱਚ ਆਪਣੇ ਖੋਲ੍ਹਣ ਲਈ ਲਿੰਕ ਸੈੱਟ ਕਰੋ।
+
ਹਟਾਓ
-
- %s ਤੋਂ ਪੂਰਾ ਪੂਰਾ ਫਾਇਦਾ ਲਵੋ।
-
ਹੋਰ ਵੇਰਵਿਆਂ ਲਈ ਕਲਿੱਕ ਕਰੋ
-
- ਚੀਜ਼ਾਂ ਇਕੱਤਰ ਕਰੋ, ਜੋ ਤੁਹਾਡੇ ਲਈ ਜ਼ਰੂਰੀ ਹਨ
-
- ਬਾਅਦ ਵਿੱਚ ਫ਼ੌਰੀ ਵਰਤਣ ਲਈ ਰਲਦੀਆਂ ਖੋਜਾਂ, ਸਾਈਤਾਂ ਤੇ ਟੈਬਾਂ ਨੂੰ ਗਰੁੱਪ ਵਿੱਚ ਇਕੱਠੀਆਂ ਕਰੋ।
-
- ਤੁਸੀਂ ਇਸ ਫ਼ੋਨ ‘ਤੇ ਹੋਰ ਫਾਇਰਫਾਕਸ ਬਰਾਊਜ਼ਰ ਉੱਤੇ %s ਵਜੋਂ ਸਾਈਨ ਇਨ ਹੋ। ਕੀ ਤੁਸੀਂ ਇਸ ਖਾਤੇ ਨਾਲ ਸਾਈਨ ਇਨ ਕਰਨਾ ਚਾਹੁੰਦੇ ਹੋ?
-
- ਤੁਸੀਂ ਇਸ ਵੈੱਬਸਾਈਟ ਨੂੰ ਸੌਖੀ ਤਰ੍ਹਾਂ ਆਪਣੀ ਫ਼ੋਨ ਦੀ ਮੁੱਖ ਸਕਰੀਨ ‘ਤੇ ਜੋੜ ਸਕਦੇ ਹੋ ਤਾਂ ਕਿ ਐਪ ਵਰਗੇ ਤਜਰਬੇ ਵਾਸਤੇ ਤੁਰੰਤ ਪਹੁੰਚ ਅਤੇ ਤੇਜ਼ੀ ਨਾਲ ਬਰਾਊਜ਼ ਕਰਨ ਲਈ ਵਰਤਿਆ ਜਾ ਸਕੇ।
+
+ ਉੱਤੇ ਜਾਓ
+
+
+ ਬੰਦ ਕਰੋ
-
+
diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml
index 37e351367..8ba0a1f4f 100644
--- a/app/src/main/res/values-pl/strings.xml
+++ b/app/src/main/res/values-pl/strings.xml
@@ -419,6 +419,11 @@
and the third is the device model. -->
%1$s na urządzeniu %2$s %3$s
+
+ Karty płatnicze
+
+ Adresy
+
Odebrane karty
@@ -449,9 +454,6 @@
Więcej informacji
-
- Całkowicie wyłączona, otwórz ustawienia, aby ją włączyć.
-
Telemetria
@@ -462,6 +464,8 @@
Dane marketingoweUdostępnia dane o funkcjach przeglądarki %1$s używanych przez użytkownika firmie Leanplum, naszemu dostawcy marketingu mobilnego.
+
+ Udostępnia podstawowe dane o użyciu firmie Adjust, naszemu dostawcy marketingu mobilnego.Badania
@@ -609,6 +613,8 @@
Prywatna sesjaPrywatne karty
+
+ Karty z innych urządzeńOtwórz nową kartę
@@ -627,6 +633,8 @@
Udostępnij wszystkie kartyOstatnio zamknięte karty
+
+ Ustawienia kontaUstawienia kart
@@ -797,8 +805,6 @@
Usunięto folder „%1$s”Dodaj folder
-
- Utworzono zakładkęDodano zakładkę
@@ -983,8 +989,6 @@
Wszystkie działaniaOstatnio używane
-
- Zalogowano jako %1$sZaloguj się do synchronizacji
@@ -1457,10 +1461,8 @@
Automatyczne wypełnianieSynchronizowanie danych logowania
-
- Włączone
-
- Wyłączone
+
+ Synchronizuj karty między urządzeniamiPołącz ponownie
@@ -1561,6 +1563,8 @@
Dane są zaszyfrowaneSynchronizuj karty między urządzeniami
+
+ Synchronizuj kartyDodaj kartę płatniczą
@@ -1568,6 +1572,8 @@
Zarządzaj zachowanymi kartamiDodaj kartę
+
+ Edytuj kartęNumer karty
@@ -1576,6 +1582,8 @@
Imię i nazwiskoPseudonim karty
+
+ Usuń kartęUsuń kartę
@@ -1587,6 +1595,9 @@
Zachowane karty
+
+ Wprowadź prawidłowy numer karty płatniczej
+
Dodaj wyszukiwarkę
@@ -1714,9 +1725,9 @@
Połącz inne urządzenie.
- Proszę uwierzytelnić się ponownie.
+ Uwierzytelnij się ponownie.
- Proszę włączyć synchronizację kart.
+ Włącz synchronizację kart.Brak otwartych kart w Firefoksach na innych urządzeniach.
@@ -1748,23 +1759,19 @@
Anuluj
+
+ Ustaw automatyczne otwieranie odnośników z witryn, wiadomości e-mail i SMS-ów w Firefoksie.
+
Usuń
-
- Zacznij synchronizować zakładki, hasła i nie tylko za pomocą konta Firefoksa.
-
Kliknij, aby wyświetlić więcej informacji
-
- Zbierz rzeczy, które są dla Ciebie ważne
-
- Połącz ze sobą podobne wyszukiwania, witryny i karty, aby szybciej do nich wracać.
-
- Zalogowano jako %s w innej przeglądarce Firefox na tym telefonie. Czy chcesz się zalogować na tym koncie?
-
- Można łatwo dodać tę stronę do ekranu głównego telefonu, aby mieć do niej bezpośredni dostęp i używać jej jak aplikację.
+
+ Przejdź w górę
+
+
+ Zamknij
-
+
diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml
index a1e97b04f..28e7c2abe 100644
--- a/app/src/main/res/values-pt-rBR/strings.xml
+++ b/app/src/main/res/values-pt-rBR/strings.xml
@@ -413,6 +413,11 @@
and the third is the device model. -->
%1$s no %2$s %3$s
+
+ Cartões de crédito
+
+ Endereços
+
Abas recebidas
@@ -444,9 +449,6 @@
Saiba mais
-
- Desativada globalmente, vá em Configurações para ativar.
-
Telemetria
@@ -457,6 +459,8 @@
Dados de marketingCompartilhar dados sobre que recursos você usa no %1$s com o Leanplum, nosso fornecedor de marketing para celular.
+
+ Compartilhar dados básicos de uso com a Adjust, nossa fornecedora de marketing em dispositivos móveisEstudos
@@ -475,7 +479,7 @@
- Ativar o Sync
+ Ativar a sincronizaçãoDigitalizar código de pareamento no Firefox de computador
@@ -529,7 +533,7 @@
Favoritos
- Favoritos do computador
+ Favoritos no computadorMenu de favoritos
@@ -582,20 +586,20 @@
Manualmente
- Após um dia
+ Após um dia sem uso
- Após uma semana
+ Após uma semana sem uso
- Após um mês
+ Após um mês sem usoFechar manualmente
- Fechar após um dia
+ Fechar após um dia sem uso
- Fechar após uma semana
+ Fechar após uma semana sem uso
- Fechar após um mês
+ Fechar após um mês sem uso
@@ -624,6 +628,8 @@
Compartilhar todas as abasAbas fechadas recentemente
+
+ Configurações de contasConfigurações de abas
@@ -792,8 +798,6 @@
%1$s excluídaAdicionar pasta
-
- Favorito criado.Favorito salvo!
@@ -979,8 +983,6 @@
Todas as açõesUsado recentemente
-
- Conectado como %1$sEntrar para sincronizar
@@ -1118,7 +1120,7 @@
Permissões de sites
- Downloads
+ Histórico de downloadsLimpar dados de navegação
@@ -1195,7 +1197,7 @@
Entrando…
- Criar uma conta
+ Conectar Conta FirefoxPermanecer desconectado
@@ -1274,11 +1276,11 @@
Pronto para digitalizar
- Entre com sua câmera
+ Conecte usando sua câmeraUsar email
- Crie uma para sincronizar o Firefox entre dispositivos.]]>
+ Crie uma para sincronizar o Firefox entre dispositivos.]]>O Firefox deixará de sincronizar com sua conta, mas não excluirá seus dados de navegação neste dispositivo.
@@ -1300,7 +1302,7 @@
Navegue sem ser seguido
- Mantenha seus dados consigo. O %s lhe protege de muitos dos rastreadores mais comuns que seguem o que você faz online.
+ Mantenha seus dados com você. O %s te protege de muitos dos rastreadores mais comuns que tentam seguir o que você faz online.Saiba mais
@@ -1347,7 +1349,7 @@
FingerprintersBloqueado:
- Permitido
+ Permitido:Rastreadores de mídias sociais
@@ -1454,10 +1456,8 @@
Preenchimento automáticoSincronização de contas
-
- Ativado
-
- Desativado
+
+ Sincronizar contas entre dispositivosReconectar
@@ -1558,6 +1558,8 @@
Os dados são criptografadosSincronizar cartões entre dispositivos
+
+ Sincronizar cartõesAdicionar cartão de crédito
@@ -1565,6 +1567,8 @@
Gerenciar cartões salvosAdicionar cartão
+
+ Editar cartãoNúmero do cartão
@@ -1573,6 +1577,8 @@
Nome no cartãoApelido do cartão
+
+ Excluir cartãoExcluir cartão
@@ -1585,6 +1591,9 @@
Cartões salvos
+
+ Digite um número de cartão de crédito válido
+
Adicionar mecanismo de pesquisa
@@ -1744,23 +1753,19 @@
Cancelar
+
+ Abra links, emails e mensagens automaticamente no Firefox.
+
Remover
-
- Aproveite ao máximo o %s.
-
Clique para ver mais detalhes
-
- Reúna o que é importante para você
-
- Agrupe pesquisas, sites e abas semelhantes para acesso rápido mais tarde.
-
- Você está conectado como %s em outro navegador Firefox neste celular. Quer entrar com esta conta?
-
- Você pode facilmente adicionar este site à tela inicial do celular para ter acesso imediato e navegar mais rápido com uma experiência semelhante a um aplicativo.
+
+ Ir para o topo
+
+
+ Fechar
-
+
diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml
index 59bfb6390..30c521ed1 100644
--- a/app/src/main/res/values-pt-rPT/strings.xml
+++ b/app/src/main/res/values-pt-rPT/strings.xml
@@ -443,9 +443,6 @@
Saber mais
-
- Desligado globalmente, aceda às Configurações para a ativar.
-
Telemetria
@@ -457,6 +454,8 @@
Partilha dados sobre as funcionalidades que utiliza no %1$s com o Leanplum, o nosso parceiro de marketing para dispositivos móveis.
+
+ Partilha informação básica de utilização com a Adjust, o nosso fornecedor de marketing para dispositivos móveisEstudos
@@ -608,6 +607,8 @@
Sessão privadaSeparadores privados
+
+ Separadores sincronizadosAdicionar separador
@@ -626,6 +627,8 @@
Partilhar todos os separadoresSeparadores fechados recentemente
+
+ Definições da contaDefinições dos separadores
@@ -794,8 +797,6 @@
Eliminada %1$sAdicionar pasta
-
- Marcador criado.Marcador guardado!
@@ -980,8 +981,6 @@
Todas as açõesRecentemente utilizadas
-
- Autenticado como %1$sIniciar sessão para sincronizar
@@ -1567,6 +1566,8 @@
Gerir cartões guardadosAdicionar cartão
+
+ Editar cartãoNúmero do cartão
@@ -1575,6 +1576,8 @@
Nome no cartãoNome alternativo do cartão
+
+ Eliminar cartãoEliminar cartão
@@ -1746,23 +1749,13 @@
Cancelar
+
+ Definir para que as ligações de sites, e-mails e mensagens sejam abertas automaticamente no Firefox.
+
Remover
-
- Tire o máximo proveito do %s.
-
Clique para mais detalhes
-
- Colecione as coisas que são importantes para si
-
- Agrupe pesquisas, sites e separadores semelhantes para um acesso rápido mais tarde.
-
- Está autenticado como %s noutro navegador Firefox neste telefone. Deseja iniciar sessão com esta conta?
-
- Pode adicionar facilmente este site ao seu ecrã inicial do seu telemóvel para ter acesso instantâneo e navegar mais rápido, com uma experiência semelhante ao de uma aplicação.
-
diff --git a/app/src/main/res/values-rm/strings.xml b/app/src/main/res/values-rm/strings.xml
index 87ff4ae7c..f4fa3590a 100644
--- a/app/src/main/res/values-rm/strings.xml
+++ b/app/src/main/res/values-rm/strings.xml
@@ -48,7 +48,7 @@
Tschernì
- %1$s vegn sviluppà da @fork-maintainers.
+ %1$s vegn sviluppà da Mozilla.
@@ -409,6 +409,11 @@
and the third is the device model. -->
%1$s sin %2$s %3$s
+
+ Cartas da credit
+
+ Adressas
+
Tabs retschavids
@@ -440,9 +445,6 @@
Ulteriuras infurmaziuns
-
- Deactivà globalmain, acceda als parameters per activar.
-
Telemetria
@@ -453,6 +455,8 @@
Datas da marketingTransmetta datas davart las funcziunalitads che ti dovras en %1$s a Leanplum, noss partenari per il marketing sin apparats mobils.
+
+ Cundivida datas d\'utilisaziun da basa cun Adjust, noss purschider da marketing mobilStudis
@@ -599,6 +603,8 @@
Sesida privataTabs privats
+
+ Tabs sincronisadsAgiuntar in tab
@@ -617,6 +623,8 @@
Cundivider tut ils tabsTabs serrads dacurt
+
+ Parameters dal contoParameters da tabs
@@ -785,8 +793,6 @@
Stizzà %1$sAgiuntar in ordinatur
-
- Creà il segnapagina.Memorisà il segnapagina!
@@ -970,8 +976,6 @@
Tuttas acziunsUtilisà dacurt
-
- Annunzià sco %1$sS\'annunziar per sincronisar
@@ -1451,10 +1455,8 @@
Endataziun automaticaSincronisar las infurmaziuns d\'annunzia
-
- Activà
-
- Deactivà
+
+ Sincronisar las datas d\'annunzia tranter tes apparatsReconnectar
@@ -1557,6 +1559,8 @@
Las datas èn criptadasSincronisar las cartas tranter ils apparats
+
+ Sincronisar las cartasAgiuntar ina carta da credit
@@ -1564,6 +1568,8 @@
Administrar las cartas memorisadasAgiuntar ina carta
+
+ Modifitgar la cartaNumer da carta
@@ -1572,6 +1578,8 @@
Num sin la cartaTes num per la carta
+
+ Stizzar la cartaStizzar la carta
@@ -1583,6 +1591,9 @@
Cartas memorisadas
+
+ Endatescha per plaschair in numer da carta da credit valid
+
Agiuntar ina maschina da tschertgar
@@ -1743,23 +1754,19 @@
Interrumper
+
+ Definescha che colliaziuns da websites, e-mails e messadis vegnan averts automaticamain en Firefox.
+
Allontanar
-
- Rabla ora il maximum da %s.
-
Cliccar per ulteriurs detagls
-
- Rimna las chaussas che t\'èn impurtantas
-
- Gruppescha retschertgas, paginas e tabs sumegliants per pli tard pudair acceder pli svelt.
-
- Ti es annunzià sco %s en in auter navigatur da Firefox sin quest telefonin. Vuls ti t\'annunziar cun quest conto?
-
- Ti pos agiuntar a moda simpla questa website al visur da partenza da tes telefonin per avair access direct e navigar pli svelt, sco sch\'i fiss ina app.
+
+ Navigar ensi
+
+
+ Serrar
-
+
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index 4fffe35fd..924d20dbe 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -434,6 +434,11 @@
and the third is the device model. -->
%1$s на %2$s %3$s
+
+ Банковские карты
+
+ Адреса
+
Вкладки с других устройств
@@ -466,9 +471,6 @@
Узнать больше
-
- Выключено глобально. Можно включить в настройках.
-
Телеметрия
@@ -479,6 +481,8 @@
Маркетинговые данныеДелиться данными о том, что вы используете в %1$s, с Leanplum, нашим мобильным маркетинговым вендором.
+
+ Делиться базовыми данными об использовании с Adjust, нашим мобильным маркетинговым вендоромИсследования
@@ -627,6 +631,8 @@
Приватная сессияПриватные вкладки
+
+ Облачные вкладкиОткрыть вкладку
@@ -645,6 +651,8 @@
Поделиться всеми вкладкамиНедавно закрытые вкладки
+
+ Настройки АккаунтаПараметры вкладок
@@ -816,8 +824,6 @@
%1$s удаленаСоздать папку
-
- Закладка создана.Закладка сохранена!
@@ -1015,8 +1021,6 @@
Все действияНедавно использованные
-
- Вы вошли как %1$sВойти в синхронизацию
@@ -1510,10 +1514,8 @@
АвтозаполнениеСинхронизация логинов
-
- Включена
-
- Отключена
+
+ Синхронизировать логины между устройствамиПереподключиться
@@ -1617,6 +1619,8 @@
Данные зашифрованыСинхронизировать карты между различными устройствами
+
+ Синхронизировать картыДобавить банковскую карту
@@ -1624,6 +1628,8 @@
Управление сохранёнными картамиДобавить карту
+
+ Изменить картуНомер карты
@@ -1632,6 +1638,8 @@
Имя держателяПсевдоним карты
+
+ Удалить картуУдалить карту
@@ -1643,6 +1651,9 @@
Сохранённые карты
+
+ Пожалуйста, введите корректный номер карты
+
Добавление поисковой системы
@@ -1830,23 +1841,19 @@
Отмена
+
+ Настройте автоматическое открытие ссылок с веб-сайтов, из электронных писем и сообщений в Firefox.
+
Убрать
-
- Получите максимум от %s.
-
Нажмите, чтобы узнать больше
-
- Собирайте то, что важно для вас
-
- Объединяйте похожие запросы, сайты и вкладки, для быстрого доступа к ним в будущем.
-
- В другом браузере Firefox на этом устройстве вы вошли как %s. Вы хотите авторизоваться с помощью этого аккаунта?
-
- Вы можете легко добавить этот веб-сайт на домашний экран вашего телефона, чтобы иметь к нему мгновенный доступ и сёрфить быстрее со скоростью нативного приложения.
+
+ Перейти наверх
+
+
+ Закрыть
-
+
diff --git a/app/src/main/res/values-sat/strings.xml b/app/src/main/res/values-sat/strings.xml
index 7aef50afb..2463204b4 100644
--- a/app/src/main/res/values-sat/strings.xml
+++ b/app/src/main/res/values-sat/strings.xml
@@ -1,30 +1,26 @@
- %s ᱱᱤᱡᱮᱨᱟᱜ
+ ᱯᱨᱟᱭᱣᱮᱴ %s
- %s (ᱱᱤᱡᱮᱨᱟᱜ)
+ %s (ᱯᱨᱟᱭᱣᱮᱴ)ᱰᱷᱮᱨ ᱮᱴᱟᱜᱟᱜ
- ᱱᱤᱡᱮᱨᱟᱜ ᱵᱽᱨᱳᱣᱡᱤᱝ ᱦᱩᱭ ᱦᱚᱪᱚᱭ ᱢᱮ
+ ᱯᱨᱟᱭᱣᱮᱴ ᱵᱽᱨᱳᱣᱡᱤᱝ ᱦᱩᱭ ᱦᱚᱪᱚᱭ ᱢᱮ
- ᱱᱤᱡᱮᱨᱟᱜ ᱵᱽᱨᱳᱣᱡᱤᱝ ᱵᱟᱝ ᱦᱩᱭ ᱦᱚᱪᱚᱜ ᱢᱮ
+ ᱯᱨᱟᱭᱣᱮᱴ ᱵᱽᱨᱳᱣᱡᱤᱝ ᱵᱟᱝ ᱦᱩᱭ ᱦᱚᱪᱚᱜ ᱢᱮᱥᱮᱸᱫᱽᱨᱟ ᱵᱟᱝᱠᱷᱟᱱ ᱴᱷᱤᱠᱟᱹᱬᱟ ᱟᱫᱮᱨ
- ᱟᱢᱟᱜ ᱠᱷᱩᱞᱟᱹ ᱟᱠᱟᱱ ᱴᱮᱵ ᱠᱚ ᱫᱚ ᱱᱚᱰᱮ ᱫᱮᱠᱷᱟᱣᱜᱼᱟ ᱾
+ ᱟᱢᱟᱜ ᱠᱷᱩᱞᱟᱹ ᱟᱠᱟᱱ ᱴᱮᱵᱽ ᱠᱚ ᱫᱚ ᱱᱚᱰᱮ ᱫᱮᱠᱷᱟᱣᱜᱼᱟ ᱾ᱟᱢᱟᱜ ᱱᱤᱡᱚᱨᱟᱜ ᱴᱮᱵᱽ ᱠᱚ ᱫᱚ ᱱᱚᱰᱮ ᱩᱫᱩᱜ ᱦᱩᱭᱩᱜ-ᱟ ᱾
-
- ᱵᱟᱭᱰᱩ
-
- JD1 ᱴᱮᱵᱽ ᱠᱷᱩᱞᱟᱹᱭ ᱢᱮ ᱾ ᱴᱮᱵᱽ ᱵᱚᱫᱚᱞ ᱞᱟᱹᱜᱤᱫ ᱴᱤᱯᱟᱹᱣ ᱢᱮ ᱾
- %1$s ᱴᱮᱵ ᱠᱚ ᱠᱷᱩᱞᱟᱹᱭ ᱢᱮ ᱾ ᱴᱮᱵ ᱵᱚᱫᱚᱞ ᱞᱟᱹᱜᱤᱫ ᱴᱤᱯᱟᱹᱣ ᱢᱮ ᱾
+ %1$s ᱴᱮᱵ ᱠᱚ ᱠᱷᱩᱞᱟᱹᱭ ᱢᱮ ᱾ ᱴᱮᱵᱽ ᱵᱚᱫᱚᱞ ᱞᱟᱹᱜᱤᱫ ᱴᱤᱯᱟᱹᱣ ᱢᱮ ᱾%1$d ᱵᱟᱪᱷᱟᱣᱮᱱᱟ
@@ -36,7 +32,7 @@
ᱢᱟᱹᱞᱴᱤᱥᱤᱞᱮᱠᱼᱴ ᱢᱳᱰ ᱵᱟᱹᱰ
- ᱵᱟᱪᱷᱟᱣᱠᱟᱱ ᱴᱮᱵ ᱠᱚ ᱛᱩᱢᱟᱹᱞ ᱥᱟᱸᱪᱟᱣ ᱢᱮ
+ ᱵᱟᱪᱷᱟᱣᱠᱟᱱ ᱴᱮᱵᱽ ᱠᱚ ᱛᱩᱢᱟᱹᱞ ᱥᱟᱸᱪᱟᱣ ᱢᱮᱵᱟᱪᱷᱟᱣᱮᱱᱟ %1$s
@@ -44,20 +40,21 @@
ᱵᱟᱹᱰ ᱟᱠᱟᱱᱟ ᱢᱟᱹᱴᱤᱥᱤᱞᱮᱼᱴ ᱢᱳᱰ
- ᱢᱟᱹᱞᱴᱤᱥᱤᱞᱮᱠᱼᱴ ᱢᱳᱰ ᱵᱚᱞᱚ ᱮᱱᱟᱢ, ᱛᱩᱢᱟᱹᱞ ᱥᱟᱸᱪᱟᱣ ᱞᱟᱹᱜᱤᱫ ᱨᱮᱵ ᱠᱚ ᱵᱟᱪᱷᱟᱣ ᱢᱮ
+ ᱢᱟᱹᱞᱴᱤᱥᱤᱞᱮᱠᱼᱴ ᱢᱳᱰ ᱵᱚᱞᱚ ᱮᱱᱟᱢ, ᱛᱩᱢᱟᱹᱞ ᱥᱟᱸᱪᱟᱣ ᱞᱟᱹᱜᱤᱫ ᱴᱮᱵᱽ ᱠᱚ ᱵᱟᱪᱷᱟᱣ ᱢᱮ ᱵᱟᱪᱷᱟᱣᱮᱱᱟ
- %1$s ᱛᱮᱭᱟᱨ ᱦᱩᱭ ᱠᱟᱱᱟ Mozilla ᱫᱟᱨᱟᱭᱛᱮ ᱾
+ %1$s ᱛᱮᱭᱟᱨ ᱦᱩᱭ ᱠᱟᱱᱟ ᱢᱳᱡᱤᱞᱟ ᱫᱟᱨᱟᱭᱛᱮ ᱾ᱟᱢ ᱱᱤᱡᱚᱨᱟᱜ ᱚᱠᱛᱚ ᱨᱮ ᱢᱮᱱᱟᱢ-ᱟ
- %1$s ᱟᱢᱟᱜ ᱥᱮᱸᱫᱽᱨᱟ ᱟᱨ ᱯᱟᱱᱛᱮᱭᱟᱜ ᱦᱤᱛᱟᱹᱞ ᱱᱤᱡᱮᱨᱟᱜ ᱴᱮᱵ ᱠᱷᱚᱱ ᱯᱷᱟᱨᱪᱟ ᱭᱟᱭ ᱡᱚᱠᱷᱚᱱ ᱟᱢ ᱩᱱᱠᱩ ᱵᱚᱸᱫᱚ ᱠᱚᱣᱟᱢ ᱟᱨ ᱮᱯ ᱟᱲᱟᱹᱜ ᱟᱢ. ᱱᱚᱶᱟ ᱫᱚ ᱵᱟᱝ ᱵᱟᱰᱟᱭ ᱵᱟᱝ ᱵᱮᱱᱟᱣ ᱢᱤᱭᱟᱭ ᱣᱮᱵᱥᱟᱭᱤᱴ ᱟᱨ ᱤᱱᱴᱚᱨᱱᱮᱴ ᱮᱢᱚᱜᱤᱪ ᱠᱷᱚᱱ, ᱱᱚᱶᱟ ᱫᱚ ᱟᱞᱜᱟ ᱵᱮᱱᱟᱣ ᱟᱭ ᱫᱚᱦᱚ ᱞᱟᱹᱜᱤᱛ ᱪᱷᱤ ᱠᱚ ᱟᱢ ᱚᱱᱞᱟᱭᱤᱱ ᱠᱚᱨᱟᱣ ᱫᱟᱢ ᱩᱱᱠᱩ ᱠᱷᱚᱱ ᱡᱟ ᱱᱚᱶᱟ ᱥᱟᱫᱷᱚᱱ ᱵᱮᱵᱷᱟᱨ ᱟᱠᱚ ᱾
- ᱱᱤᱡᱚᱨᱟᱜ ᱵᱨᱟᱣᱡᱤᱝ ᱨᱮᱭᱟᱜ ᱥᱚᱢᱟᱱ ᱮᱰᱮ ᱠᱚ
+ %1$s ᱟᱢᱟᱜ ᱥᱮᱸᱫᱽᱨᱟ ᱟᱨ ᱵᱽᱨᱳᱣᱡᱤᱝ ᱱᱟᱜᱟᱢ ᱯᱷᱟᱨᱪᱟᱭ ᱛᱟᱦᱮᱱᱟ ᱱᱤᱡᱮᱨᱟᱜ ᱴᱮᱵᱽ ᱠᱷᱚᱱ ᱡᱚᱠᱷᱚᱱ ᱟᱢ ᱚᱱᱟ ᱵᱚᱸᱫᱚᱭ ᱛᱟᱦᱮᱱᱟᱢ ᱵᱟᱝᱠᱷᱟᱱ ᱮᱯ ᱵᱟᱹᱜᱤ ᱜᱷᱟᱹᱰᱤᱠ ᱾
+ᱡᱚᱦᱚᱜᱽ ᱱᱚᱶᱟ ᱫᱚ ᱮᱱᱚᱱᱚᱢᱟᱥ ᱵᱟᱭ ᱵᱮᱱᱟᱣ ᱢᱮᱭᱟ ᱣᱮᱵᱥᱟᱭᱤᱴ ᱨᱮ ᱟᱨ ᱟᱢᱟᱜ ᱤᱱᱴᱚᱨᱱᱮᱴ ᱥᱮᱣᱟ ᱮᱢᱚᱜᱤᱪ ᱠᱷᱚᱱ, ᱱᱚᱶᱟ ᱫᱚ ᱟᱥᱟᱱ ᱵᱮᱱᱟᱣ ᱛᱟᱦᱮᱱᱟᱭ ᱟᱢᱟᱜ ᱚᱱᱞᱟᱭᱤᱱ ᱠᱟᱹᱢᱤ ᱠᱚ ᱱᱤᱡᱮᱨᱟᱜ ᱫᱚᱦᱚ ᱞᱟᱹᱜᱤᱫ ᱩᱱᱠᱩ ᱠᱷᱚᱱ ᱡᱟᱦᱟᱸᱭ ᱠᱚ ᱱᱚᱶᱟ ᱥᱟᱫᱷᱚᱱ ᱵᱮᱵᱷᱟᱨ ᱛᱟᱦᱮᱱᱼᱟ ᱾
+ ᱱᱤᱡᱮᱨᱟᱜ ᱵᱽᱨᱳᱣᱡᱤᱝ ᱵᱟᱵᱚᱛ ᱥᱚᱢᱟᱱ ᱮᱰᱮ ᱠᱚᱚᱠᱛᱚ ᱢᱮᱴᱟᱣ ᱢᱮ
@@ -88,14 +85,14 @@
ᱵᱟᱦᱨᱮ ᱚᱰᱚᱠ
- ᱚᱠᱟ ᱠᱚ ᱫᱚ ᱢᱟᱲᱟᱝ ᱫᱤᱱ, ᱦᱟᱴ, ᱟᱨ ᱵᱟᱝ ᱢᱟᱦᱟ ᱠᱚᱨᱮ ᱵᱟᱝ ᱧᱮᱞ ᱟᱠᱟᱱᱟ ᱚᱱᱟ ᱠᱚ ᱫᱚ ᱠᱷᱭᱞᱟᱹ ᱴᱮᱵ ᱠᱚ ᱨᱮ ᱟᱡ ᱛᱮᱜᱮ ᱵᱚᱸᱫᱚᱜ ᱢᱟ ᱾
+ ᱚᱠᱟ ᱠᱚ ᱫᱚ ᱢᱟᱲᱟᱝ ᱫᱤᱱ, ᱦᱟᱴ, ᱟᱨ ᱵᱟᱝ ᱢᱟᱦᱟ ᱠᱚᱨᱮ ᱵᱟᱝ ᱧᱮᱞ ᱟᱠᱟᱱᱟ ᱚᱱᱟ ᱠᱚ ᱫᱚ ᱠᱷᱭᱞᱟᱹ ᱴᱮᱵᱽ ᱠᱚ ᱨᱮ ᱟᱡ ᱛᱮᱜᱮ ᱵᱚᱸᱫᱚᱜ ᱢᱟ ᱾ᱮᱴᱟᱜᱟ ᱚᱯᱥᱚᱱ ᱠᱚ ᱫᱮᱠᱷᱟᱣᱢᱮᱵᱟᱹᱰ
- ᱠᱷᱩᱞᱟᱹ ᱴᱮᱵ ᱞᱮᱭᱟᱩᱴ ᱠᱚ ᱵᱚᱫᱚᱞ ᱢᱮ ᱾ ᱥᱟᱡᱟᱣ ᱴᱷᱮᱱ ᱪᱟᱞᱟᱣ ᱠᱟᱛᱮᱫᱽ ᱴᱮᱵ ᱣᱤᱣ ᱞᱟᱛᱟᱨ ᱨᱮ ᱜᱨᱤᱰ ᱵᱟᱪᱷᱟᱣ ᱢᱮ ᱾
+ ᱠᱷᱩᱞᱟᱹ ᱴᱮᱵ ᱞᱮᱭᱟᱩᱴ ᱠᱚ ᱵᱚᱫᱚᱞ ᱢᱮ ᱾ ᱥᱟᱡᱟᱣ ᱴᱷᱮᱱ ᱪᱟᱞᱟᱣ ᱠᱟᱛᱮᱫᱽ ᱴᱮᱵᱽ ᱣᱤᱣ ᱞᱟᱛᱟᱨ ᱨᱮ ᱜᱨᱤᱰ ᱵᱟᱪᱷᱟᱣ ᱢᱮ ᱾ ᱥᱟᱡᱟᱣ ᱛᱮ ᱪᱟᱞᱟᱜᱽ ᱢᱮ
@@ -112,13 +109,13 @@
- ᱡᱷᱤᱡᱽ ᱟᱠᱟᱱ ᱴᱮᱵ ᱠᱚ
+ ᱡᱷᱤᱡᱽ ᱟᱠᱟᱱ ᱴᱮᱵᱽ ᱠᱚᱛᱟᱭᱚᱢᱞᱟᱦᱟ
- ᱨᱤᱯᱷᱨᱮᱥ
+ ᱱᱟᱶᱟ ᱟᱹᱨᱩᱛᱤᱸᱜᱩ
@@ -127,6 +124,8 @@
ᱵᱩᱠᱢᱟᱨᱠ ᱥᱟᱯᱲᱟᱣ ᱢᱮᱮᱰ-ᱟᱸᱱᱥ
+
+ ᱯᱟᱥᱱᱟᱣᱠᱚᱱᱚᱰᱮ ᱮᱰ-ᱟᱸᱱᱥ ᱠᱚ ᱵᱟᱹᱱᱩᱜᱼᱟ
@@ -144,7 +143,7 @@
ᱵᱚᱦᱟᱞ ᱢᱮ
- ᱥᱤᱸᱠᱼᱰ ᱴᱮᱵ ᱠᱚ
+ ᱥᱤᱸᱠᱼᱰ ᱴᱮᱵᱽ ᱠᱚ ᱨᱤᱥᱤᱸᱠ
@@ -171,11 +170,16 @@
ᱨᱤᱰᱚᱨ ᱵᱷᱭᱩ ᱵᱚᱸᱫᱚᱭ ᱢᱮ
- ᱮᱯᱯ ᱨᱮ ᱡᱷᱤᱡ ᱢᱮ
+ ᱮᱯ ᱨᱮ ᱡᱷᱤᱡᱽ ᱢᱮᱧᱮᱞ ᱵᱤᱰᱟᱹᱣ
+
+ ᱥᱮᱞᱮᱫᱽ
+
+ ᱥᱟᱯᱲᱟᱣ
+
ᱵᱟᱝ ᱡᱩᱲᱟᱹᱣ ᱫᱟᱲᱮᱭᱟᱜ ᱠᱟᱱᱟ ᱾ URL ᱥᱠᱤᱢ ᱵᱟᱝ ᱪᱤᱱᱦᱟᱹᱣ ᱫᱟᱲᱮᱞᱟᱱᱟ ᱾
@@ -213,6 +217,9 @@
ᱰᱷᱮᱨ ᱥᱮᱬᱟᱭ ᱢᱮ
+
+ %s ᱥᱮᱸᱫᱽᱨᱟᱭ ᱢᱮ
+
ᱱᱟᱶᱟ Firefox ᱴᱮᱵ ᱠᱷᱩᱞᱟᱹᱭ ᱢᱮ
@@ -241,12 +248,12 @@
ᱜᱚᱲᱚ
- Google Play ᱨᱮ ᱫᱚᱨ ᱢᱮ
+ ᱜᱩᱜᱚᱞ ᱯᱞᱮ ᱨᱮ ᱫᱚᱨ ᱢᱮᱯᱩᱥᱴᱟᱹᱣ ᱮᱢ ᱢᱮ
- %1$s ᱨᱮᱭᱟᱜ ᱵᱟᱵᱚᱛ
+ %1$s ᱵᱟᱵᱚᱛᱟᱢᱟᱜ ᱦᱚᱠ ᱠᱚ
@@ -264,13 +271,13 @@
ᱥᱟᱭᱤᱴ ᱨᱮᱭᱟᱜ ᱪᱷᱟᱹᱲ ᱠᱚ
- ᱱᱤᱡᱮᱨᱟᱜ ᱵᱨᱟᱩᱡᱤᱝ
+ ᱱᱤᱡᱮᱨᱟᱜ ᱵᱽᱨᱳᱣᱡᱤᱝᱞᱤᱸᱠ ᱠᱚ ᱱᱤᱡᱮᱨᱟᱜ ᱴᱮᱵ ᱨᱮ ᱡᱷᱤᱡᱽ ᱢᱮ
- ᱱᱤᱡᱮᱨᱟᱜ ᱵᱨᱟᱩᱡᱤᱝ ᱨᱮ ᱥᱠᱨᱤᱱᱥᱚᱴ ᱠᱚ ᱦᱮᱥᱟᱨᱤᱭᱟᱹ ᱢᱮ
+ ᱱᱤᱡᱮᱨᱟᱜ ᱵᱽᱨᱳᱣᱡᱤᱝ ᱨᱮ ᱥᱠᱨᱤᱱᱥᱚᱴ ᱠᱚ ᱦᱮᱥᱟᱨᱤᱭᱟᱹ ᱢᱮ
- ᱡᱩᱫᱤ ᱢᱟᱸᱡᱩᱨ ᱮᱱᱟ, ᱯᱨᱟᱭᱣᱮᱴ ᱴᱮᱵ ᱠᱚ ᱫᱚ ᱧᱮᱞᱚᱜ ᱜᱮᱭᱟ ᱡᱩᱫᱤ ᱢᱟᱹᱞᱴᱤᱯᱟᱹᱞ ᱮᱯᱯ ᱠᱚ ᱠᱷᱩᱞᱟᱹ ᱛᱟᱦᱮᱸᱱᱟ
+ ᱡᱩᱫᱤ ᱢᱟᱸᱡᱩᱨ ᱮᱱᱟ, ᱯᱨᱟᱭᱣᱮᱴ ᱴᱮᱵᱽ ᱠᱚ ᱫᱚ ᱧᱮᱞᱚᱜ ᱜᱮᱭᱟ ᱡᱩᱫᱤ ᱢᱟᱹᱞᱴᱤᱯᱟᱹᱞ ᱮᱯᱯ ᱠᱚ ᱠᱷᱩᱞᱟᱹ ᱛᱟᱦᱮᱸᱱᱟᱱᱤᱡᱮᱨᱟᱜ ᱵᱨᱟᱩᱡᱤᱝ ᱠᱷᱟᱴᱚᱢᱟᱪᱷᱟ ᱥᱮᱞᱮᱫᱽ ᱢᱮ
@@ -296,7 +303,7 @@
ᱠᱩᱥᱤᱭᱟᱜ ᱛᱮᱭᱟᱨ
- ᱵᱩᱠᱢᱟᱨᱠ, ᱦᱤᱛᱟᱹᱞ ᱟᱨ ᱵᱟᱹᱲᱛᱤ ᱠᱚ ᱟᱢᱟᱜ Firefox ᱠᱷᱟᱛᱟ ᱥᱟᱶ ᱛᱮ ᱥᱭᱸᱠ ᱢᱮ
+ ᱵᱩᱠᱢᱟᱨᱠ, ᱱᱟᱜᱟᱢ ᱟᱨ ᱵᱟᱹᱲᱛᱤ ᱠᱚ ᱟᱢᱟᱜ Firefox ᱠᱷᱟᱛᱟ ᱥᱟᱶᱛᱮ ᱥᱭᱸᱠ ᱢᱮFirefox ᱠᱷᱟᱛᱟ
@@ -312,7 +319,7 @@
ᱵᱟᱲᱦᱟᱣᱟᱜ ᱦᱟᱹᱛᱤᱭᱟᱹᱨ
- ᱥᱟᱸᱜᱤᱧ ᱫᱚᱥ ᱥᱟᱦᱟᱭ USB ᱥᱟᱶ ᱛᱮ
+ ᱨᱤᱢᱳᱴ ᱰᱤᱵᱚᱜᱤᱝ ᱣᱟᱭᱟ USBᱥᱮᱸᱫᱽᱨᱟ ᱤᱱᱡᱤᱱ ᱠᱚ ᱩᱫᱩᱜ ᱢᱮ
@@ -328,13 +335,13 @@
ᱵᱩᱠᱢᱟᱨᱠ ᱠᱚ ᱥᱮᱸᱫᱽᱨᱟᱭ ᱢᱮ
- ᱥᱭᱸᱠ ᱟᱠᱟᱱ ᱴᱮᱵ ᱠᱚ ᱥᱮᱸᱫᱽᱨᱟᱭ ᱢᱮ
+ ᱥᱭᱸᱠ ᱟᱠᱟᱱ ᱴᱮᱵᱽ ᱠᱚ ᱥᱮᱸᱫᱽᱨᱟᱭ ᱢᱮᱠᱷᱟᱛᱟ ᱨᱮᱭᱟᱜ ᱥᱟᱡᱟᱣ ᱠᱚURLs ᱟᱪᱛᱮ ᱯᱩᱨᱟᱹᱣᱼᱜ ᱢᱟ
- ᱞᱤᱸᱠ ᱠᱚ ᱮᱯ ᱨᱮ ᱡᱷᱤᱡ ᱢᱮ
+ ᱞᱤᱸᱠ ᱠᱚ ᱮᱯ ᱨᱮ ᱡᱷᱤᱡᱽ ᱢᱮᱵᱟᱦᱨᱮ ᱨᱮᱭᱟᱜ ᱰᱟᱣᱱᱞᱳᱰ ᱢᱮᱱᱮᱡᱟᱹᱨ
@@ -357,19 +364,25 @@
ᱥᱮᱞᱮᱫᱽ ᱮᱦᱚᱵᱽ ᱛᱩᱢᱟᱹᱞ ᱵᱚᱫᱚᱞᱮᱱᱟ ᱾ ᱵᱚᱫᱚᱞ ᱠᱚ ᱫᱚᱦᱚ ᱞᱟᱹᱜᱤᱫ ᱮᱯᱯᱞᱤᱠᱮᱥᱚᱱ ᱵᱚᱸᱫᱚᱜ ᱠᱟᱱᱟ…
+
+
+ ᱮᱰᱼᱚᱱ ᱫᱚ ᱵᱟᱭ ᱟᱠᱟᱱᱟ
+
+ ᱮᱰᱼᱚᱱ ᱢᱟᱲᱟᱝ ᱠᱷᱚᱱ ᱵᱚᱦᱟᱞ ᱟᱠᱟᱱᱟ
+
ᱱᱤᱛᱚᱜ ᱥᱭᱸᱠ ᱢᱮᱚᱠᱟ ᱥᱭᱸᱠ ᱨᱮᱭᱟᱜ ᱛᱟᱦᱮᱸᱱᱟ ᱵᱟᱪᱷᱟᱣ ᱢᱮ
- ᱦᱤᱛᱟᱹᱞ
+ ᱱᱟᱜᱟᱢᱵᱩᱠᱢᱟᱨᱠ ᱠᱚᱵᱚᱞᱚᱱ ᱠᱚ
- ᱡᱷᱤᱡ ᱴᱮᱵ ᱠᱚ
+ ᱡᱷᱤᱡᱽ ᱴᱮᱵᱽ ᱠᱚᱵᱟᱦᱨᱮ ᱚᱰᱚᱠ
@@ -393,14 +406,14 @@
- ᱧᱟᱢᱟᱠᱟᱱ ᱴᱮᱵ ᱠᱚ
+ ᱧᱟᱢᱟᱠᱟᱱ ᱴᱮᱵᱽ ᱠᱚ
- ᱚᱞᱜᱟ Firefox ᱥᱟᱫᱷᱚᱱ ᱠᱷᱚᱱ ᱴᱮᱵ ᱠᱚ ᱨᱮᱭᱟᱜ ᱠᱷᱚᱵᱚᱨ ᱧᱟᱢᱮᱱᱟ ᱾
+ ᱚᱞᱜᱟ Firefox ᱥᱟᱫᱷᱚᱱ ᱠᱷᱚᱱ ᱴᱮᱵᱽ ᱠᱚ ᱨᱮᱭᱟᱜ ᱠᱷᱚᱵᱚᱨ ᱧᱟᱢᱮᱱᱟ ᱾ᱴᱮᱵ ᱧᱟᱢᱮᱱᱟ
- ᱴᱮᱵ ᱠᱚ ᱧᱟᱢᱮᱱᱟ
+ ᱴᱮᱵᱽ ᱠᱚ ᱧᱟᱢᱮᱱᱟ%s ᱠᱷᱚᱱ ᱴᱮᱵ
@@ -423,9 +436,6 @@
ᱰᱷᱮᱨ ᱥᱮᱬᱟᱭ ᱢᱮ
-
- ᱡᱮᱜᱮᱛ ᱨᱮ ᱵᱚᱸᱫᱚ ᱦᱩᱭ ᱠᱟᱱᱟ, ᱪᱟᱹᱞᱩ ᱞᱟᱹᱜᱤᱫ ᱛᱮ ᱥᱟᱡᱟᱣ ᱨᱮ ᱪᱟᱹᱞᱩᱭ ᱢᱮ ᱾
-
ᱴᱮᱞᱭᱢᱮᱴᱨᱭ
@@ -485,7 +495,7 @@
ᱧᱩᱛ
- ᱵᱮᱴᱨᱭ ᱥᱮᱣᱟᱹᱨ ᱛᱮ ᱥᱮᱴ ᱢᱮ
+ ᱵᱮᱴᱨᱭ ᱥᱮᱣᱟᱹᱨ ᱛᱮ ᱥᱮᱴ ᱦᱩᱭᱩᱜᱼᱟᱥᱟᱫᱷᱚᱱ ᱩᱭᱦᱟᱹᱨ ᱯᱟᱸᱡᱟᱭ ᱢᱮ
@@ -493,12 +503,12 @@
ᱱᱟᱶᱟ ᱟᱹᱨᱩ ᱞᱟᱹᱜᱤᱛ ᱛᱮ ᱚᱨ ᱢᱮ
- ᱦᱟᱹᱛᱤᱭᱟᱹᱨ ᱩᱠᱩ ᱞᱟᱹᱜᱤᱛ ᱛᱮ ᱛᱚᱛᱨᱚᱭ ᱢᱮ
+ ᱴᱩᱞᱵᱟᱨ ᱩᱠᱩ ᱞᱟᱹᱜᱤᱫᱛᱮ ᱛᱚᱛᱨᱚᱭ ᱢᱮ
- ᱴᱮᱵ ᱠᱚ ᱵᱚᱫᱚᱞ ᱞᱟᱹᱜᱤᱫ ᱥᱟᱭᱤᱰ ᱛᱮᱴᱩᱞᱵᱟᱨ ᱠᱷᱚᱥᱨᱚᱫ ᱢᱮ
+ ᱴᱮᱵᱽ ᱠᱚ ᱵᱚᱫᱚᱞ ᱞᱟᱹᱜᱤᱫᱛᱮ ᱴᱩᱞᱵᱟᱨ ᱫᱷᱟᱨᱮᱛᱮ ᱜᱷᱟᱥᱠᱟᱣ ᱢᱮ
- ᱴᱮᱵ ᱠᱚ ᱠᱷᱩᱞᱟᱹ ᱞᱟᱹᱜᱤᱫ ᱴᱩᱞᱵᱟᱨ ᱥᱣᱟᱭᱤᱯ ᱢᱮ
+ ᱴᱮᱵᱽ ᱠᱚ ᱠᱷᱩᱞᱟᱹ ᱞᱟᱹᱜᱤᱫ ᱴᱩᱞᱵᱟᱨ ᱥᱣᱟᱭᱤᱯ ᱢᱮ
@@ -506,7 +516,7 @@
ᱤᱥᱠᱨᱤᱱᱥᱚᱴ ᱠᱚ
- ᱰᱟᱩᱱᱞᱚᱰ ᱠᱚ
+ ᱰᱟᱩᱱᱞᱳᱰ ᱠᱚᱵᱩᱠᱢᱟᱨᱠ ᱠᱚ
@@ -518,9 +528,9 @@
ᱮᱴᱟᱜᱟ ᱵᱩᱠᱢᱟᱨᱠ ᱠᱚ
- ᱦᱤᱛᱟᱹᱞ
+ ᱱᱟᱜᱟᱢ
- ᱥᱭᱸᱠᱼᱰ ᱴᱮᱵ ᱠᱚ
+ ᱥᱭᱸᱠᱼᱰ ᱴᱮᱵᱽ ᱠᱚᱯᱟᱲᱦᱟᱣ ᱞᱤᱥᱴᱤ
@@ -528,35 +538,35 @@
ᱥᱟᱡᱟᱣ ᱠᱚ
- ᱦᱤᱛᱟᱹᱞ ᱡᱤᱱᱤᱥ ᱢᱮᱱᱩ
+ ᱱᱟᱜᱟᱢ ᱡᱤᱱᱤᱥ ᱢᱮᱱᱩᱵᱚᱸᱫᱚᱭ ᱢᱮ
- ᱱᱤᱛᱚᱜᱽᱼᱟᱜ ᱵᱚᱸᱫᱚᱼᱟᱜ ᱴᱮᱵ ᱠᱚ
+ ᱱᱤᱛᱚᱜᱽᱼᱟᱜ ᱵᱚᱸᱫᱚᱼᱟᱜ ᱴᱮᱵᱽ ᱠᱚ
- ᱥᱟᱱᱟᱢ ᱦᱤᱛᱟᱹᱞ ᱫᱮᱠᱷᱟᱣᱢᱮ
+ ᱥᱟᱱᱟᱢ ᱱᱟᱜᱟᱢ ᱫᱮᱠᱷᱟᱣᱢᱮ
- %d ᱴᱮᱵ ᱠᱚ
+ %d ᱴᱮᱵᱽ ᱠᱚ
- %d ᱴᱮᱵ
+ %d ᱴᱮᱵᱽ
- ᱱᱤᱛᱚᱜᱽᱼᱟᱜ ᱵᱚᱸᱫᱚᱼᱟᱜ ᱴᱮᱵ ᱠᱚ ᱵᱟᱹᱱᱩᱜᱼᱟ
+ ᱱᱤᱛᱚᱜᱽᱼᱟᱜ ᱵᱚᱸᱫᱚᱼᱟᱜ ᱴᱮᱵᱽ ᱠᱚ ᱵᱟᱹᱱᱩᱜᱼᱟ
- ᱴᱮᱵ ᱠᱚ
+ ᱴᱮᱵᱽ ᱠᱚ
- ᱴᱮᱵ ᱧᱮᱞ
+ ᱴᱮᱵᱽ ᱵᱷᱤᱭᱩ
- ᱞᱤᱥᱴᱭ
+ ᱞᱤᱥᱴᱤ
- ᱡᱟᱹᱞᱤ
+ ᱡᱟᱸᱜᱞᱟ
- ᱴᱮᱵ ᱠᱚ ᱵᱚᱸᱫᱚᱭ ᱢᱮ
+ ᱴᱮᱵᱽ ᱠᱚ ᱵᱚᱸᱫᱚᱭ ᱢᱮᱛᱤ ᱛᱮᱭᱟᱜ
@@ -579,11 +589,11 @@
- ᱡᱷᱤᱡᱽ ᱟᱠᱟᱱ ᱴᱮᱵ ᱠᱚ
+ ᱡᱷᱤᱡᱽ ᱟᱠᱟᱱ ᱴᱮᱵᱽ ᱠᱚᱯᱨᱭᱣᱮᱴ ᱯᱟᱲᱤ
- ᱯᱨᱭᱣᱮᱴ ᱴᱮᱵ ᱠᱚ
+ ᱯᱨᱟᱭᱣᱮᱴ ᱴᱮᱵᱽ ᱠᱚᱴᱮᱵ ᱥᱮᱞᱮᱫᱽ ᱢᱮ
@@ -591,19 +601,19 @@
ᱯᱨᱭᱣᱮᱴ
- ᱡᱷᱤᱡᱽ ᱟᱠᱟᱱ ᱴᱮᱵ ᱠᱚ
+ ᱡᱷᱤᱡᱽ ᱟᱠᱟᱱ ᱴᱮᱵᱽ ᱠᱚᱛᱩᱢᱟᱹᱞ ᱨᱮ ᱥᱟᱸᱪᱟᱣ ᱢᱮᱵᱟᱪᱷᱟᱣ
- ᱡᱷᱚᱛᱚ ᱴᱮᱵ ᱠᱚ ᱦᱟᱹᱴᱧ ᱢᱮ
+ ᱡᱷᱚᱛᱚ ᱴᱮᱵᱽ ᱠᱚ ᱦᱟᱹᱴᱧ ᱢᱮ
- ᱱᱤᱛᱚᱜᱽᱼᱟᱜ ᱵᱚᱸᱫᱚᱼᱟᱜ ᱴᱮᱵ ᱠᱚ
+ ᱱᱤᱛᱚᱜᱽᱼᱟᱜ ᱵᱚᱸᱫᱚᱼᱟᱜ ᱴᱮᱵᱽ ᱠᱚᱴᱮᱵ ᱨᱮᱭᱟᱜ ᱥᱟᱡᱟᱣ ᱠᱚ
- ᱡᱷᱚᱛᱚ ᱴᱮᱵ ᱠᱚ ᱵᱚᱸᱫᱚᱭ ᱢᱮ
+ ᱡᱷᱚᱛᱚ ᱴᱮᱵᱽ ᱠᱚ ᱵᱚᱸᱫᱚᱭ ᱢᱮᱱᱟᱶᱟ ᱴᱮᱵ
@@ -617,33 +627,33 @@
ᱵᱟᱪᱷᱟᱣ ᱟᱠᱟᱱ ᱴᱮᱵᱽ ᱠᱚ ᱦᱟᱹᱴᱤᱧ ᱢᱮ
- ᱵᱟᱪᱷᱟᱣ ᱟᱠᱟᱱ ᱴᱮᱵ ᱢᱮᱱᱭᱩ ᱠᱚ
+ ᱵᱟᱪᱷᱟᱣ ᱟᱠᱟᱱ ᱴᱮᱵᱽ ᱢᱮᱱᱭᱩ ᱠᱚᱴᱮᱵ ᱛᱩᱢᱟᱹᱞ ᱠᱷᱚᱱ ᱚᱪᱚᱜ ᱢᱮ
- ᱴᱮᱵ ᱠᱚ ᱵᱟᱪᱷᱟᱣ ᱢᱮ
+ ᱴᱮᱵᱽ ᱠᱚ ᱵᱟᱪᱷᱟᱣ ᱢᱮᱴᱮᱵ ᱵᱚᱸᱫᱚᱭ ᱢᱮ%s ᱴᱮᱵ ᱵᱚᱸᱫᱚᱭ ᱢᱮ
- ᱴᱮᱵ ᱢᱮᱱᱩ ᱡᱷᱤᱜ ᱢᱮ
+ ᱴᱮᱵᱽ ᱢᱮᱱᱭᱩ ᱡᱷᱤᱡᱽ ᱢᱮ
- ᱡᱷᱚᱛᱚ ᱴᱮᱵ ᱠᱚ ᱵᱚᱸᱫᱚᱭ ᱢᱮ
+ ᱡᱷᱚᱛᱚ ᱴᱮᱵᱽ ᱠᱚ ᱵᱚᱸᱫᱚᱭ ᱢᱮ
- ᱴᱮᱵ ᱠᱚ ᱦᱟᱹᱴᱤᱧ ᱢᱮ
+ ᱴᱮᱵᱽ ᱠᱚ ᱦᱟᱹᱴᱤᱧ ᱢᱮ
- ᱴᱮᱵ ᱠᱚ ᱛᱩᱢᱟᱹᱞ ᱨᱮ ᱥᱟᱸᱪᱟᱣ ᱢᱮ
+ ᱴᱮᱵᱽ ᱠᱚ ᱛᱩᱢᱟᱹᱞ ᱨᱮ ᱥᱟᱸᱪᱟᱣ ᱢᱮᱴᱮᱵ ᱢᱮᱱᱭᱩ
- ᱴᱮᱵ ᱦᱟᱹᱴᱤᱸᱧ ᱢᱮ
+ ᱴᱮᱵ ᱦᱟᱹᱴᱤᱧ ᱢᱮᱢᱮᱴᱟᱣ ᱢᱮᱥᱟᱸᱪᱟᱣ ᱢᱮ
- ᱦᱟᱹᱴᱤᱝ ᱢᱮ
+ ᱦᱟᱹᱴᱤᱧ ᱢᱮCurrent session image
@@ -661,7 +671,7 @@
ᱚᱪᱚᱜ ᱢᱮ
- ᱦᱤᱛᱟᱹᱞ ᱠᱷᱚᱱ ᱢᱮᱴᱟᱣ ᱢᱮ
+ ᱱᱟᱜᱟᱢ ᱠᱷᱚᱱ ᱢᱮᱴᱟᱣ ᱢᱮ%1$s (ᱯᱨᱭᱣᱮᱴ ᱢᱳᱰ)
@@ -669,11 +679,11 @@
- ᱦᱤᱛᱟᱹᱞ ᱢᱮᱴᱟᱣ ᱢᱮ
+ ᱱᱟᱜᱟᱢ ᱢᱮᱴᱟᱣ ᱢᱮ
- ᱪᱮᱫ ᱟᱢ ᱜᱚᱴᱟ ᱛᱮ ᱢᱮᱱᱟᱢᱼᱟ ᱟᱢᱟᱜ ᱦᱤᱛᱟᱹᱞ ᱢᱮᱴᱟᱣ ᱞᱟᱹᱜᱤᱛ ᱛᱮ?
+ ᱪᱮᱫ ᱟᱢ ᱜᱚᱴᱟ ᱛᱮ ᱢᱮᱱᱟᱢᱼᱟ ᱟᱢᱟᱜ ᱱᱟᱜᱟᱢ ᱢᱮᱴᱟᱣ ᱞᱟᱹᱜᱤᱛ ᱛᱮ?
- ᱦᱤᱛᱟᱹᱞ ᱢᱮᱴᱟᱣᱮᱱᱟ
+ ᱱᱟᱜᱟᱢ ᱢᱮᱴᱟᱣᱮᱱᱟ%1$s ᱢᱮᱴᱟᱣᱮᱱᱟ
@@ -681,7 +691,7 @@
ᱱᱚᱠᱚᱞ ᱢᱮ
- ᱦᱟᱹᱴᱤᱝ ᱢᱮ
+ ᱦᱟᱹᱴᱤᱧ ᱢᱮᱱᱟᱶᱟ ᱴᱮᱵ ᱨᱮ ᱡᱷᱤᱡ ᱢᱮ
@@ -707,7 +717,7 @@
ᱢᱟᱨᱮᱭᱟᱜ
- ᱱᱚᱰᱮ ᱦᱤᱛᱟᱹᱞ ᱵᱚᱱᱩᱜ-ᱟ
+ ᱱᱚᱰᱮ ᱱᱟᱜᱟᱢ ᱵᱚᱱᱩᱜ-ᱟ
@@ -767,8 +777,6 @@
%1$s ᱢᱮᱴᱟᱣᱮᱱᱟᱯᱚᱴᱚᱢ ᱥᱮᱞᱮᱫᱽ ᱢᱮ
-
- ᱵᱩᱠᱢᱟᱨᱠ ᱵᱮᱱᱟᱣᱱᱟ ᱾ᱵᱩᱠᱢᱟᱨᱠ ᱥᱟᱸᱪᱟᱣᱮᱱᱟ!
@@ -871,15 +879,15 @@
ᱪᱟᱹᱞᱩᱵᱚᱸᱫᱚ
-
+
ᱟᱰᱤᱭᱳ ᱟᱨ ᱣᱤᱰᱤᱭᱳ ᱦᱮᱥᱟᱨᱤᱭᱟᱹ ᱢᱮᱥᱮᱞᱩᱞᱟᱹᱨ ᱰᱟᱴᱟ ᱨᱮᱜᱤ ᱟᱰᱤᱭᱳ ᱟᱨ ᱣᱤᱰᱤᱭᱳ ᱟᱴᱠᱟᱣ ᱢᱮᱟᱰᱤᱭᱳ ᱟᱨ ᱣᱤᱰᱤᱭᱳ Wi-Fi ᱨᱮ ᱮᱱᱮᱡᱚᱣᱟ
-
+
ᱟᱰᱤᱭᱳ ᱜᱤ ᱟᱴᱠᱟᱣ ᱢᱮ
-
+
ᱟᱰᱤᱭᱳ ᱟᱨ ᱣᱤᱰᱤᱭᱳ ᱟᱴᱠᱟᱣ ᱢᱮᱪᱚᱞᱩ
@@ -893,9 +901,9 @@
ᱛᱩᱢᱟᱹᱞ ᱢᱮᱱᱭᱩ
- ᱡᱤᱱᱤᱥ ᱠᱚ ᱡᱟᱣᱨᱟᱭ ᱢᱮ ᱚᱠᱟ ᱟᱢ ᱞᱟᱹᱜᱤᱫ ᱴᱷᱤᱠ ᱟ ᱾ \nᱞᱚᱜᱚᱱ ᱧᱟᱢ ᱞᱟᱹᱜᱤᱫ ᱥᱚᱢᱟᱱ ᱥᱮᱸᱫᱽᱨᱟ, ᱥᱟᱭᱤᱴ ᱟᱨ ᱴᱮᱵ ᱠᱚ ᱫᱳᱞ ᱠᱟᱜ ᱢᱮ ᱾
+ ᱡᱤᱱᱤᱥ ᱠᱚ ᱡᱟᱣᱨᱟᱭ ᱢᱮ ᱚᱠᱟ ᱟᱢ ᱞᱟᱹᱜᱤᱫ ᱴᱷᱤᱠ ᱟ ᱾ \nᱞᱚᱜᱚᱱᱛᱮ ᱧᱟᱢ ᱞᱟᱹᱜᱤᱫ ᱥᱚᱢᱟᱱ ᱥᱮᱸᱫᱽᱨᱟ, ᱥᱟᱭᱤᱴ ᱟᱨ ᱴᱮᱵᱽ ᱠᱚ ᱫᱳᱞ ᱠᱟᱜ ᱢᱮ ᱾
- ᱴᱮᱵ ᱠᱚ ᱵᱟᱪᱷᱟᱣ ᱢᱮ
+ ᱴᱮᱵᱽ ᱠᱚ ᱵᱟᱪᱷᱟᱣ ᱢᱮᱛᱩᱢᱟᱹᱞ ᱵᱟᱪᱷᱟᱣ ᱢᱮ
@@ -907,15 +915,15 @@
ᱡᱚᱛᱚ ᱠᱚ ᱵᱟᱝ ᱵᱟᱪᱷᱟᱣ ᱢᱮ
- ᱥᱟᱸᱪᱟᱣ ᱞᱟᱹᱜᱤᱛ ᱛᱮ ᱴᱮᱵ ᱠᱚ ᱵᱟᱪᱷᱟᱣ ᱢᱮ
+ ᱥᱟᱸᱪᱟᱣ ᱞᱟᱹᱜᱤᱛ ᱛᱮ ᱴᱮᱵᱽ ᱠᱚ ᱵᱟᱪᱷᱟᱣ ᱢᱮ
- %d ᱴᱮᱵ ᱠᱚ ᱵᱟᱪᱷᱟᱣ ᱟᱠᱟᱱᱟ
+ %d ᱴᱮᱵᱽ ᱠᱚ ᱵᱟᱪᱷᱟᱣ ᱟᱠᱟᱱᱟ
- %d ᱴᱮᱵ ᱵᱟᱪᱷᱟᱣ ᱟᱠᱟᱱᱟ
+ %d ᱴᱮᱵᱽ ᱵᱟᱪᱷᱟᱣ ᱟᱠᱟᱱᱟ
- ᱴᱮᱵ ᱠᱚ ᱥᱟᱸᱪᱟᱣᱮᱱᱟ!
+ ᱴᱮᱵᱽ ᱠᱚ ᱥᱟᱸᱪᱟᱣᱮᱱᱟ!ᱛᱩᱢᱟᱹᱞ ᱥᱟᱸᱪᱟᱣᱮᱱᱟ!
@@ -969,7 +977,7 @@
ᱥᱟᱫᱷᱚᱱ ᱠᱚ ᱡᱚᱲᱟᱣ ᱵᱚᱱᱩᱜ-ᱟ
- ᱴᱮᱵ ᱠᱚ ᱮᱢᱟᱱ ᱵᱷᱮᱡᱟ ᱵᱟᱵᱚᱛ ᱵᱟᱰᱟᱭ ᱢᱮ…
+ ᱴᱮᱵᱽ ᱠᱚ ᱮᱢᱟᱱ ᱵᱷᱮᱡᱟ ᱵᱟᱵᱚᱛ ᱵᱟᱰᱟᱭ ᱢᱮ…ᱚᱞᱜᱟ ᱥᱟᱫᱷᱚᱱ ᱡᱩᱰᱟᱹᱣ ᱢᱮ…
@@ -977,9 +985,9 @@
ᱱᱤᱡᱮᱨᱟᱜ ᱵᱨᱟᱩᱡᱤᱝ ᱚᱠᱛᱚ
- ᱯᱨᱭᱣᱮᱴ ᱴᱮᱵ ᱠᱚ ᱢᱮᱴᱟᱣ ᱢᱮ
+ ᱯᱨᱟᱭᱣᱮᱴ ᱴᱮᱵᱽ ᱠᱚ ᱢᱮᱴᱟᱣ ᱢᱮ
- ᱯᱨᱭᱣᱮᱴ ᱴᱮᱵ ᱠᱚ ᱵᱚᱸᱫᱚᱭ ᱢᱮ
+ ᱯᱨᱟᱭᱣᱮᱴ ᱴᱮᱵᱽ ᱠᱚ ᱵᱚᱸᱫᱚᱭ ᱢᱮᱡᱷᱤᱡ ᱢᱮ
@@ -993,13 +1001,13 @@
ᱴᱮᱵ ᱢᱮᱴᱟᱣᱮᱱᱟ
- ᱴᱮᱵ ᱠᱚ ᱢᱮᱴᱟᱣᱮᱱᱟ
+ ᱴᱮᱵᱽ ᱠᱚ ᱢᱮᱴᱟᱣᱮᱱᱟᱴᱮᱵ ᱵᱚᱸᱫᱚᱭᱮᱱᱟ
- ᱴᱮᱵ ᱠᱚ ᱵᱚᱸᱫᱚᱭᱮᱱᱟ
+ ᱴᱮᱵᱽ ᱠᱚ ᱵᱚᱸᱫᱚᱭᱮᱱᱟ
- ᱴᱮᱵ ᱠᱚ ᱵᱚᱸᱫᱽ ᱮᱱᱟ!
+ ᱴᱮᱵᱽ ᱠᱚ ᱵᱚᱸᱫᱽ ᱮᱱᱟ!ᱵᱩᱻᱠᱢᱟᱨᱠ ᱠᱚ ᱥᱟᱺᱪᱟᱣ ᱮᱱᱟ!
@@ -1009,9 +1017,9 @@
ᱱᱤᱡᱮᱨᱟᱜ ᱴᱮᱵ ᱵᱚᱸᱫᱚᱭᱮᱱᱟ
- ᱱᱤᱡᱮᱨᱟᱜ ᱴᱮᱵ ᱠᱚ ᱵᱚᱸᱫᱚᱭᱮᱱᱟ
+ ᱯᱨᱟᱭᱣᱮᱴ ᱴᱮᱵᱽ ᱠᱚ ᱵᱚᱸᱫᱚᱭᱮᱱᱟ
- ᱱᱤᱡᱮᱨᱟᱜ ᱴᱮᱵ ᱠᱚ ᱢᱮᱴᱟᱣᱮᱱᱟ
+ ᱯᱨᱟᱭᱣᱮᱴ ᱴᱮᱵᱽ ᱠᱚ ᱢᱮᱴᱟᱣᱮᱱᱟᱚᱱᱰᱩ
@@ -1056,18 +1064,18 @@
ᱪᱤᱠᱤ ᱢᱟᱞ ᱫᱚ ᱮᱱᱰᱨᱚᱭᱮᱰ ᱥᱟᱡᱟᱣ ᱥᱟᱞᱟᱜ ᱥᱚᱢᱟᱱᱚᱜᱼᱟ ᱾ ᱪᱤᱠᱤ ᱢᱟᱯ ᱵᱚᱸᱫᱚ ᱞᱟᱹᱜᱤᱫ ᱱᱚᱰᱮ ᱢᱮᱱᱮᱡ ᱢᱮ ᱾
- ᱵᱨᱟᱩᱡᱤᱝ ᱰᱟᱴᱟ ᱢᱮᱴᱟᱣ
+ ᱵᱽᱨᱳᱣᱡᱤᱝ ᱰᱟᱴᱟ ᱢᱮᱴᱟᱣ
- ᱡᱷᱤᱡ ᱴᱮᱵ ᱠᱚ
+ ᱡᱷᱤᱡᱽ ᱴᱮᱵᱽ ᱠᱚ
- %d ᱴᱮᱵ ᱠᱚ
+ %d ᱴᱮᱵᱽ ᱠᱚ
- ᱵᱨᱟᱩᱡᱤᱝ ᱦᱤᱛᱟᱹᱞ ᱟᱨ ᱥᱟᱭᱤᱴ ᱰᱟᱴᱟ
+ ᱵᱨᱟᱩᱡᱤᱝ ᱱᱟᱜᱟᱢ ᱟᱨ ᱥᱟᱭᱤᱴ ᱰᱟᱴᱟ%d ᱴᱷᱤᱠᱬᱟᱹ ᱠᱚ
- ᱦᱤᱛᱟᱹᱞ
+ ᱱᱟᱜᱟᱢ%d ᱥᱟᱦᱴᱟ ᱠᱚ
@@ -1081,10 +1089,12 @@
ᱡᱚᱜᱟᱣ ᱡᱟᱭᱜᱟ ᱠᱷᱚᱞᱤ ᱛᱟᱦᱮᱱ ᱟᱭᱥᱟᱭᱤᱴ ᱨᱮᱭᱟᱜ ᱪᱷᱟᱹᱲ ᱠᱚ
+
+ ᱰᱟᱩᱱᱞᱳᱰ ᱠᱚ
- ᱵᱨᱟᱩᱡᱤᱝ ᱰᱟᱴᱟ ᱢᱮᱴᱟᱣ ᱢᱮ
+ ᱵᱽᱨᱳᱣᱡᱤᱝ ᱰᱟᱴᱟ ᱢᱮᱴᱟᱣ ᱢᱮ
- ᱵᱨᱟᱩᱡᱤᱝ ᱰᱟᱴᱟ ᱢᱮᱴᱟᱣ ᱢᱮ ᱟᱲᱟᱜ ᱜᱷᱚᱲᱤ
+ ᱵᱽᱨᱳᱣᱡᱤᱝ ᱰᱟᱴᱟ ᱢᱮᱴᱟᱣ ᱢᱮ ᱟᱲᱟᱜ ᱜᱷᱚᱲᱤᱵᱨᱟᱩᱡᱤᱝ ᱰᱟᱴᱟ ᱟᱪᱛᱮ ᱢᱮᱴᱟᱣ ᱛᱟᱦᱮᱱᱟᱭ ᱡᱚᱠᱷᱚᱱ ᱟᱢ \"ᱟᱲᱟᱹᱜ\" ᱵᱟᱪᱷᱟᱣ ᱛᱟᱦᱮᱱᱟᱢ ᱢᱩᱞ ᱢᱮᱱᱩ ᱠᱷᱚᱱ
@@ -1140,9 +1150,6 @@
%s ᱨᱮ ᱥᱟᱹᱜᱩᱱ ᱫᱟᱨᱟᱢ!ᱞᱟᱦᱟ ᱠᱷᱚᱱ ᱠᱷᱟᱛᱟ ᱢᱮᱱᱟᱜ ᱛᱟᱢᱟ?
-
- %s ᱵᱟᱵᱚᱛ ᱵᱟᱲᱟᱭ ᱢᱮᱧᱮᱞ ᱢᱮ ᱪᱮᱫ ᱱᱟᱶᱟ ᱢᱮᱱᱟᱜ-ᱟᱱᱚᱰᱮ ᱛᱮᱞᱟ ᱠᱚ ᱧᱟᱢ ᱟᱢ
-
- ᱵᱩᱠᱢᱟᱨᱠ, ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫ ᱟᱨ ᱵᱟᱹᱲᱛᱤ ᱠᱚ ᱞᱟᱹᱜᱤᱛ ᱟᱢᱟᱜ Firefox ᱠᱷᱟᱛᱟ ᱨᱮ ᱥᱭᱸᱠ ᱮᱦᱚᱵᱽ ᱢᱮ ᱾
-
- ᱰᱷᱮᱨ ᱥᱮᱬᱟᱭ ᱢᱮ
@@ -1162,42 +1165,25 @@
ᱦᱮᱸ, ᱵᱚᱞᱚ ᱪᱷᱚᱭᱤᱧ ᱢᱮᱵᱚᱞᱚᱱᱟ ᱠᱟᱹᱱᱟᱹᱧ…
-
- Firefox ᱨᱮ ᱵᱚᱞᱚᱱ ᱥᱩᱦᱤ ᱢᱮᱵᱟᱦᱨᱮ ᱨᱮᱜᱮ ᱛᱟᱦᱮᱸ ᱠᱚᱜ ᱢᱮᱥᱭᱸᱠ ᱪᱟᱹᱞᱩ ᱢᱮᱱᱟᱜ-ᱟᱵᱚᱞᱚᱱ ᱥᱩᱦᱤ ᱰᱤᱜᱟᱹᱣᱮᱱᱟ
-
- ᱟᱡ ᱛᱮᱭᱟᱜ ᱱᱤᱥᱚᱱ
-
- ᱯᱨᱭᱣᱮᱥᱭ ᱟᱨ ᱨᱩᱠᱷᱤᱭᱟᱹ ᱥᱟᱡᱟᱣ ᱯᱟᱧᱡᱟ ᱫᱟᱱᱟᱲ, ᱢᱟᱞᱣᱮᱨ ᱟᱨ ᱠᱚᱢᱯᱟᱭ ᱠᱚ ᱮᱢᱟᱱ ᱵᱞᱚᱠ ᱠᱚᱣᱟ ᱠᱚ ᱚᱠᱚᱭ ᱫᱚ ᱟᱢ ᱯᱟᱧᱡᱟ ᱠᱟᱫ ᱢᱮᱭᱟ ᱾ᱵᱮᱥᱟᱜ (ᱢᱩᱞ ᱯᱷᱮᱲᱟᱛ)
-
- ᱯᱟᱧᱡᱟ ᱫᱟᱱᱟᱲ ᱠᱚ ᱵᱞᱚᱠ ᱠᱚᱣᱟᱭ ᱾ ᱥᱟᱦᱴᱟ ᱫᱚ ᱢᱟᱲᱟᱝ ᱞᱮᱠᱷᱟ ᱜᱮ ᱞᱟᱫᱮᱜᱼᱟ ᱾ᱱᱤᱦᱟᱹᱛ (ᱵᱟᱛᱟᱣᱮᱢᱠᱟᱱᱟ)ᱱᱤᱦᱟᱹᱛ
-
- ᱡᱟᱹᱥᱛᱤ ᱯᱟᱧᱡᱟ ᱫᱟᱱᱟᱲ, ᱰᱷᱟᱣᱨᱟ ᱟᱨ ᱴᱟᱴᱠᱟ ᱚᱰᱚᱠ ᱠᱚ ᱟᱴᱠᱟᱣ ᱟᱭ , ᱥᱟᱠᱟᱢ ᱞᱚᱜᱚᱱ ᱞᱚᱰᱚᱜᱼᱟ ᱦᱮᱞᱮ ᱠᱤᱪᱷᱤ ᱡᱤᱱᱤᱥ ᱴᱷᱤᱠ ᱥᱮ ᱵᱟᱭ ᱠᱟᱹᱢᱤᱟᱭ ᱾
-
- ᱡᱟᱭᱜᱟ ᱦᱟᱛᱟᱣ ᱢᱮ
-
- ᱞᱟᱛᱟᱨ ᱴᱩᱞᱵᱟᱨ ᱥᱟᱞᱟᱜ ᱢᱤᱫ ᱛᱤ ᱵᱨᱟᱣᱡᱤᱝ ᱥᱟᱞᱟᱜ ᱪᱮᱛᱟᱱ ᱥᱮᱱ ᱨᱟᱠᱟᱵ ᱨᱮᱭᱟᱜ ᱪᱮᱥᱴᱟᱭ ᱢᱮ ᱾ᱱᱤᱡᱮᱨᱟᱜ ᱨᱮ ᱵᱨᱟᱩᱡ ᱢᱮᱢᱤᱫ ᱡᱮᱠᱷᱟ ᱯᱨᱭᱣᱮᱴ ᱴᱮᱵ ᱠᱷᱩᱞᱟᱹᱭ ᱢᱮ: %s ᱟᱭᱠᱚᱱ ᱨᱮ ᱴᱤᱯᱟᱹᱣ ᱢᱮ ᱾
- ᱡᱷᱚᱛᱚ ᱵᱮᱲᱟ ᱱᱤᱡᱚᱨᱟᱜ ᱴᱮᱵᱠᱚ ᱠᱷᱩᱞᱟ ᱠᱟᱜᱼᱢᱮ: ᱱᱤᱡᱚᱴᱨᱟᱜ ᱵᱨᱟᱣᱡᱤᱝ ᱥᱟᱡᱟᱣ ᱠᱚ ᱟᱹᱯᱰᱮᱴ ᱢᱮ ᱾
+ ᱡᱷᱚᱛᱚ ᱵᱮᱲᱟ ᱱᱤᱡᱚᱨᱟᱜ ᱴᱮᱵᱽᱠᱚ ᱠᱷᱩᱞᱟ ᱠᱟᱜᱼᱢᱮ: ᱱᱤᱡᱚᱴᱨᱟᱜ ᱵᱨᱟᱣᱡᱤᱝ ᱥᱟᱡᱟᱣ ᱠᱚ ᱟᱹᱯᱰᱮᱴ ᱢᱮ ᱾ᱥᱟᱡᱟᱣ ᱠᱚ ᱡᱷᱤᱡ ᱢᱮ
@@ -1214,8 +1200,6 @@
ᱩᱭᱦᱟᱹᱨ ᱵᱟᱪᱷᱟᱣ ᱛᱟᱢ
-
- ᱛᱤᱱᱟᱹᱜ ᱜᱟᱱ ᱵᱮᱴᱨᱭ ᱵᱚᱪᱚᱛ ᱢᱮ ᱟᱨ ᱟᱢᱟᱜ ᱢᱮᱫ ᱵᱟᱸᱪᱟᱣ ᱛᱟᱢ ᱧᱩᱛ ᱩᱭᱦᱟᱹᱨ ᱪᱟᱹᱞᱩ ᱠᱟᱛᱮ ᱾ᱟᱪᱛᱮ
@@ -1226,7 +1210,7 @@
ᱢᱟᱨᱥᱟᱞ ᱩᱭᱦᱟᱹᱨ
- ᱴᱮᱵ ᱠᱚ ᱠᱩᱞ ᱦᱩᱭᱮᱱᱟ!
+ ᱴᱮᱵᱽ ᱠᱚ ᱠᱩᱞ ᱦᱩᱭᱮᱱᱟ!ᱴᱮᱵ ᱠᱩᱞ ᱦᱩᱭᱮᱱᱟ!
@@ -1236,11 +1220,11 @@
ᱠᱳᱰ ᱥᱠᱮᱱ ᱢᱮ
- https://firefox.com/pair ᱨᱮ ᱪᱟᱞᱟ ᱢᱮ]]>
+ https://firefox.com/pair ᱨᱮ ᱪᱟᱞᱟᱜ ᱢᱮ]]>
- ᱥᱠᱮᱱ ᱞᱚᱜᱤᱛ ᱛᱮ ᱛᱮᱭᱟᱨ ᱢᱮᱱᱟᱢ-ᱟ
+ ᱥᱠᱮᱱ ᱞᱚᱜᱤᱫᱛᱮ ᱛᱮᱭᱟᱨ ᱢᱮᱱᱟᱢᱼᱟ
- ᱟᱢᱟᱜ ᱠᱮᱢᱨᱟ ᱛᱮ ᱵᱚᱞᱚᱱ ᱥᱩᱦᱤ ᱢᱮ
+ ᱟᱢᱟᱜ ᱠᱮᱢᱮᱨᱟ ᱛᱮ ᱵᱚᱞᱚᱱ ᱥᱩᱦᱤ ᱢᱮᱵᱟᱹᱫᱟᱹᱞ ᱛᱮ ᱮᱢᱮᱞ ᱵᱮᱵᱷᱟᱨ ᱢᱮ
@@ -1264,19 +1248,15 @@
ᱯᱟᱸᱡᱟ ᱵᱮᱜᱚᱨ ᱵᱨᱟᱩᱡ ᱢᱮ
- ᱟᱢᱟᱜᱽ ᱰᱟᱴᱟ ᱟᱢ ᱴᱷᱮᱱ ᱜᱮ ᱫᱚᱦᱚᱭ ᱢᱮ ᱾ %s ᱫᱚ ᱟᱭᱢ ᱟᱭᱢᱟ ᱯᱟᱧᱡᱟ ᱫᱟᱱᱟᱲ ᱠᱚ ᱴᱷᱮᱱ ᱠᱷᱚᱱ ᱚᱱᱞᱟᱭᱤᱱ ᱨᱩᱠᱷᱤᱭᱟᱹᱭ ᱢᱮᱟᱭ ᱾
+ ᱟᱢᱟᱜᱽ ᱰᱟᱴᱟ ᱟᱢ ᱴᱷᱮᱱ ᱜᱮ ᱫᱚᱦᱚᱭ ᱢᱮ ᱾ %s ᱫᱚ ᱟᱭᱢᱟ ᱥᱚᱢᱟᱱ ᱯᱟᱧᱡᱟ ᱫᱟᱱᱟᱲ ᱠᱚ ᱴᱷᱮᱱ ᱠᱷᱚᱱ ᱚᱱᱞᱟᱭᱤᱱ ᱨᱩᱠᱷᱤᱭᱟᱹᱭ ᱢᱮᱟᱭ ᱾ᱰᱷᱮᱨ ᱥᱮᱬᱟᱭ ᱢᱮᱱᱟᱯ (ᱢᱩᱞ ᱯᱷᱮᱲᱟᱛ)
-
- ᱠᱚᱢ ᱯᱟᱧᱡᱟ ᱫᱟᱱᱟᱲ ᱠᱚ ᱵᱚᱸᱫᱚᱭᱮᱟ ᱾ ᱥᱟᱦᱴᱟ ᱢᱟᱲᱟᱝ ᱞᱮᱠᱷᱟ ᱞᱟᱫᱮᱜᱼᱟ ᱾ᱢᱩᱞ ᱜᱷᱮᱨ ᱮᱥᱮᱫ ᱨᱚᱠᱷᱭᱟ ᱫᱚ ᱪᱮᱫ ᱮ ᱵᱞᱚᱠ ᱪᱷᱚ ᱠᱮᱜᱼᱟᱭᱱᱤᱦᱟᱹᱛ
-
- ᱡᱟᱹᱥᱛᱤ ᱯᱟᱧᱡᱟ ᱫᱟᱱᱟᱲ, ᱰᱷᱟᱣᱨᱟ ᱟᱨ ᱴᱟᱴᱠᱟ ᱚᱰᱚᱠ ᱠᱚ ᱟᱴᱠᱟᱣ ᱟᱭ , ᱥᱟᱠᱟᱢ ᱞᱚᱜᱚᱱ ᱞᱚᱰᱚᱜᱼᱟ ᱦᱮᱞᱮ ᱠᱤᱪᱷᱤ ᱡᱤᱱᱤᱥ ᱴᱷᱤᱠ ᱥᱮ ᱵᱟᱭ ᱠᱟᱹᱢᱤᱟᱭ ᱾ᱴᱷᱤᱴ ᱜᱷᱮᱨ ᱮᱥᱮᱫ ᱨᱩᱠᱷᱭᱟ ᱫᱚ ᱪᱮᱫ ᱵᱞᱚᱠ ᱠᱮᱜᱼᱟᱭ
@@ -1299,11 +1279,11 @@
ᱯᱟᱧᱡᱟ ᱫᱟᱱᱟᱲ ᱡᱤᱱᱤᱥ
- ᱡᱷᱚᱛᱚ ᱛᱮᱵ ᱠᱚᱨᱮ ᱠᱚ
+ ᱡᱷᱚᱛᱚ ᱛᱮᱵᱽ ᱠᱚᱨᱮ
- ᱠᱷᱟᱹᱞᱤ ᱱᱤᱡᱮᱨᱟᱜ ᱴᱮᱵ ᱠᱚᱨᱮ ᱜᱮ
+ ᱠᱷᱟᱹᱞᱤ ᱯᱨᱟᱭᱣᱮᱴ ᱴᱮᱵᱽ ᱠᱚᱨᱮ ᱜᱮ
- ᱠᱷᱟᱹᱞᱤ ᱠᱩᱥᱤᱭᱟᱜ ᱴᱮᱵ ᱠᱚᱨᱮ
+ ᱠᱷᱟᱹᱞᱤ ᱠᱩᱥᱤᱭᱟᱜ ᱴᱮᱵᱽ ᱠᱚᱨᱮᱠᱨᱭᱯᱴᱚᱢᱟᱭᱱᱟᱹᱨ ᱠᱚ
@@ -1378,7 +1358,7 @@
1 ᱴᱮᱵ
- %d ᱴᱮᱵ ᱠᱚ
+ %d ᱴᱮᱵᱽ ᱠᱚ
@@ -1410,23 +1390,19 @@
ᱞᱚᱜᱤᱱ ᱠᱚ ᱟᱨ ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫ ᱠᱚ ᱥᱟᱸᱪᱟᱣ ᱢᱮ
- ᱥᱟᱸᱪᱟᱣ ᱞᱟᱹᱜᱤᱫ ᱛᱮ ᱠᱩᱠᱞᱤ
+ ᱥᱟᱸᱪᱟᱣ ᱞᱟᱹᱜᱤᱫᱛᱮ ᱠᱩᱠᱞᱤ
- ᱥᱟᱸᱪᱟᱣ ᱛᱤᱥ ᱦᱚᱸ ᱵᱟᱝ
+ ᱛᱤᱥ ᱦᱚᱸ ᱟᱞᱳᱢ ᱥᱟᱸᱪᱟᱣᱼᱟ
- ᱟᱪᱛᱮ ᱯᱮᱨᱮᱡ
+ ᱟᱪᱛᱮ ᱯᱮᱨᱮᱡᱽᱞᱚᱜᱤᱱ ᱠᱚ ᱥᱤᱸᱠ ᱢᱮ
-
- ᱪᱚᱞᱩ
-
- ᱵᱚᱸᱫᱚᱨᱤᱠᱚᱱᱮᱠᱼᱴᱛᱷᱟᱨ ᱞᱟᱹᱜᱤᱛ ᱵᱚᱞᱚᱱ ᱥᱩᱦᱤ ᱢᱮ
- ᱥᱟᱸᱪᱟᱣᱠᱟᱱ ᱞᱚᱜᱤᱱ ᱠᱚ
+ ᱥᱟᱸᱪᱟᱣᱟᱠᱟᱱ ᱞᱚᱜᱤᱱ ᱠᱚᱞᱚᱜᱤᱱ ᱚᱠᱟ %s ᱨᱮ ᱥᱟᱸᱪᱟᱣ ᱟᱨ ᱥᱭᱸᱠ ᱥᱟᱱᱟᱢ ᱠᱟᱱᱟ ᱚᱱᱟ ᱠᱚ ᱱᱚᱰᱮ ᱫᱮᱠᱷᱟᱣᱜᱼᱟ ᱾
@@ -1438,7 +1414,7 @@
ᱞᱚᱜᱤᱱᱥ ᱟᱨ ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫ ᱫᱚ ᱱᱚᱶᱟ ᱥᱟᱭᱤᱴ ᱞᱟᱹᱜᱤᱫ ᱵᱟᱝ ᱥᱟᱸᱪᱟᱣᱜᱼᱟ ᱾
- ᱡᱚᱛᱚ ᱪᱷᱟᱰᱟ ᱠᱚ ᱢᱮᱴᱟᱣ ᱢᱮ
+ ᱡᱷᱚᱛᱚ ᱪᱷᱟᱰᱟᱠᱚ ᱢᱮᱴᱟᱣ ᱢᱮᱞᱚᱜᱤᱱ ᱠᱚ ᱥᱮᱸᱫᱽᱨᱟᱭ ᱢᱮ
@@ -1641,9 +1617,9 @@
ᱫᱚᱭᱟᱠᱟᱛᱮ ᱴᱮᱵ ᱥᱭᱸᱠᱤᱝ ᱮᱢ ᱪᱷᱚᱭ ᱢᱮ ᱾
- ᱟᱢ ᱴᱷᱮᱱ ᱚᱠᱟ ᱦᱚᱸ ᱴᱮᱵ ᱠᱚ ᱚᱞᱜᱟ Firefox ᱢᱮᱱᱟᱜ ᱥᱟᱫᱷᱚᱱ ᱨᱮ ᱵᱟᱝ ᱠᱷᱩᱞᱟᱹ ᱟᱠᱟᱱᱟ ᱾
+ ᱟᱢ ᱴᱷᱮᱱ ᱚᱠᱟ ᱦᱚᱸ ᱴᱮᱵᱽ ᱠᱚ ᱚᱞᱜᱟ Firefox ᱢᱮᱱᱟᱜ ᱥᱟᱫᱷᱚᱱ ᱨᱮ ᱵᱟᱝ ᱠᱷᱩᱞᱟᱹ ᱟᱠᱟᱱᱟ ᱾
- ᱟᱢᱟᱜ ᱮᱴᱟᱜ ᱥᱟᱫᱷᱚᱱ ᱠᱷᱚᱱ ᱴᱮᱵ ᱞᱤᱥᱴᱤ ᱫᱮᱠᱷᱟᱣ ᱢᱮ ᱾
+ ᱟᱢᱟᱜ ᱮᱴᱟᱜ ᱥᱟᱫᱷᱚᱱ ᱠᱷᱚᱱ ᱴᱮᱵᱽ ᱞᱤᱥᱴᱤ ᱫᱮᱠᱷᱟᱣ ᱢᱮ ᱾ᱛᱷᱟᱨ ᱞᱟᱹᱜᱤᱛ ᱵᱚᱞᱚᱱ ᱥᱩᱦᱤ ᱢᱮ
@@ -1671,19 +1647,7 @@
ᱚᱪᱟᱜ
-
- %s ᱠᱷᱚᱱ ᱡᱟᱹᱥᱛᱤ ᱡᱤᱱᱤᱥ ᱵᱟᱰᱟᱭ ᱢᱮ ᱾
-
ᱟᱨᱦᱚᱸ ᱵᱤᱵᱨᱚᱬ ᱞᱟᱹᱜᱤᱫ ᱱᱚᱰᱮ ᱚᱛᱟᱭ ᱢᱮ
-
- ᱚᱱᱟ ᱡᱤᱱᱤᱥ ᱠᱚ ᱡᱟᱨᱣᱟᱭ ᱢᱮ ᱡᱟ ᱟᱢ ᱢᱮᱴᱚᱨ ᱢᱤᱭᱟ
-
- ᱥᱚᱢᱟᱱ ᱥᱮᱸᱫᱽᱨᱟ, ᱥᱟᱭᱤᱴ ᱟᱨ ᱴᱮᱵ ᱠᱚ ᱛᱟᱭᱚᱢ ᱛᱮ ᱞᱚᱜᱚᱱ ᱥᱮᱴᱮᱨ ᱞᱟᱹᱜᱤᱫ ᱫᱳᱞ ᱛᱮᱨᱟᱴ ᱠᱟᱜ ᱢᱮ ᱾
-
- ᱟᱢ %s ᱞᱮᱠᱷᱟᱛᱮ ᱚᱞᱜᱟ Firefox ᱵᱨᱟᱩᱡᱟᱹᱨ ᱨᱮ ᱟᱢᱟᱜ ᱥᱟᱫᱷᱚᱱ ᱛᱮ ᱵᱚᱞᱚ ᱟᱠᱟᱱᱟᱢ ᱾ ᱟᱢ ᱪᱮᱫ ᱱᱚᱶᱟ ᱠᱷᱟᱛᱟ ᱨᱮ ᱵᱚᱞᱚ ᱥᱟᱱᱟᱢ ᱠᱟᱱᱟ?
-
- ᱟᱢ ᱞᱚᱜᱚᱱ ᱵᱨᱟᱩᱡ ᱟᱨ ᱮᱯᱯ ᱞᱮᱠᱷᱟᱱ ᱚᱱᱩᱵᱷᱟᱹᱵ ᱤᱫᱤ ᱞᱟᱹᱜᱤᱫ ᱣᱮᱵᱥᱟᱭᱤᱴ ᱫᱚ ᱥᱟᱫᱷᱚᱱ ᱨᱮᱭᱟᱜ ᱚᱲᱟᱜ ᱥᱠᱨᱭᱤᱱ ᱨᱮ ᱥᱮᱞᱮᱫᱽ ᱫᱟᱲᱮᱟᱜᱼᱟᱢ ᱾
-
+
diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml
index 1b9ad213f..a8da36f25 100644
--- a/app/src/main/res/values-sk/strings.xml
+++ b/app/src/main/res/values-sk/strings.xml
@@ -50,7 +50,7 @@
Vybraná
- %1$s vyvíja @fork-maintainers.
+ %1$s vyvíja Mozilla.
@@ -299,7 +299,7 @@
Vlastný server pre synchronizáciu
- Server pre účet Fierfox alebo synchronizáciu bol zmenený. Pre použitie zmien sa teraz aplikácia ukončí…
+ Server pre účet Firefox alebo synchronizáciu bol zmenený. Pre použitie zmien sa teraz aplikácia ukončí…Účet
@@ -357,7 +357,7 @@
Otvárať odkazy v aplikáciách
- Externý správca preberania súborov
+ Externý správca sťahovaniaDoplnky
@@ -420,6 +420,11 @@
and the third is the device model. -->
%1$s na %2$s %3$s
+
+ Platobné karty
+
+ Adresy
+
Prijaté karty
@@ -452,9 +457,6 @@
Ďalšie informácie
-
- Ochranu pred sledovaním môžete opätovne zapnúť v nastaveniach.
-
Telemetria
@@ -465,6 +467,8 @@
Marketingové údajeZdieľa údaje o používaní funkcií v aplikácii %1$s so spoločnosťou Leanplum, našim partnerom v oblasti mobilného marketingu.
+
+ Zdieľa základné údaje o používaní so spoločnosťou Adjust, našim partnerom pre marketingŠtúdie
@@ -533,7 +537,7 @@
Snímky obrazovky
- Prevzaté súbory
+ Stiahnuté súboryZáložky
@@ -612,6 +616,8 @@
Súkromné prehliadanieSúkromné karty
+
+ Synchronizované kartyPridať kartu
@@ -630,6 +636,8 @@
Zobraziť všetky kartyNedávno zatvorené karty
+
+ Nastavenia účtuNastavenia kariet
@@ -746,15 +754,15 @@
- Vymazať zoznam prevzatých súborov
+ Vymazať zoznam stiahnutých súborov
- Naozaj chcete vymazať svoju históriu preberania?
+ Naozaj chcete vymazať svoju históriu sťahovania?
- Prevzaté súbory boli odstránené
+ Stiahnuté súbory boli odstránenéSúbor %1$s bol odstránený
- Žiadne prevzaté súbory
+ Žiadne stiahnuté súboryPočet vybraných položiek: %1$d
@@ -800,8 +808,6 @@
Bol odstránený priečinok %1$sPridanie priečinku
-
- Záložka bola vytvorená.Záložka bola uložená!
@@ -891,7 +897,7 @@
Obsah chránený pomocou DRM
- Vždy sa spýtať
+ Vždy sa opýtaťZablokované
@@ -987,10 +993,8 @@
Všetky akcieNaposledy použité
-
- Prihlásený ako %1$s
- Prihlásiť sa k synchronizácii
+ Prihlásiť sa a synchronizovaťPrihlásiť sa k službe Sync
@@ -1125,12 +1129,12 @@
Povolenia stránok
- Prevzaté súbory
+ Stiahnuté súboryOdstrániť údaje o prehliadaní
- Odstrániť údaje o prehliadaní pri ukončení aplikácie
+ Odstraňovať údaje o prehliadaní pri ukončení aplikácieAutomaticky odstráni údaje o prehliadaní po stlačení tlačidla „Ukončiť“ v hlavnej ponuke
@@ -1157,7 +1161,7 @@
Firefox Preview je odteraz Firefox NightlyFirefox Nightly je aktualizovaný každý deň a obsahuje experimentálne funkcie.
- Môže však byť menej stabilný. Ak chcete stabilnejší prehliadač, prevezmite si beta verziu.
+ Môže však byť menej stabilný. Ak chcete stabilnejší prehliadač, stiahnite si beta verziu.Prevezmite si Firefox pre Android Beta
@@ -1216,7 +1220,7 @@
Firefox automaticky zabraňuje spoločnostiam tajne vás sledovať pri prehliadaní webu.
- Štandardná (predvolené nastavenie)
+ Štandardná (predvolená)Vyvážená ochrana a výkon. Neovplyvní načítanie webových stránok.
@@ -1278,7 +1282,7 @@
Naskenovať kód
- https://firefox.com/pair]]>
+ https://firefox.com/pair]]>Skenovanie je pripravené
@@ -1310,7 +1314,7 @@
Ďalšie informácie
- Štandardná (predvolené nastavenie)
+ Štandardná (predvolená)Vyvážená ochrana a výkon. Neovplyvní načítanie webových stránok.
@@ -1452,17 +1456,15 @@
Ukladanie prihlasovacích údajov
- Pred uložením sa spýtať
+ Pred uložením sa opýtaťNeukladaťAutomatické dopĺňanieSynchronizácia prihlasovacích údajov
-
- Zapnutá
-
- Vypnutá
+
+ Synchronizovať prihlasovacie údaje medzi zariadeniamiZnova pripojiť
@@ -1562,6 +1564,8 @@
Údaje sú šifrovanéSynchronizovať karty naprieč zariadeniami
+
+ Synchronizovať platobné kartyPridať platobnú kartu
@@ -1569,6 +1573,8 @@
Spravovať uložené kartyPridať kartu
+
+ Upraviť kartuČíslo karty
@@ -1577,6 +1583,8 @@
Meno na kartePrezývka karty
+
+ Odstrániť kartuOdstrániť kartu
@@ -1588,6 +1596,9 @@
Uložené karty
+
+ Prosím, zadajte platné číslo platobnej karty
+
Pridať vyhľadávací modul
@@ -1722,7 +1733,7 @@
Zobraziť zoznam kariet z ostatných zariadení.
- Prihlásiť sa k službe Sync
+ Prihlásiť sa a synchronizovaťŽiadne otvorené karty
@@ -1747,23 +1758,19 @@
Zrušiť
+
+ Nastavte si automatické otváranie webových stránok, e-mailov a správ vo Firefoxe.
+
Odstrániť
-
- Využite %s naplno.
-
Kliknutím zobrazíte viac podrobností
-
- Zbierajte veci, na ktorých vám záleží
-
- Zoskupte podobné vyhľadávania, weby a karty, aby ste k nim mali rýchly prístup neskôr.
-
- V inom Firefoxe na tomto telefóne ste prihlásení ako %s. Chceli by ste sa prihlásiť s týmto účtom?
-
- Túto webovú stránku si môžete jednoducho pridať na svoju úvodnú obrazovku a mať tak okamžitý prístup k prehliadaniu.
+
+ Prejsť nahor
+
+
+ Zavrieť
-
+
diff --git a/app/src/main/res/values-sl/strings.xml b/app/src/main/res/values-sl/strings.xml
index 29a4c5174..f3ff182a1 100644
--- a/app/src/main/res/values-sl/strings.xml
+++ b/app/src/main/res/values-sl/strings.xml
@@ -416,6 +416,11 @@
and the third is the device model. -->
%1$s na %2$s %3$s
+
+ Kreditne kartice
+
+ Naslove
+
Prejeti zavihki
@@ -447,9 +452,6 @@
Več o tem
-
- Izklopljeno povsod, pojdite v Nastavitve in ga vklopite.
-
Telemetrija
@@ -632,6 +634,8 @@
Deli vse zavihkeNedavno zaprti zavihki
+
+ Nastavitve računaNastavitve zavihkov
@@ -802,8 +806,6 @@
Mapa %1$s izbrisanaDodaj mapo
-
- Zaznamek ustvarjen.Zaznamek shranjen!
@@ -1194,6 +1196,8 @@
Tukaj poiščite odgovoreSinhronizirajte Firefox med napravami
+
+ Prinesite zaznamke, zgodovino in gesla v Firefox na tej napravi.
@@ -1210,12 +1214,24 @@
Sync je vklopljenPrijava ni uspela
+
+ Vedno vključena zasebnost
+
+ Firefox samodejno prepreči, da bi vas podjetja na skrivaj spremljala po spletu.Običajno (privzeto)
+
+ Uravnotežena zasebnost in učinkovitost delovanja. Strani bodo delovale običajno.Strogo (priporočljivo)Strogo
+
+ Zavrača več sledilcev in pospeši nalaganje strani, vendar deli strani lahko nehajo delovati.
+
+ Izberite postavitev orodne vrstice
+
+ Postavite si orodno vrstico na doseg roke. Naj bo na dnu ali pa jo premaknite na vrh.Brskajte zasebnoIzberite si temo
+
+ Prihranite nekaj baterije in sprostite oči s temnim načinom.Samodejno
@@ -1296,10 +1314,14 @@
Več o temObičajno (privzeto)
+
+ Uravnotežena zasebnost in učinkovitost delovanja. Strani bodo delovale običajno.Kaj je zavrnjeno s standardno zaščito pred sledenjemStrogo
+
+ Zavrača več sledilcev in pospeši nalaganje strani, vendar deli strani lahko nehajo delovati.Kaj je zavrnjeno s strogo zaščito pred sledenjem
@@ -1443,10 +1465,6 @@
Samodejno izpolnjevanjeSinhronizacija prijav
-
- Vključeno
-
- IzključenoPonovno poveži
@@ -1543,6 +1561,8 @@
Kreditne kartice
+
+ Shrani in samodejno izpolni karticePodatki so šifrirani
@@ -1550,14 +1570,22 @@
Dodaj kreditno kartico
+
+ Upravljanje shranjenih karticDodaj kartico
+
+ Uredi karticoŠtevilka karticeDatum potekaIme na kartici
+
+ Vzdevek kartice
+
+ Izbriši karticoIzbriši kartico
@@ -1567,6 +1595,12 @@
Prekliči
+
+ Shranjene kartice
+
+
+ Vnesite veljavno številko kreditne kartice
+
Dodaj iskalnik
@@ -1729,23 +1763,16 @@
Prekliči
+
+ Nastavite, naj se povezave s spletnih strani, e-pošte in sporočil samodejno odpirajo v Firefoxu.
+
Odstrani
-
- Kar najbolje izkoristite %s.
-
Kliknite za podrobnosti
-
- Zbirajte stvari, ki vam kaj pomenijo
-
- Združite podobna iskanja, spletne strani in zavihke za hitrejši dostop.
-
- V drugem brskalniku Firefox na tem telefonu ste prijavljeni kot %s. Ali se želite prijaviti s tem računom?
-
- To stran lahko preprosto dodate na svoj domači zaslon telefona za lažji dostop in hitrejše brskanje v načinu, podobnem aplikaciji.
+
+ Zapri
-
+
diff --git a/app/src/main/res/values-sq/strings.xml b/app/src/main/res/values-sq/strings.xml
index 17143694e..fa1e32869 100644
--- a/app/src/main/res/values-sq/strings.xml
+++ b/app/src/main/res/values-sq/strings.xml
@@ -47,7 +47,7 @@
E përzgjedhur
- %1$s prodhohet nga @fork-maintainers.
+ %1$s prodhohet nga Mozilla.
@@ -442,9 +442,6 @@
Mësoni më tepër
-
- Çaktivizuar globalisht, kaloni te Rregullimet që ta aktivizoni.
-
Telemetry
@@ -455,6 +452,8 @@
Të dhëna marketinguNdan me Leanplum-in, furnizuesi ynë i marketingut për celular, të dhëna rreth se cilat veçori përdorni në %1$s.
+
+ I jep Adjust-it, tregtuesit tonë të marketingut për platforma celulare, të dhëna elementare mbi përdoriminStudime
@@ -601,12 +600,16 @@
Sesion privatSkeda private
+
+ Skeda të njëkohësuaraShtoni skedëShtoni skedë privatePrivate
+
+ NjëkohësojiHapi Skedat
@@ -617,6 +620,8 @@
Nda krejt skedatSkeda të mbyllura së fundi
+
+ Rregullime llogarieRregullime skedash
@@ -786,8 +791,6 @@
%1$s u fshiShtoni dosje
-
- Faqeruajtësi u krijua.Faqerojtësi u ruajt!
@@ -972,8 +975,6 @@
Krejt veprimetPërdorur së fundi
-
- Futur si %1$sQë të njëkohësoni, bëni hyrjen
@@ -1446,10 +1447,8 @@
VetëplotësimeNjëkohëso kredenciale hyrjeje
-
- On
-
- Off
+
+ Njëkohëso kredenciale hyrjesh nëpër pajisjeRilidhu
@@ -1550,10 +1549,16 @@
Të dhënat janë të fshehtëzuaraNjëkohëso të dhëna kartash mes pajisjesh
+
+ Njëkohëso kartaShtoni kartë krediti
+
+ Administroni karta të ruajturaShtoni kartë
+
+ Përpunoni kartënNumër Karte
@@ -1562,6 +1567,8 @@
Emër në KartëNofkë Karte
+
+ Fshije kartënFshije kartën
@@ -1571,6 +1578,11 @@
Anuloje
+
+ Karta të ruajtura
+
+ Ju lutemi, jepni një numër të vlefshëm karte krediti
+
Shtoni motor kërkimesh
@@ -1731,23 +1743,13 @@
Anuloje
+
+ Caktoni lidhje prej sajtesh, email-esh dhe mesazhezh për hapje të automatizuar në Firefox.
+
Hiqe
-
- Përfitoni maksimumin nga %s.
-
Klikoni për më tepër hollësi
-
- Koleksiononi gjërat që kanë rëndësi për ju
-
- Gruponi tok kërkime, sajte dhe skeda të ngjashme, për përdorim të shpejtë më pas.
-
- Jeni i futur si %s në një tjetër shfletues Firefox në këtë telefon. Doni të hyhet me këtë llogari?
-
- Këtë sajt mund ta shtoni lehtë te skena Kreu e telefonit tuaj, që ta hapni në çast dhe të shfletoni më shpejt, si të ish aplikacion.
-
diff --git a/app/src/main/res/values-su/strings.xml b/app/src/main/res/values-su/strings.xml
index a8e4b6669..117a4f63d 100644
--- a/app/src/main/res/values-su/strings.xml
+++ b/app/src/main/res/values-su/strings.xml
@@ -610,6 +610,8 @@
Sesi nyamuniTab nyamuni
+
+ Tab singkronTambah tab
@@ -628,6 +630,8 @@
Bagikeun sadaya tabTab nu anyar ditutup
+
+ Setélan akunSetélan tab
@@ -984,8 +988,6 @@
Sadaya lampahAnyar dianggo
-
- Asup salaku %1$sAsup pikeun nyingkronkeun
@@ -1571,14 +1573,22 @@
Tambah kartu kiridit
+
+ Kokolakeun kartu anu diteundeunTambah kartu
+
+ Ropéa kartuNomer KartuTanggal KadaluarsaNgaran dina Kartu
+
+ Ngaran Kartu
+
+ Hapus kartuHapus kartu
@@ -1751,6 +1761,9 @@
Bolay
+
+ Setél tutumbu ti raramatloka, surélék, jeung surat pikeun muka otomatis dina Firefox.
+
Piceun
diff --git a/app/src/main/res/values-sv-rSE/strings.xml b/app/src/main/res/values-sv-rSE/strings.xml
index 13ee64499..f18b8ba38 100644
--- a/app/src/main/res/values-sv-rSE/strings.xml
+++ b/app/src/main/res/values-sv-rSE/strings.xml
@@ -420,6 +420,11 @@
and the third is the device model. -->
%1$s på %2$s %3$s
+
+ Kreditkort
+
+ Adresser
+
Mottagna flikar
@@ -451,9 +456,6 @@
Läs mer
-
- Stängt av globalt, gå till Inställningar för att slå på den.
-
Telemetri
@@ -465,6 +467,8 @@
MarknadsföringsdataDelar data om vilka funktioner du använder i %1$s med Leanplum, vår mobila marknadsföringsleverantör.
+
+ Delar grundläggande användningsdata med Adjust, vår leverantör av mobil marknadsföringUndersökningar
@@ -634,6 +638,8 @@
Dela alla flikarNyligen stängda flikar
+
+ KontoinställningarFlikinställningar
@@ -804,8 +810,6 @@
Tog bort %1$sLägg till mapp
-
- Bokmärke skapat.Bokmärke sparat!
@@ -990,8 +994,6 @@
Alla åtgärderNyligen använd
-
- Inloggad som %1$sLogga in för att synkronisera
@@ -1469,10 +1471,8 @@
AutofyllSynkronisera inloggningar
-
- På
-
- Av
+
+ Synkronisera inloggningar mellan enheterÅteranslut
@@ -1572,6 +1572,8 @@
Data är krypteradSynkronisera kort mellan enheter
+
+ Synkronisera kortLägg till kreditkort
@@ -1579,6 +1581,8 @@
Hantera sparade kreditkortLägg till kreditkort
+
+ Redigera kortKortnummer
@@ -1587,6 +1591,8 @@
Namn på kortSmeknamn för kort
+
+ Ta bort kortTa bort kort
@@ -1599,6 +1605,9 @@
Sparade kreditkort
+
+ Ange ett giltigt kreditkortsnummer
+
Lägg till sökmotor
@@ -1758,23 +1767,19 @@
Avbryt
+
+ Ställ in länkar från webbplatser, e-post och meddelanden så att de öppnas automatiskt i Firefox.
+
Ta bort
-
- Få ut det mesta av %s.
-
Klicka för mer information
-
- Samla de saker som är viktiga för dig
-
- Gruppera liknande sökningar, webbplatser och flikar för snabb åtkomst senare.
-
- Du är inloggad som %s på en annan Firefox-webbläsare på den här telefonen. Vill du logga in med det här kontot?
-
- Du kan enkelt lägga till den här webbplatsen på telefonens startsida för att få direktåtkomst och surfa snabbare med en appliknande upplevelse.
+
+ Navigera uppåt
+
+
+ Stäng
-
+
diff --git a/app/src/main/res/values-te/strings.xml b/app/src/main/res/values-te/strings.xml
index a690132cd..5e86f2b58 100644
--- a/app/src/main/res/values-te/strings.xml
+++ b/app/src/main/res/values-te/strings.xml
@@ -209,6 +209,9 @@
ఇంకా తెలుసుకోండి
+
+ %sలో వెతకండి
+
ఒక కొత్త Firefox ట్యాబును తెరువండి
@@ -340,6 +343,9 @@
గమనింపులు
+
+
+ అభిమత పొడగింతల సేకరణసరే
@@ -349,6 +355,9 @@
సేకరణ యజమాని (వాడుకరి ID)
+
+
+ పొడగింతకు తోడ్పాటు లేదుపొడగింత ఇప్పటికే స్థాపితమైవుంది
@@ -387,6 +396,11 @@
and the third is the device model. -->
%2$s %3$sలో %1$s
+
+ క్రెడిట్ కార్డులు
+
+ చిరునామాలు
+
అందుకన్న ట్యాబులు
@@ -418,9 +432,6 @@
ఇంకా తెలుసుకోండి
-
- సర్వత్రా అచేనంగా ఉంది, దీన్ని చేతనించుకోడానికి అమరికలకు వెళ్ళండి.
-
టెలీమెట్రీ
@@ -578,6 +589,8 @@
అంతరంగిక సెషనుఅంతరంగిక ట్యాబులు
+
+ సింకైన ట్యాబులుట్యాబును చేర్చు
@@ -594,6 +607,8 @@
అన్ని ట్యాబులను పంచుకోఇటీవల మూసిన ట్యాబులు
+
+ ఖాతా అమరికలుట్యాబు అమరికలు
@@ -767,8 +782,6 @@
%1$s తొలగించబడిందిసంచయం చేర్చు
-
- ఇష్టాంశం సృష్టించబడింది.ఇష్టాంశం భద్రమయింది!
@@ -857,6 +870,8 @@
స్థానంగమనింపులు
+
+ నిరంతర నిల్వఅనుమతిని అడుగు
@@ -874,15 +889,21 @@
అచేతనంఆడియో, వీడియోలను అనుమతించు
+
+ ఆడియో, వీడియోలను అనుమతించుఆడియో, వీడియోలను సెల్యులార్ డేటాని వాడుతున్నప్పుడు మాత్రమే నిరోధించుఆడియో, వీడియోలు వై-ఫైలో ఉన్నప్పుడు మాత్రమే ఆడతాయిఆడియోను మాత్రమే నిరోధించు
+
+ ఆడియోను మాత్రమే నిరోధించుఆడియో, వీడియోలను నిరోధించు
+
+ ఆడియో, వీడియోలను నిరోధించుచేతనం
@@ -1155,6 +1176,8 @@
అవును, నన్ను ప్రవేశింపజేయిప్రవేశింపజేస్తూంది…
+
+ నమోదవ్వండినిష్క్రమించే ఉండు
@@ -1167,6 +1190,8 @@
కఠినం (సిఫార్సు చేయబడింది)కఠినం
+
+ పనిముట్లపట్టీ ఎక్కడ ఉండాలో ఎంచుకోండిఅంతరంగికంగా విహరించండిమీ అలంకారాన్ని ఎంచుకోండి
+
+ చీకటి రీతితో కొంచెం బ్యాటరీని, మీ కంటిచూపుని ఆదాచేసుకోండి.ఆటోమెటిక్
@@ -1393,10 +1420,6 @@
స్వయంపూరణప్రవేశాలను సింక్ చేయి
-
- చేతనం
-
- అచేతనంమళ్లీ అనుసంధానించు
@@ -1491,10 +1514,21 @@
క్రెడిట్ కార్డులు
+
+ కార్డులను భద్రపరుచు, స్వయంచాలకంగా పూరించు
+
+ డేటా గుప్తీకరించబడుతుంది
+
+
+ కార్డులను పరికరాల మధ్య సింక్రనించుక్రెడిట్ కార్డును చేర్చు
+
+ భద్రపరచిన కార్డుల నిర్వహణకార్డును చేర్చు
+
+ కార్డును మార్చుకార్డు నెంబరు
@@ -1503,6 +1537,8 @@
కార్డు మీద పేరుకార్డు మారుపేరు
+
+ కార్డును తొలగించుకార్డును తొలగించు
@@ -1674,18 +1710,10 @@
తొలగించు
-
- %s నుండి ఎక్కువగా పొందండి.
-
మరిన్ని వివరాలకు నొక్కండి
-
- మీకు ముఖ్యమైన విషయాలను సేకరించండి
-
- ఈ ఫోను లోని మరో Firefox విహారిణిలో మీరు %sగా ప్రవేశించారు. మీరు అదే ఖాతాతో ప్రవేశించాలనుకుంటున్నారా?
-
- తక్షణం చేరుకొని, అనువర్తనం-వంటి అనుభూతితో వేగంగా విహరించడానికి, ఈ వెబ్సైటును మీ చరవాణి ముంగిలి తెరకు తేలికగా చేర్చుకోవచ్చు.
+
+ మూసివేయి
-
+
diff --git a/app/src/main/res/values-tg/strings.xml b/app/src/main/res/values-tg/strings.xml
index 9588ca9b4..a245a087f 100644
--- a/app/src/main/res/values-tg/strings.xml
+++ b/app/src/main/res/values-tg/strings.xml
@@ -214,7 +214,7 @@
Иҷозат дода нашавад
- Ба пешниҳодҳои ҷустуҷӯ дар ҷаласаҳои махфӣ иҷозат диҳед?
+ Ба пешниҳодҳои ҷустуҷӯ дар ҷаласаҳои махфӣ иҷозат медиҳед?%s ба низоми ҷустуҷӯии пешфарз ҳамаи он чизеро, ки шумо ба навори нишонӣ ворид мекунед, мефиристонад.
@@ -411,6 +411,11 @@
and the third is the device model. -->
%1$s дар %2$s %3$s
+
+ Кортҳои кредитӣ
+
+ Нишониҳо
+
Варақаҳо аз дастгоҳҳои дигар
@@ -441,9 +446,6 @@
Маълумоти бештар
-
- Комилан ғайрифаъол карда шуд, Барои фаъол кардани он ба Танзимот гузаред.
-
Телеметрия
@@ -454,6 +456,8 @@
Маълумоти маркетингӣМаълумот дар бораи хусусиятҳое, ки шумо дар %1$s истифода мебаред бо Leanplum, яъне фурӯшандаи маркетинги мобилии мо, мубодила карда шавад
+
+ Маълумоти асосии истифодабариро бо «Adjust», фурӯшандаи маркетингии мобилии мо, мубодила мекунадТаҳқиқҳо
@@ -603,6 +607,8 @@
Ҷаласаи махфӣВарақаҳои махфӣ
+
+ Варақаҳои ҳамоҳангшудаИлова кардани варақа
@@ -621,6 +627,8 @@
Мубодила кардани ҳамаи варақаҳоВарақаҳои ба наздикӣ пӯшидашуда
+
+ Танзимоти ҳисобТанзимоти варақа
@@ -788,8 +796,6 @@
%1$s нест карда шудИлова кардани ҷузвадон
-
- Хатбарак эҷод карда шуд.Хатбарак нигоҳ дошта шуд!
@@ -976,8 +982,6 @@
Ҳамаи амалҳоИстифодашудаи охирин
-
- Ҳамчун %1$s ворид шудБарои ҳамоҳангсозӣ ворид шавед
@@ -1341,7 +1345,7 @@
Криптомайнерҳо
- Хонандаи изи ангушт
+ Хонандаи нақши ангуштМанъ карда мешавадИҷозат дода мешавад
@@ -1358,7 +1362,7 @@
Скриптҳои зараровареро, ки дастгоҳи шуморо барои истеҳсоли пули рақамӣ дастрас мекунанд, пешгирӣ менамояд.
- Хонандаи изи ангушт
+ Хонандаи нақши ангуштҶамъкунии маълумоти нодири муайяншавандаеро дар бораи дастгоҳи шумо, ки метавонад бо мақсадҳои пайгирӣ истифода шавад, манъ мекунад.
@@ -1451,10 +1455,8 @@
Пуркунии худкорВоридшавиҳои ҳамоҳангшуда
-
- Фаъол
-
- Ғайрифаъол
+
+ Ҳамоҳанг кардани воридшавиҳо байни дастгоҳҳоАз нав пайваст кардан
@@ -1555,6 +1557,8 @@
Маълумот рамзгузорӣ карда шудҲамоҳанг кардани кортҳо байни дастгоҳҳо
+
+ Ҳамоҳанг кардани кортҳоИлова кардани корти насия
@@ -1562,6 +1566,8 @@
Идора кардани кортҳои нигоҳдошташудаИлова кардани корт
+
+ Таҳрир кардани кортРақами корт
@@ -1571,6 +1577,8 @@
Номи корбари корт
+
+ Нест кардани кортНест кардани корт
@@ -1583,6 +1591,9 @@
Кортҳои нигоҳдошташуда
+
+ Лутфан, рақами корти кредитии дурустро ворид намоед
+
Илова кардани низоми ҷустуҷӯӣ
@@ -1741,22 +1752,19 @@
Бекор кардан
+
+ Пайванҳоеро, танзим кунед, ки онҳо аз сомонаҳо, почтаи электронӣ ва паёмҳо дар браузери Firefox ба таври худкор кушода шаванд.
+
Тоза кардан
-
- Аз %s баштар истифода баред.
-
Барои тафсилоти бештар зер кунед
-
- Чизҳоеро, ки ба шумо муҳиманд, ҷамъ кунед
-
- Ҷустуҷӯҳо, сомонаҳо ва варақаҳои монандро барои дастрасии фаврӣ дар оянда якҷоя кунед.
-
- Шумо ҳамчун %s тавассути браузери дигари Firefox дар ин телефон ворид шудаед. Шумо мехоҳед, ки бо ин ҳисоб ворид шавед?
-
- Шумо метавонед ин сомонаро ба экрани асосии телефони худ ба осонӣ илова кунед, то ки ба он дастрасии фаврӣ дошта бошед ва бо таҷрибаи ба барнома монанд зудтар паймоиш кунед.
+
+ Ба боло гузаред
+
+
+ Пӯшидан
+
diff --git a/app/src/main/res/values-th/strings.xml b/app/src/main/res/values-th/strings.xml
index 96a7de2d4..2ee95d0d1 100644
--- a/app/src/main/res/values-th/strings.xml
+++ b/app/src/main/res/values-th/strings.xml
@@ -48,7 +48,7 @@
เลือกแล้ว
- %1$s ผลิตขึ้นโดย @fork-maintainers
+ %1$s ผลิตขึ้นโดย Mozilla
@@ -602,6 +602,8 @@
วาระส่วนตัวแท็บส่วนตัว
+
+ แท็บที่ซิงค์เพิ่มแท็บ
@@ -620,6 +622,8 @@
แบ่งปันแท็บทั้งหมดแท็บที่ปิดล่าสุด
+
+ การตั้งค่าบัญชีการตั้งค่าแท็บ
@@ -975,8 +979,6 @@
การกระทำทั้งหมดเพิ่งใช้ล่าสุด
-
- ลงชื่อเข้าเป็น %1$sลงชื่อเข้าใช้เพื่อซิงค์
@@ -1557,6 +1559,8 @@
จัดการบัตรที่บันทึกไว้เพิ่มบัตร
+
+ แก้ไขบัตรหมายเลขบัตร
@@ -1565,6 +1569,8 @@
ชื่อบนบัตรชื่อย่อบัตร
+
+ ลบบัตรลบบัตร
@@ -1736,6 +1742,9 @@
ยกเลิก
+
+ ตั้งลิงก์จากเว็บไซต์ อีเมล และข้อความให้เปิดโดยอัตโนมัติใน Firefox
+
ลบ
diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml
index a611dc97f..46a0a1cfc 100644
--- a/app/src/main/res/values-tr/strings.xml
+++ b/app/src/main/res/values-tr/strings.xml
@@ -414,6 +414,11 @@
and the third is the device model. -->
%1$s - %2$s %3$s
+
+ Kredi kartları
+
+ Adresler
+
Alınan sekmeler
@@ -445,9 +450,6 @@
Daha fazla bilgi al
-
- Tamamen kapalı. Açmak için Ayarlar’a gidin.
-
Telemetri
@@ -458,6 +460,8 @@
Pazarlama verileriKullandığınız %1$s özellikleri ile ilgili verileri mobil pazarlama hizmeti aldığımız Leanplum ile paylaşır.
+
+ Temel kullanım verilerini mobil pazarlama iş ortağımız Adjust ile paylaşırAraştırmalar
@@ -605,6 +609,8 @@
Gizli oturumGizli sekmeler
+
+ Eşitlenmiş sekmelerSekme ekle
@@ -623,6 +629,8 @@
Tüm sekmeleri paylaşSon kapatılan sekmeler
+
+ Hesap ayarlarıSekme ayarları
@@ -792,8 +800,6 @@
%1$s silindiKlasör ekle
-
- Yer imi oluşturuldu.Yer imi kaydedildi!
@@ -978,8 +984,6 @@
Tüm eylemlerSon kullanılanlar
-
- Kullanıcı: %1$sEşitlemek için giriş yap
@@ -1449,10 +1453,8 @@
Otomatik doldurHesapları eşitle
-
- Açık
-
- Kapalı
+
+ Hesapları cihazlar arasında eşitleYeniden bağlan
@@ -1553,6 +1555,8 @@
Veriler şifrelenirKartları cihazlar arasında eşitle
+
+ Kartları eşitleKredi kartı ekle
@@ -1560,6 +1564,8 @@
Kayıtlı kartları yönetKart ekle
+
+ Kartı düzenleKart numarası
@@ -1568,6 +1574,8 @@
Kart üzerindeki adKart adı
+
+ Kartı silKartı sil
@@ -1580,6 +1588,9 @@
Kayıtlı kartlar
+
+ Lütfen geçerli bir kredi kartı numarası girin
+
Arama motoru ekle
@@ -1738,23 +1749,19 @@
İptal
+
+ Web siteleri, e-postalar ve mesajlardaki bağlantılar otomatik olarak Firefox’ta açılsın.
+
Kaldır
-
- %s tarayıcınızdan en iyi şekilde yararlanın.
-
Ayrıntılar için tıklayın
-
- Sizin için önemli olan şeyleri toplayın
-
- Benzer aramaları, siteleri ve sekmeleri gruplandırarak daha sonra onlara hızlıca erişebilirsiniz.
-
- Bu telefondaki başka bir Firefox tarayıcısına %s olarak giriş yapmışsınız. Bu hesapla giriş yapmak ister misiniz?
-
- Bu siteyi telefonunuzun ana ekranına ekleyerek ona hızlıca erişebilir, site bir uygulamaymış gibi daha hızlı gezinti yapabilirsiniz.
+
+ Yukarı
+
+
+ Kapat
-
+
diff --git a/app/src/main/res/values-tt/strings.xml b/app/src/main/res/values-tt/strings.xml
index b3e710d66..790303dce 100644
--- a/app/src/main/res/values-tt/strings.xml
+++ b/app/src/main/res/values-tt/strings.xml
@@ -176,6 +176,8 @@
Эзләү
+
+ Җиһаз теленә иярүТелне эзләү
@@ -186,6 +188,8 @@
Эзләү системасыЭзләү системасы көйләүләре
+
+ Сылтаманы алмашу буферыннан алуРөхсәт итү
@@ -327,6 +331,9 @@
Искәртүләр
+
+
+ Үзгә кушымчалар җыентыгыОК
@@ -397,6 +404,8 @@
ТелеметрияМаркетинг мәгълүматлары
+
+ ТикшеренүләрMozilla урнашу хезмәте
@@ -518,6 +527,8 @@
Хосусый утырышХосусый таблар
+
+ Синхронланган табларТаб өстәү
@@ -538,6 +549,8 @@
Барлык табларны уртаклашуКүптән түгел ябылган таблар
+
+ Хисап көйләүләреТаб көйләүләре
@@ -1243,6 +1256,22 @@
Кредит карталарыМәгълүматлар шифрланган
+
+ Карталарны җиһазлар арасында синхронлау
+
+ Кредит картасын өстәү
+
+ Сакланган карталар белән идарә итү
+
+ Картаны өстәү
+
+ Карта номеры
+
+ Вакыты чыгу датасы
+
+ Картадагы исем
+
+ Картаны бетерүСаклау
@@ -1250,6 +1279,9 @@
Баш тарту
+
+ Сакланган карталар
+
Эзләү системасын өстәү
@@ -1272,9 +1304,24 @@
Күбрәк белү
+
+ Күбрәк белү сылтамасы
+
+
+ Эзләү системасының исемен кертегез
+
+ Эзләнәсе сүзтезмәне кертегез
+
+ Эзләнәсе сүзтезмәнең Мисал форматына туры килүен тикшерегез
+
+ %s ясалды
+
+ %s сакланды%s бетерелде
+
+ %s яңартыла…%s программасын кабызу
@@ -1286,6 +1333,18 @@
Хәвефсез бәйләнешХәвефсез булмаган бәйләнеш
+
+ Барлык сайтлардагы барлык рөхсәтләрне дә чистартуны раслыйсызмы?
+
+ Бу сайт өчен бирелгән барлык рөхсәтләрне дә чистартуны раслыйсызмы?
+
+ Әлеге сайт өчен бирелгән бу рөхсәтне кире алуны раслыйсызмы?
+
+ Төп мәкаләләр
+
+ Бу кыстыргычны бетерүне раслыйсызмы?
+
+ Төп сайтларга өстәүРаслаучы: %1$s
@@ -1293,8 +1352,14 @@
БетерүҮзгәртү
+
+ Бу логинны бетерүне раслыйсызмы?Бетерү
+
+ Логин көйләүләре
+
+ Логин үзгәрешләрен саклау.Үзгәрешләрдән ваз кичү
@@ -1319,6 +1384,8 @@
Яхшы, аңладымИсем
+
+ Төп сайт исемеОК
diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml
index 04ff2b82d..4121d9621 100644
--- a/app/src/main/res/values-uk/strings.xml
+++ b/app/src/main/res/values-uk/strings.xml
@@ -368,7 +368,7 @@
Власна збірка додатків
- OK
+ ГараздСкасувати
@@ -419,6 +419,11 @@
and the third is the device model. -->
%1$s на %2$s %3$s
+
+ Кредитні картки
+
+ Адреси
+
Отримані вкладки
@@ -451,9 +456,6 @@
Докладніше
-
- Вимкнено глобально. Перейдіть у Налаштування, щоб увімкнути.
-
Телеметрія
@@ -464,6 +466,8 @@
Маркетингові даніНадсилання даних про функції, які ви використовуєте в %1$s, нашій компанії з мобільного маркетингу Leanplum.
+
+ Надсилати основні дані про використання до нашого постачальника мобільного маркетингу AdjustДослідження
@@ -632,6 +636,8 @@
Поділитися всіма вкладкамиНедавно закриті вкладки
+
+ Обліковий записНалаштування вкладок
@@ -801,8 +807,6 @@
%1$s видаленоДодати теку
-
- Закладку створено.Закладку збережено!
@@ -989,8 +993,6 @@
Недавно використані
-
- Ви увійшли як %1$sУвійти до синхронізації
@@ -1464,10 +1466,8 @@
АвтозаповненняСинхронізація паролів
-
- Увімкнено
-
- Вимкнено
+
+ Синхронізувати паролі між пристроямиВідновити підключення
@@ -1570,6 +1570,8 @@
Дані зашифрованоСинхронізувати картки між пристроями
+
+ Синхронізувати карткиДодати кредитну картку
@@ -1577,6 +1579,8 @@
Керувати збереженими карткамиДодати картку
+
+ Змінити карткуНомер картки
@@ -1585,6 +1589,8 @@
Ім’я на картціПсевдонім картки
+
+ Видалити карткуВидалити картку
@@ -1597,6 +1603,9 @@
Збережені картки
+
+ Введіть дійсний номер кредитної картки
+
Додати засіб пошуку
@@ -1752,27 +1761,23 @@
Назва популярного сайту
- OK
+ ГараздСкасувати
+
+ Автоматично відкривати посилання з вебсайтів, електронних листів та повідомлень у Firefox.
+
Вилучити
-
- Отримайте якнайбільше від %s.
-
Показати подробиці
-
- Зберігайте важливе для вас
-
- Групуйте подібні пошуки, сайти та вкладки для швидкого доступу.
-
- Ви увійшли як %s в іншому браузері Firefox на цьому пристрої. Бажаєте увійти в цей обліковий запис?
-
- Ви можете легко додати цей вебсайт на головний екран вашого телефону, щоб мати миттєвий доступ до нього і працювати швидше, неначе зі звичайною програмою.
+
+ Вгору
+
+
+ Закрити
-
+
diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml
index 13da33a48..0890effa6 100644
--- a/app/src/main/res/values-vi/strings.xml
+++ b/app/src/main/res/values-vi/strings.xml
@@ -412,6 +412,11 @@
and the third is the device model. -->
%1$s trên %2$s %3$s
+
+ Thẻ tín dụng
+
+ Địa chỉ
+
Các thẻ đã nhận
@@ -443,9 +448,6 @@
Tìm hiểu thêm
-
- Đã tắt toàn bộ, đi tới cài đặt để bật nó.
-
Telemetry
@@ -456,6 +458,8 @@
Dữ liệu tiếp thịChia sẻ dữ liệu về những tính năng bạn sử dụng trong %1$s với Leanplum, nhà cung cấp tiếp thị di động của chúng tôi.
+
+ Chia sẻ dữ liệu sử dụng cơ bản với Adjust, nhà cung cấp tiếp thị di động của chúng tôiNghiên cứu
@@ -602,6 +606,8 @@
Phiên riêng tưCác thẻ riêng tư
+
+ Các thẻ đã đồng bộThêm thẻ
@@ -620,6 +626,8 @@
Chia sẻ tất cả các thẻCác thẻ đã đóng gần đây
+
+ Cài đặt tài khoảnCài đặt thẻ
@@ -788,8 +796,6 @@
Đã xóa %1$sThêm thư mục
-
- Đã tạo dấu trang.Đã lưu dấu trang!
@@ -974,8 +980,6 @@
Mọi hành độngĐược sử dụng gần đây
-
- Đã đăng nhập bằng tài khoản %1$sĐăng nhập vào đồng bộ hóa
@@ -1441,10 +1445,8 @@
Tự động điềnĐồng bộ hóa thông tin đăng nhập
-
- Bật
-
- Tắt
+
+ Đồng bộ hóa thông tin đăng nhập trên các thiết bịKết nối lại
@@ -1544,6 +1546,8 @@
Dữ liệu được mã hóaĐồng bộ thông tin thẻ tín dụng trên các thiết bị
+
+ Đồng bộ hóa các thẻ tín dụngThêm thẻ tín dụng
@@ -1551,6 +1555,8 @@
Quản lý thẻ tín dụng đã lưuThêm thẻ
+
+ Chỉnh sửa thẻSố thẻ
@@ -1559,6 +1565,8 @@
Tên trên thẻBiệt danh thẻ
+
+ Xóa thẻXóa thẻ
@@ -1571,6 +1579,9 @@
Thẻ tín dụng đã lưu
+
+ Vui lòng nhập số thẻ tín dụng hợp lệ
+
Thêm công cụ tìm kiếm
@@ -1729,23 +1740,19 @@
Huỷ bỏ
+
+ Đặt các liên kết từ trang web, email và tin nhắn để tự động mở trong Firefox.
+
Xóa
-
- Tận dụng tối đa %s.
-
Chạm để biết thêm chi tiết
-
- Thu thập những thứ quan trọng với bạn
-
- Nhóm các tìm kiếm, trang web và thẻ tương tự để truy cập nhanh sau này.
-
- Bạn đã đăng nhập dưới dạng %s trên một trình duyệt Firefox khác trên điện thoại này. Bạn có muốn đăng nhập bằng tài khoản này không?
-
- Bạn có thể dễ dàng thêm trang web vào màn hình chính của điện thoại của bạn để có thể truy cập và duyệt web nhanh hơn với trải nghiệm giống như trên ứng dụng.
+
+ Điều hướng lên
+
+
+ Đóng
-
+
diff --git a/app/src/main/res/values-zh-rCN/mozonline_strings.xml b/app/src/main/res/values-zh-rCN/mozonline_strings.xml
index d09a3e0b9..182ef30f4 100644
--- a/app/src/main/res/values-zh-rCN/mozonline_strings.xml
+++ b/app/src/main/res/values-zh-rCN/mozonline_strings.xml
@@ -19,4 +19,21 @@
同意并继续退出应用
-
\ No newline at end of file
+
+
+
+ 百度
+
+ 京东
+
+ 拼多多
+
+
+
+ 使用本地服务
+
+ firefox.com.cn/pair 并扫描网站上的二维码]]>
+
+ https://firefox.com.cn/pair]]>
+
+
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index 7ca87790a..af863296b 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -424,6 +424,12 @@
and the third is the device model. -->
%2$s %3$s 上的 %1$s
+
+ 信用卡
+
+
+ 邮政地址
+
收到标签页
@@ -458,9 +464,6 @@
详细了解
-
- 已全局关闭,请前往“设置”开启。
-
遥测技术
@@ -471,6 +474,8 @@
营销数据与我们的移动营销供应商 Leanplum 分享您使用 %1$s 的哪些功能。
+
+ 与我们的移动营销服务商 Adjust 分享基本使用数据研究
@@ -641,6 +646,8 @@
分享所有标签页最近关闭的标签页
+
+ 账户设置标签页设置
@@ -757,9 +764,9 @@
- 清除下载记录
+ 清空下载记录
- 您确定要清除下载记录吗?
+ 您确定要清空下载记录吗?下载记录已清除
@@ -812,8 +819,6 @@
已删除 %1$s新建文件夹
-
- 书签已创建。书签已保存!
@@ -1014,8 +1019,6 @@
所有动作最近使用
-
- 已登录为 %1$s登录同步服务
@@ -1482,7 +1485,7 @@
您可以轻松将此网站添加到设备主屏幕,以便迅捷访问并以类似应用的体验畅享浏览。
- 我的密码
+ 密码保存登录名和密码
@@ -1493,16 +1496,14 @@
自动填写同步登录信息
-
- 开启
-
- 关闭
+
+ 跨设备同步登录信息重新连接登录同步服务
- 存放的登录信息
+ 保存的登录信息您保存或同步到 %s 的登录信息将显示于此处。
@@ -1530,7 +1531,7 @@
重新输入您的 PIN 码
- 解锁以查看您存放的登录信息
+ 解锁以查看您保存的登录信息此连接不安全。输入的登录信息可能被窃取。
@@ -1564,11 +1565,11 @@
隐藏密码
- 解锁以查看您存放的登录信息
+ 解锁以查看您保存的登录信息保护您的登录名和密码
- 设置锁定方式、PIN 码或密码以保护您存放的登录名与密码,避免他人盗用。
+ 设置锁定方式、PIN 码或密码以保护您保存的登录名与密码,避免他人盗用。稍后
@@ -1591,18 +1592,22 @@
信用卡
- 保存并自动填充信用卡信息
+ 保存并自动填充卡片信息数据已加密
- 跨设备同步信用卡信息
+ 跨设备同步卡片信息
+
+ 同步卡片信息添加信用卡
- 管理存放的信用卡
+ 管理保存的卡片添加信用卡
+
+ 编辑卡片卡号
@@ -1611,8 +1616,10 @@
卡上姓名卡的昵称
+
+ 删除卡片
- 删除此卡
+ 删除卡片保存
@@ -1621,7 +1628,10 @@
取消
- 存放的信用卡
+ 保存的卡片
+
+
+ 请输入有效的信用卡卡号添加搜索引擎
@@ -1781,23 +1791,19 @@
取消
+
+ 将网站、电子邮件及聊天工具中的链接设为在 Firefox 中自动打开。
+
移除
-
- 全面体验 %s。
-
点击了解更多信息
-
- 有用的东西收藏在这
-
- 将相似的搜索查询、网站和标签页归入一组,方便以后快速访问。
-
- 您已在此手机上的其他 Firefox 浏览器上以 %s 身份登录。也要使用此账号登录吗?
-
- 您可以轻松将此网站添加到手机主屏幕,以便迅捷访问并以类似应用的体验畅享浏览。
+
+ 向上导航
+
+
+ 关闭
-
+
diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml
index 182be607b..881dc581f 100644
--- a/app/src/main/res/values-zh-rTW/strings.xml
+++ b/app/src/main/res/values-zh-rTW/strings.xml
@@ -419,6 +419,11 @@
and the third is the device model. -->
在 %2$s %3$s 上的 %1$s
+
+ 信用卡
+
+ 地址
+
收到分頁
@@ -452,9 +457,6 @@
了解更多
-
- 已全域關閉。請到「設定」頁面開啟。
-
Telemetry
@@ -465,6 +467,8 @@
行銷資料與我們的行動行銷服務供應商 Leanplum 分享您在 %1$s 使用了哪些功能。
+
+ 與我們的行動行銷服務廠商 Adjust 分享基本使用資料使用者研究
@@ -613,6 +617,8 @@
隱私瀏覽階段隱私分頁
+
+ 同步的分頁新增分頁
@@ -631,6 +637,8 @@
分享所有分頁最近關閉的分頁
+
+ 帳號設定分頁選項
@@ -802,8 +810,6 @@
已刪除 %1$s新增資料夾
-
- 已建立書籤。已加入書籤!
@@ -1001,8 +1007,6 @@
所有動作最近使用
-
- 已登入為 %1$s登入進行同步
@@ -1478,10 +1482,8 @@
自動填寫同步登入資訊
-
- 開啟
-
- 關閉
+
+ 在不同裝置間同步登入資訊重新連結
@@ -1582,6 +1584,8 @@
資料有加密在不同裝置間同步卡片資料
+
+ 同步信用卡資訊新增信用卡
@@ -1589,6 +1593,8 @@
管理已儲存的卡片新增付款卡片
+
+ 編輯卡片資訊卡號
@@ -1597,6 +1603,8 @@
持卡人姓名卡片暱稱
+
+ 刪除卡片刪除卡片
@@ -1609,6 +1617,9 @@
已儲存的卡片
+
+ 請輸入有效的信用卡號
+
新增搜尋引擎
@@ -1767,23 +1778,19 @@
取消
+
+ 設定使用 Firefox 自動開啟網站、郵件、簡訊當中的鏈結。
+
移除
-
- 發揮 %s 的最大威力。
-
點這裡取得詳細資訊
-
- 收集對您而言重要的東西
-
- 將類似的搜尋、網站、分頁放在一起,方便之後快速使用。
-
- 您已經在這支手機的其他 Firefox 瀏覽器以 %s 登入。也要使用此帳號登入嗎?
-
- 您可將此網站加到手機主畫面,方便快速開啟,或是以類似 App 的方式使用。
+
+ 向上導航
+
+
+ 關閉
-
+
diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml
index e0866ffaf..1c8ce3c53 100644
--- a/app/src/main/res/values/attrs.xml
+++ b/app/src/main/res/values/attrs.xml
@@ -28,6 +28,9 @@
+
+
+
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index f8d09ea35..0809ebebf 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -4,13 +4,12 @@
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
- #BFBFC9
- #312A65#7A312A65
- #9059FF#529059FF#7A9059FF#1F592ACB
+ #15141A
+ #E7DFFF#20123A
@@ -35,7 +34,7 @@
#FF15141A#0015141A@color/photonLightGrey30
- @color/photonDarkGrey10
+ @color/dark_grey_90#7542E5#0250BB#E31587
@@ -44,7 +43,7 @@
@color/foundation_light_theme@color/foundation_light_theme@color/foundation_light_theme
- @color/light_grey_50
+ @color/photonLightGrey50@color/accent_light_theme#C45A27#FFFDE2
@@ -61,6 +60,7 @@
#008787#0060df@color/accent_bright_light_theme
+ @color/accent_bright_light_theme@color/photonWhite@color/photonLightGrey30
@@ -74,7 +74,7 @@
@color/photonLightGrey30#ffffff#312A65
- @color/ink_20
+ @color/photonInk20@color/ink_20_48a@color/accent_normal_theme@color/photonLightGrey10
@@ -105,7 +105,7 @@
#F520123A#F515141A@color/photonDarkGrey10
- @color/photonLightGrey60
+ @color/photonLightGrey05#AB71FF#00B3F4#FF6BBA
@@ -143,7 +143,7 @@
@color/photonDarkGrey50@color/photonDarkGrey10#9059FF
- @color/violet_50
+ @color/photonViolet50@color/photonLightGrey05@color/photonLightGrey05@color/photonDarkGrey50
@@ -206,7 +206,7 @@
@color/photonInk50@color/photonDarkGrey10#9059FF
- @color/violet_50
+ @color/photonViolet50@color/violet_50_48a
@@ -258,6 +258,7 @@
@color/mozac_widget_favicon_background_light_theme@color/mozac_widget_favicon_border_light_theme@color/accent_bright_light_theme
+ @color/menu_item_button_light_theme@color/tab_tray_item_text_light_theme
@@ -283,8 +284,9 @@
@color/top_site_title_text_light_theme@color/photonLightGrey30
- @color/light_grey_50
- @color/ink_20
+ @color/photonLightGrey50
+ @color/photonInk20
+ @color/photonLightGrey30@color/synced_tabs_separator_light_theme
@@ -358,10 +360,8 @@
#5C592ACB@color/foundation_private_theme#FBFBFE
- #15141A@color/white_color#cccccc
- #E7DFFF#232749#FFF36E#960E18
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index 40e04af06..32440af48 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -191,8 +191,8 @@
28dp
- 10dp
- 12dp
+ 24dp
+ 24dp5dp16dp4dp
@@ -206,13 +206,24 @@
48dp
- 40dp
- 4dp
- 64dp
+ 60dp
+ 12dp
+ 84dp8dp12dp8dp
+
+
+ 48dp
+
+ 16dp
+ 16sp
+ 16dp
+ 48dp
+
48dp
diff --git a/app/src/main/res/values/mozonline_strings.xml b/app/src/main/res/values/mozonline_strings.xml
index 89c9fbc4c..61724501e 100644
--- a/app/src/main/res/values/mozonline_strings.xml
+++ b/app/src/main/res/values/mozonline_strings.xml
@@ -19,4 +19,21 @@
Agree and ContinueExit the App
-
\ No newline at end of file
+
+
+
+ Baidu
+
+ JD
+
+ PDD
+
+
+
+ Use domestic China service
+
+ firefox.com.cn/pair]]>
+
+ https://firefox.com.cn/pair]]>
+
+
diff --git a/app/src/main/res/values/preference_keys.xml b/app/src/main/res/values/preference_keys.xml
index 1d5a9656f..6a1996bae 100644
--- a/app/src/main/res/values/preference_keys.xml
+++ b/app/src/main/res/values/preference_keys.xml
@@ -7,7 +7,6 @@
pref_key_search_enginepref_key_add_Search_enginepref_key_passwords
- pref_key_credit_cards_addressespref_key_site_permissionspref_key_add_private_browsing_shortcutpref_key_accessibility
@@ -48,6 +47,8 @@
pref_key_accountpref_key_sign_inpref_key_account_auth_error
+ pref_key_allow_domestic_china_fxa_server
+ pref_key_have_read_fxa_account_jsonpref_key_override_fxa_serverpref_key_override_sync_tokenserverpref_key_private_mode
@@ -87,7 +88,6 @@
pref_key_sync_sign_inpref_key_sync_problemproject_id
- pref_key_fxa_signed_inpref_key_fxa_has_synced_itemspref_key_search_widget_installedpref_key_saved_logins_sorting_strategy
@@ -179,6 +179,18 @@
pref_key_logins_secure_warning_syncpref_key_logins_secure_warning
+
+
+ pref_key_credit_cards
+
+ pref_key_credit_cards_save_and_autofill_cards
+
+ pref_key_credit_cards_sync_cards_across_devices
+
+ pref_key_credit_cards_add_credit_card
+
+ pref_key_credit_cards_manage_saved_cards
+
pref_key_open_links_in_a_private_tabpref_key_open_links_in_external_app
@@ -258,14 +270,18 @@
pref_key_return_to_browser
+ pref_key_open_next_tab_desktop_mode
+
+
+ pref_key_experiment_card_home
+
pref_key_secret_debug_info
- pref_key_leanplum_user_id
- pref_key_leanplum_device_idpref_key_search_region_homepref_key_search_region_currentpref_key_show_credit_cards_featurepref_key_show_address_feature
+ pref_key_nimbus_experiments
diff --git a/app/src/main/res/values/static_strings.xml b/app/src/main/res/values/static_strings.xml
index 167d0e59a..cca417637 100644
--- a/app/src/main/res/values/static_strings.xml
+++ b/app/src/main/res/values/static_strings.xml
@@ -43,6 +43,9 @@
Use new Tabs TrayA refactored tabs tray that will include Synced Tabs.
+
+ Nimbus Experiments
+
Show Synced Tabs in the tabs tray
@@ -70,8 +73,6 @@
Telemetry
- Leanplum User Id
- Leanplum Device IdSearchHome regionCurrent region
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 20ccda79f..e89fd06f6 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -19,10 +19,6 @@
Your open tabs will be shown here.Your private tabs will be shown here.
-
- Baidu
-
- JD1 open tab. Tap to switch tabs.
@@ -181,6 +177,11 @@
AppearanceCustomize reader view
+
+ Add
+
+ Edit
+
Unable to connect. Unrecognizable URL scheme.
@@ -442,9 +443,6 @@
Learn more
-
- Turned off globally, go to Settings to turn it on.
-
Telemetry
@@ -455,6 +453,8 @@
Marketing dataShares data about what features you use in %1$s with Leanplum, our mobile marketing vendor.
+
+ Shares basic usage data with Adjust, our mobile marketing vendorStudies
@@ -601,12 +601,16 @@
Private sessionPrivate tabs
+
+ Synced tabsAdd tabAdd private tabPrivate
+
+ SyncOpen Tabs
@@ -617,6 +621,8 @@
Share all tabsRecently closed tabs
+
+ Account settingsTab settings
@@ -963,6 +969,8 @@
All actionsRecently used
+
+ Sign in to syncSign in to Sync
@@ -1154,9 +1162,6 @@
Welcome to %s!Already have an account?
-
- Get to know %sSee what’s newGet answers here
-
- Start syncing bookmarks, passwords, and more with your Firefox account.
+
+ Sync Firefox between devices
- Learn more
+ Bring bookmarks, history, and passwords to Firefox on this device.
@@ -1176,8 +1181,8 @@
Yes, sign me inSigning in…
-
- Sign in to Firefox
+
+ Sign upStay signed out
@@ -1185,14 +1190,13 @@
Failed to sign-in
- Automatic privacy
-
- Privacy and security settings block trackers, malware, and companies that follow you.
+ Always-on privacy
+
+ Firefox automatically stops companies from secretly following you around the web.Standard (default)
- Blocks fewer trackers. Pages will load normally.
+ Balanced for privacy and performance. Pages load normally.Strict (recommended)
@@ -1215,8 +1219,11 @@
In English this is an idiom for "choose a side as in an argument or fight"
but it is ok to make this more literally about "choosing a position in a physical space -->
Take a position
+ Blocks more trackers so pages load faster, but some on-page functionality may break.
+
+ Pick your toolbar placement
- Try one-handed browsing with the bottom toolbar or move it to the top.
+ Put the toolbar within easy reach. Keep it on the bottom, or move it to the top.Browse privatelyYour privacy
+ The first parameter is the name of the app (e.g. Firefox Preview) Substitute %s for long browser name. -->
We’ve designed %s to give you control over what you share online and what you share with us.Read our privacy notice
@@ -1243,7 +1250,7 @@
Choose your theme
- Save some battery and your eyesight by enabling dark mode.
+ Save some battery and your eyesight with dark mode.Automatic
@@ -1298,13 +1305,13 @@
Standard (default)
- Blocks fewer trackers. Pages will load normally.
+ Balanced for privacy and performance. Pages load normally.What’s blocked by standard tracking protectionStrict
- Blocks more trackers, ads, and popups. Pages load faster, but some functionality might not work.
+ Blocks more trackers so pages load faster, but some on-page functionality may break.What’s blocked by strict tracking protection
@@ -1538,6 +1545,44 @@
Sort logins menu
+
+
+ Credit cards
+
+ Save and autofill cards
+
+ Data is encrypted
+
+ Sync cards across devices
+
+ Add credit card
+
+ Manage saved cards
+
+ Add card
+
+ Edit card
+
+ Card Number
+
+ Expiration Date
+
+ Name on Card
+
+ Card Nickname
+
+ Delete card
+
+ Delete card
+
+ Save
+
+ Save
+
+ Cancel
+
+ Saved cards
+
Add search engine
@@ -1719,6 +1764,8 @@
Place FAB at the topEnable to place button for new tab at the top, disable to place it at the bottom
+
+ Set links from websites, emails, and messages to open automatically in Firefox.Remove
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index c0d2b4ad2..143fd6b7a 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -82,6 +82,8 @@
@color/search_suggestion_indicator_icon_bookmark_color_normal_theme@color/select_login_header_normal_theme@color/preference_section_header_normal_theme
+ @color/menu_item_button_normal_theme
+
@color/primary_text_normal_theme@color/caption_text_normal_theme
@@ -232,6 +234,7 @@
@color/search_suggestion_indicator_icon_bookmark_color_dark_theme@color/accent_high_contrast_private_theme@color/preference_section_header_dark_theme
+ @color/accent_high_contrast_private_theme@color/primary_text_private_theme
@@ -602,7 +605,8 @@
@dimen/top_sites_favicon_sizefitCenter@dimen/top_sites_favicon_padding
- @drawable/mozac_widget_favicon_background
+ @drawable/top_sites_background
+ 6dp