1
0
Fork 0

Copione merged onto master

master
blallo 2020-08-07 00:00:23 +02:00
commit 90e4a38f45
178 changed files with 732 additions and 813 deletions

View File

@ -81,52 +81,45 @@ Pre-requisites:
2. **Import** the project into Android Studio **or** build on the command line: 2. **Import** the project into Android Studio **or** build on the command line:
```shell ```shell
./gradlew clean app:assembleGeckoBetaDebug ./gradlew clean app:assembleDebug
``` ```
Use app:assembleGeckoNightlyDebug to build with the Gecko Nightly version instead.
If this errors out, make sure that you have an `ANDROID_SDK_ROOT` environment If this errors out, make sure that you have an `ANDROID_SDK_ROOT` environment
variable pointing to the right path. variable pointing to the right path.
3. Make sure to select the correct build variant in Android Studio. See the next section. 3. Make sure to select the correct build variant in Android Studio. See the next section.
### Guide to Build Variants ### Build Variants
We have a lot of build variants. Each variant is composed of two flavors. One flavor is the version of Gecko to use and the other describes For general development, we recommend the **debug** build variant. Here's an explanation of each variant:
which app id and settings to use. Here is a description of what each means:
- **geckoBeta** (recommended) uses the Beta variant of the Gecko rendering engine, which corresponds to the next version of Gecko which will go to production - **debug**: the default for developers, similar to most other Android apps. It is debuggable, uses a Nightly GeckoView with debug symbols, adds tools like LeakCanary for troublingshooting, and does not strip unused code.
- **geckoNightly** uses the Nightly variant of the Gecko rendering engine, which is the version which will arrive after beta and is less stable - **nightly**: what we ship to the Firefox Nightly channel, using GeckoView Nightly.
- **beta**: what we ship to the Firefox Beta channel, using GeckoView Beta. It is more stable than nightly.
- **release**: what we ship as Firefox for Android, using GeckoView Release. It is the most stable.
<br /> nightly, beta, and release are unsigned and `debuggable=false` by default. If
<br /> you want these variants to be:
- automatically signed, see [Automatically signing release builds](#automatically-sign-release-builds)
- **debug** uses debug symbols and debug signing, adds tools like LeakCanary for troubleshooting, and does not strip unused or wasteful code - `debuggable=true`, see [Building debuggable release variants](#building-debuggable-release-variants)
- **fenixNightly** is a release build with nightly signing which uses the org.mozilla.fenix.nightly app id for nightly releases to Google Play
- **fenixBeta** is a release build with beta signing which uses the org.mozilla.fenix.beta app id for beta releases to Google Play
- **fenixProduction** is a release build with release signing which uses the org.mozilla.fenix app id for production releases to Google Play
- **fennecProduction** is an experimental build with release signing which uses the org.mozilla.firefox app id and supports upgrading the older Firefox. **WARNING** Pre-production versions of this may result in data loss.
- **forPerformanceTest**: see "Performance Build Variants" below.
#### Performance Build Variants #### Performance Build Variants
For accurate performance measurements, read this section! For accurate performance measurements, read this section!
If you want to analyze performance during **local development** (note: there is a non-trivial performance impact - see caveats): If you want to analyze performance during **local development** (note: there is a non-trivial performance impact - see caveats):
- Recommendation: use a `forPerformanceTest` variant with local Leanplum, Adjust, & Sentry API tokens: contact the front-end perf group for access to them - Recommendation: use a debuggable variant (see "local.properties helpers" below) with local Leanplum, Adjust, & Sentry API tokens: contact the front-end perf group for access to them
- Rationale: `forPerformanceTest` is a release variant with `debuggable` set to true. There are numerous performance-impacting differences between debug and release variants so we need a release variant. To profile, we also need debuggable, which is disabled on other release variants. If API tokens are not provided, the SDKs may change their behavior in non-trivial ways. - Rationale: There are numerous performance-impacting differences between debug and release variants so we need a release variant. To profile, we also need debuggable, which is disabled by default for release variants. If API tokens are not provided, the SDKs may change their behavior in non-trivial ways.
- Caveats: - Caveats:
- debuggable has a non-trivial & variable impact on performance but is needed to take profiles. - debuggable has a non-trivial & variable impact on performance but is needed to take profiles.
- Random experiment opt-in & feature flags may impact performance (see [perf-frontend-issues#45](https://github.com/mozilla-mobile/perf-frontend-issues/issues/45) for mitigation). - Random experiment opt-in & feature flags may impact performance (see [perf-frontend-issues#45](https://github.com/mozilla-mobile/perf-frontend-issues/issues/45) for mitigation).
- This is slower to build than debug builds because it does additional tasks (e.g. minification) similar to other release builds - This is slower to build than debug builds because it does additional tasks (e.g. minification) similar to other release builds
Nightly `forPerformanceTest` variants with API tokens already added [are also available from Taskcluster](https://firefox-ci-tc.services.mozilla.com/tasks/index/project.mobile.fenix.v2.performance-test/).
If you want to run **performance tests/benchmarks** in automation or locally: If you want to run **performance tests/benchmarks** in automation or locally:
- Recommendation: production builds. If debuggable is required, use recommendation above but note the caveat above. If your needs are not met, please contact the front-end perf group to identify a new solution. - Recommendation: production builds. If debuggable is required, use recommendation above but note the caveat above. If your needs are not met, please contact the front-end perf group to identify a new solution.
- Rationale: like the rationale above, we need release variants so the choice is based on the debuggable flag. - Rationale: like the rationale above, we need release variants so the choice is based on the debuggable flag.
For additional context on these recommendations, see [the perf build variant analysis](https://docs.google.com/document/d/1aW-m0HYncTDDiRz_2x6EjcYkjBpL9SHhhYix13Vil30/edit#). For additional context on these recommendations, see [the perf build variant analysis](https://docs.google.com/document/d/1aW-m0HYncTDDiRz_2x6EjcYkjBpL9SHhhYix13Vil30/edit#).
Before you can install any release variants including `forPerformanceTest`, **you will need to sign them:** see [Automatically signing release builds](#automatically-sign-release-builds) for details. Before you can install any release variants, **you will need to sign them:** see [Automatically signing release builds](#automatically-sign-release-builds) for details.
## Pre-push hooks ## Pre-push hooks
To reduce review turn-around time, we'd like all pushes to run tests locally. We'd To reduce review turn-around time, we'd like all pushes to run tests locally. We'd
@ -181,6 +174,22 @@ With this line, release build variants will automatically be signed with your de
This is helpful when you're building release variants frequently, for example to test feature flags and or do performance analyses. This is helpful when you're building release variants frequently, for example to test feature flags and or do performance analyses.
### Building debuggable release variants
Nightly, Beta and Release variants are getting published to Google Play and therefore are not debuggable. To locally create debuggable builds of those variants, add the following to `<proj-root>/local.properties`:
```sh
debuggable
```
### Setting raptor manifest flag
To set the raptor manifest flag in Nightly, Beta and Release variants, add the following to `<proj-root>/local.properties`:
```sh
raptorEnabled
```
### Auto-publication workflow for android-components and application-services ### Auto-publication workflow for android-components and application-services
If you're making changes to these projects and want to test them in Fenix, auto-publication workflow is the fastest, most reliable If you're making changes to these projects and want to test them in Fenix, auto-publication workflow is the fastest, most reliable
way to do that. way to do that.

View File

@ -6,7 +6,6 @@ apply plugin: 'com.android.application'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-android-extensions'
apply plugin: 'jacoco' apply plugin: 'jacoco'
apply from: "$project.rootDir/automation/gradle/versionCode.gradle"
apply plugin: 'androidx.navigation.safeargs.kotlin' apply plugin: 'androidx.navigation.safeargs.kotlin'
apply plugin: 'com.google.android.gms.oss-licenses-plugin' apply plugin: 'com.google.android.gms.oss-licenses-plugin'
@ -48,6 +47,10 @@ android {
if (gradle.hasProperty("localProperties.autosignReleaseWithDebugKey")) { if (gradle.hasProperty("localProperties.autosignReleaseWithDebugKey")) {
signingConfig signingConfigs.debug signingConfig signingConfigs.debug
} }
if (gradle.hasProperty("localProperties.debuggable")) {
debuggable true
}
} }
buildTypes { buildTypes {
@ -58,25 +61,31 @@ android {
resValue "bool", "IS_DEBUG", "true" resValue "bool", "IS_DEBUG", "true"
pseudoLocalesEnabled true pseudoLocalesEnabled true
} }
forPerformanceTest releaseTemplate >> { // the ">>" concatenates the raptor-specific options with the template nightly releaseTemplate >> {
applicationIdSuffix ".fenix.performancetest"
debuggable true
manifestPlaceholders = [
// Since we configure this build to behave like a "fennec" flavored build, we need
// to set a shared user id for the manifest. The actual values does not matter.
// However we pick a unique value to not "clash" with other Fenix/Fennec builds
// installed on the device.
"sharedUserId": "org.mozilla.fenix.performancetest.sharedID"
]
}
fenixProduction releaseTemplate >> {
applicationIdSuffix ".fenix" applicationIdSuffix ".fenix"
buildConfigField "boolean", "USE_RELEASE_VERSIONING", "true" buildConfigField "boolean", "USE_RELEASE_VERSIONING", "true"
def deepLinkSchemeValue = "fenix-nightly" def deepLinkSchemeValue = "fenix-nightly"
buildConfigField "String", "DEEP_LINK_SCHEME", "\"$deepLinkSchemeValue\"" buildConfigField "String", "DEEP_LINK_SCHEME", "\"$deepLinkSchemeValue\""
manifestPlaceholders = ["deepLinkScheme": deepLinkSchemeValue] manifestPlaceholders = ["deepLinkScheme": deepLinkSchemeValue]
} }
fennecProduction releaseTemplate >> { beta releaseTemplate >> {
buildConfigField "boolean", "USE_RELEASE_VERSIONING", "true"
applicationIdSuffix ".firefox_beta"
def deepLinkSchemeValue = "fenix-beta"
buildConfigField "String", "DEEP_LINK_SCHEME", "\"$deepLinkSchemeValue\""
manifestPlaceholders = [
// This release type is meant to replace Firefox (Beta channel) and therefore needs to inherit
// its sharedUserId for all eternity. See:
// https://searchfox.org/mozilla-central/search?q=moz_android_shared_id&case=false&regexp=false&path=
// Shipping an app update without sharedUserId can have
// fatal consequences. For example see:
// - https://issuetracker.google.com/issues/36924841
// - https://issuetracker.google.com/issues/36905922
"sharedUserId": "org.mozilla.firefox.sharedID",
"deepLinkScheme": deepLinkSchemeValue
]
}
release releaseTemplate >> {
buildConfigField "boolean", "USE_RELEASE_VERSIONING", "true" buildConfigField "boolean", "USE_RELEASE_VERSIONING", "true"
applicationIdSuffix ".firefox" applicationIdSuffix ".firefox"
def deepLinkSchemeValue = "fenix" def deepLinkSchemeValue = "fenix"
@ -93,49 +102,6 @@ android {
"deepLinkScheme": deepLinkSchemeValue "deepLinkScheme": deepLinkSchemeValue
] ]
} }
fennecBeta releaseTemplate >> {
buildConfigField "boolean", "USE_RELEASE_VERSIONING", "true"
applicationIdSuffix ".firefox_beta"
def deepLinkSchemeValue = "fenix-beta"
buildConfigField "String", "DEEP_LINK_SCHEME", "\"$deepLinkSchemeValue\""
manifestPlaceholders = [
// This release type is meant to replace Firefox (Release channel) and therefore needs to inherit
// its sharedUserId for all eternity. See:
// https://searchfox.org/mozilla-central/search?q=moz_android_shared_id&case=false&regexp=false&path=
// Shipping an app update without sharedUserId can have
// fatal consequences. For example see:
// - https://issuetracker.google.com/issues/36924841
// - https://issuetracker.google.com/issues/36905922
"sharedUserId": "org.mozilla.firefox.sharedID",
"deepLinkScheme": deepLinkSchemeValue
]
}
}
variantFilter { // There's a "release" build type that exists by default that we don't use (it's replaced by "nightly" and "beta")
if (buildType.name == 'release') {
setIgnore true
}
// Current build variant setup:
//
// | geckoNightly | geckoBeta |
// |--------------------|---------------|-----------|
// | debug | | | Both variants for testing and development.
// | forPerformanceTest | | | Both variants unless the perf team only cares about Nightly (TBD).
// | fenixProduction | | | Fenix Production (to be renamed `Nightly`) ships with GV Nightly
// | fennecProduction | | | Fenix build to replace production Firefox builds
// | fennecBeta | | | Fenix build to replace beta Firefox builds
def flavors = flavors*.name.toString().toLowerCase()
if (buildType.name == 'fenixProduction' && flavors.contains("geckobeta")) {
setIgnore true
}
if ((buildType.name == 'fennecProduction' || buildType.name == 'fennecBeta') && flavors.contains("geckonightly")) {
setIgnore true
}
} }
aaptOptions { aaptOptions {
@ -162,30 +128,20 @@ android {
androidTest { androidTest {
resources.srcDirs += ['src/androidTest/resources'] resources.srcDirs += ['src/androidTest/resources']
} }
fennecBeta { debug {
java.srcDirs = ['src/migration/java'] java.srcDirs = ['src/geckoNightly/java']
}
nightly {
java.srcDirs = ['src/geckoNightly/java']
}
beta {
java.srcDirs = ['src/migration/java', 'src/geckoBeta/java']
manifest.srcFile "src/migration/AndroidManifest.xml" manifest.srcFile "src/migration/AndroidManifest.xml"
} }
fennecProduction { release {
java.srcDirs = ['src/migration/java'] java.srcDirs = ['src/migration/java', 'src/geckoRelease/java']
manifest.srcFile "src/migration/AndroidManifest.xml" manifest.srcFile "src/migration/AndroidManifest.xml"
} }
forPerformanceTest {
// We want our performance test builds to use the same code as our "fennec" flavor builds
// since those builds will ship to our large user base.
java.srcDirs = ['src/migration/java']
manifest.srcFile "src/migration/AndroidManifest.xml"
}
}
productFlavors {
geckoNightly {
dimension "engine"
}
geckoBeta {
dimension "engine"
}
} }
splits { splits {
@ -216,8 +172,6 @@ android {
} }
} }
def baseVersionCode = generatedVersionCode
android.applicationVariants.all { variant -> android.applicationVariants.all { variant ->
// ------------------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------------------------
@ -252,27 +206,11 @@ android.applicationVariants.all { variant ->
// same version code. Therefore we need to have different version codes for our ARM and x86 // same version code. Therefore we need to have different version codes for our ARM and x86
// builds. // builds.
// Our generated version code now has a length of 9 (See automation/gradle/versionCode.gradle).
// Our x86 builds need a higher version code to avoid installing ARM builds on an x86 device
// with ARM compatibility mode.
variant.outputs.each { output -> variant.outputs.each { output ->
def abi = output.getFilter(OutputFile.ABI) def abi = output.getFilter(OutputFile.ABI)
// We use the same version code generator, that we inherited from Fennec, across all channels - even on
def versionCodeOverride // channels that never shipped a Fennec build.
if (variant.name.contains("Fennec")) { def versionCodeOverride = Config.generateFennecVersionCode(abi)
versionCodeOverride = Config.generateFennecVersionCode(abi)
} else if (abi == "x86_64") {
versionCodeOverride = baseVersionCode + 3
} else if (abi == "x86") {
versionCodeOverride = baseVersionCode + 2
} else if (abi == "arm64-v8a") {
versionCodeOverride = baseVersionCode + 1
} else if (abi == "armeabi-v7a") {
versionCodeOverride = baseVersionCode
} else {
throw RuntimeException("Unknown ABI: $abi")
}
println("versionCode for $abi = $versionCodeOverride") println("versionCode for $abi = $versionCodeOverride")
@ -377,8 +315,11 @@ ext.gleanDocsDirectory = "$rootDir/docs"
apply plugin: "org.mozilla.telemetry.glean-gradle-plugin" apply plugin: "org.mozilla.telemetry.glean-gradle-plugin"
dependencies { dependencies {
geckoNightlyImplementation Deps.mozilla_browser_engine_gecko_nightly debugImplementation Deps.mozilla_browser_engine_gecko_nightly
geckoBetaImplementation Deps.mozilla_browser_engine_gecko_beta
nightlyImplementation Deps.mozilla_browser_engine_gecko_nightly
betaImplementation Deps.mozilla_browser_engine_gecko_beta
releaseImplementation Deps.mozilla_browser_engine_gecko_release
implementation Deps.kotlin_stdlib implementation Deps.kotlin_stdlib
implementation Deps.kotlin_coroutines implementation Deps.kotlin_coroutines
@ -604,17 +545,15 @@ task printVariants {
fileName: it.outputFileName, fileName: it.outputFileName,
]}, ]},
build_type: it.buildType.name, build_type: it.buildType.name,
engine: it.productFlavors.find { it.dimension == 'engine' }.name,
name: it.name, name: it.name,
]} ]}
// AndroidTest is a special case not included above // AndroidTest is a special case not included above
variants.add([ variants.add([
apks: [[ apks: [[
abi: 'noarch', abi: 'noarch',
fileName: 'app-geckoNightly-debug-androidTest.apk', fileName: 'app-debug-androidTest.apk',
]], ]],
build_type: 'androidTest', build_type: 'androidTest',
engine: 'geckoNightly',
name: 'androidTest', name: 'androidTest',
]) ])
println 'variants: ' + groovy.json.JsonOutput.toJson(variants) println 'variants: ' + groovy.json.JsonOutput.toJson(variants)

View File

@ -10,7 +10,6 @@ import okhttp3.mockwebserver.MockWebServer
import org.junit.Rule import org.junit.Rule
import org.junit.Before import org.junit.Before
import org.junit.After import org.junit.After
import org.junit.Ignore
import org.junit.Test import org.junit.Test
import org.mozilla.fenix.helpers.AndroidAssetDispatcher import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityTestRule import org.mozilla.fenix.helpers.HomeActivityTestRule
@ -44,7 +43,6 @@ class SettingsAddonsTest {
} }
// Walks through settings add-ons menu to ensure all items are present // Walks through settings add-ons menu to ensure all items are present
@Ignore("Failing, see: https://github.com/mozilla-mobile/fenix/issues/13218")
@Test @Test
fun settingsAddonsItemsTest() { fun settingsAddonsItemsTest() {
homeScreen { homeScreen {
@ -76,7 +74,6 @@ class SettingsAddonsTest {
} }
} }
@Ignore("Failing, see: https://github.com/mozilla-mobile/fenix/issues/13220")
// Opens the addons settings menu, installs an addon, then uninstalls // Opens the addons settings menu, installs an addon, then uninstalls
@Test @Test
fun verifyAddonsCanBeUninstalled() { fun verifyAddonsCanBeUninstalled() {

View File

@ -2,8 +2,9 @@ package org.mozilla.fenix.ui.robots
import androidx.test.espresso.Espresso.onView import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.withContentDescription import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.matcher.ViewMatchers.withId
import org.hamcrest.CoreMatchers.allOf import org.hamcrest.CoreMatchers.allOf
import org.mozilla.fenix.R import org.mozilla.fenix.R
@ -35,11 +36,11 @@ class SettingsSubMenuAddonsManagerAddonDetailedMenuRobot {
} }
private fun assertAddonMenuItems() { private fun assertAddonMenuItems() {
enableSwitchButton().check(matches(isCompletelyDisplayed())) enableSwitchButton().check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
settingsButton().check(matches(isCompletelyDisplayed())) settingsButton().check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
detailsButton().check(matches(isCompletelyDisplayed())) detailsButton().check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
permissionsButton().check(matches(isCompletelyDisplayed())) permissionsButton().check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
removeAddonButton().check(matches(isCompletelyDisplayed())) removeAddonButton().check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
} }
} }

View File

@ -31,6 +31,7 @@ import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.platform.app.InstrumentationRegistry import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.By import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.UiSelector
import androidx.test.uiautomator.Until import androidx.test.uiautomator.Until
import org.hamcrest.Matcher import org.hamcrest.Matcher
import org.hamcrest.Matchers.allOf import org.hamcrest.Matchers.allOf
@ -350,10 +351,9 @@ class ThreeDotMenuMainRobot {
fun openAddonsManagerMenu(interact: SettingsSubMenuAddonsManagerRobot.() -> Unit): SettingsSubMenuAddonsManagerRobot.Transition { fun openAddonsManagerMenu(interact: SettingsSubMenuAddonsManagerRobot.() -> Unit): SettingsSubMenuAddonsManagerRobot.Transition {
clickAddonsManagerButton() clickAddonsManagerButton()
mDevice.waitNotNull( mDevice.findObject(
Until.findObject(By.text("Recommended")), UiSelector().text("Recommended")
waitingTime ).waitForExists(waitingTime)
)
SettingsSubMenuAddonsManagerRobot().interact() SettingsSubMenuAddonsManagerRobot().interact()
return SettingsSubMenuAddonsManagerRobot.Transition() return SettingsSubMenuAddonsManagerRobot.Transition()

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

View File

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

View File

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 59 KiB

View File

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 59 KiB

View File

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

Before

Width:  |  Height:  |  Size: 8.7 KiB

After

Width:  |  Height:  |  Size: 8.7 KiB

View File

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

View File

@ -1,29 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
<shortcut
android:shortcutId="open_new_tab"
android:enabled="true"
android:icon="@drawable/ic_static_shortcut_tab"
android:shortcutShortLabel="@string/home_screen_shortcut_open_new_tab_2"
android:shortcutLongLabel="@string/home_screen_shortcut_open_new_tab_2">
<intent
android:action="org.mozilla.fenix.OPEN_TAB"
android:targetPackage="org.mozilla.fenix.beta"
android:targetClass="org.mozilla.fenix.IntentReceiverActivity" />
</shortcut>
<shortcut
android:shortcutId="open_new_private_tab"
android:enabled="true"
android:icon="@drawable/ic_static_shortcut_private_tab"
android:shortcutShortLabel="@string/home_screen_shortcut_open_new_private_tab_2"
android:shortcutLongLabel="@string/home_screen_shortcut_open_new_private_tab_2">
<intent
android:action="org.mozilla.fenix.OPEN_PRIVATE_TAB"
android:targetPackage="org.mozilla.fenix.beta"
android:targetClass="org.mozilla.fenix.IntentReceiverActivity" />
</shortcut>
</shortcuts>

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

View File

@ -1,29 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
<shortcut
android:shortcutId="open_new_tab"
android:enabled="true"
android:icon="@drawable/ic_static_shortcut_tab"
android:shortcutShortLabel="@string/home_screen_shortcut_open_new_tab_2"
android:shortcutLongLabel="@string/home_screen_shortcut_open_new_tab_2">
<intent
android:action="org.mozilla.fenix.OPEN_TAB"
android:targetPackage="org.mozilla.fenix.nightly"
android:targetClass="org.mozilla.fenix.IntentReceiverActivity" />
</shortcut>
<shortcut
android:shortcutId="open_new_private_tab"
android:enabled="true"
android:icon="@drawable/ic_static_shortcut_private_tab"
android:shortcutShortLabel="@string/home_screen_shortcut_open_new_private_tab_2"
android:shortcutLongLabel="@string/home_screen_shortcut_open_new_private_tab_2">
<intent
android:action="org.mozilla.fenix.OPEN_PRIVATE_TAB"
android:targetPackage="org.mozilla.fenix.nightly"
android:targetClass="org.mozilla.fenix.IntentReceiverActivity" />
</shortcut>
</shortcuts>

View File

@ -1,18 +0,0 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.mozilla.fenix">
<application>
<service android:name=".customtabs.CustomTabsService">
<!-- Trusted Web Activities are currently only supported in nightly. -->
<intent-filter tools:node="removeAll" />
<intent-filter>
<action android:name="android.support.customtabs.action.CustomTabsService" />
<category android:name="androidx.browser.trusted.category.TrustedWebActivities" />
</intent-filter>
</service>
</application>
</manifest>

View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<resources>
<color name="ic_launcher_background">@color/nightly_launcher_background</color>
</resources>

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<!-- 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/. -->
<resources>
<!-- Name of the application -->
<string name="app_name" translatable="false">Firefox Nightly</string>
</resources>

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground_fennec_nightly"/>
</adaptive-icon>

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground_fennec_nightly"/>
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<resources>
<color name="ic_launcher_background">@color/debug_launcher_background</color>
</resources>

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<!-- 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/. -->
<resources>
<!-- Name of the application -->
<string name="app_name" translatable="false">Firefox Nightly</string>
</resources>

View File

@ -1,28 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
<shortcut
android:shortcutId="open_new_tab"
android:enabled="true"
android:icon="@drawable/ic_static_shortcut_tab"
android:shortcutShortLabel="@string/home_screen_shortcut_open_new_tab_2"
android:shortcutLongLabel="@string/home_screen_shortcut_open_new_tab_2">
<intent
android:action="org.mozilla.fenix.OPEN_TAB"
android:targetPackage="org.mozilla.fennec_aurora"
android:targetClass="org.mozilla.fenix.IntentReceiverActivity" />
</shortcut>
<shortcut
android:shortcutId="open_new_private_tab"
android:enabled="true"
android:icon="@drawable/ic_static_shortcut_private_tab"
android:shortcutShortLabel="@string/home_screen_shortcut_open_new_private_tab_2"
android:shortcutLongLabel="@string/home_screen_shortcut_open_new_private_tab_2">
<intent
android:action="org.mozilla.fenix.OPEN_PRIVATE_TAB"
android:targetPackage="org.mozilla.fennec_aurora"
android:targetClass="org.mozilla.fenix.IntentReceiverActivity" />
</shortcut>
</shortcuts>

View File

@ -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/. */
import android.content.Context
import android.os.Bundle
import mozilla.components.browser.engine.gecko.autofill.GeckoLoginDelegateWrapper
import mozilla.components.browser.engine.gecko.glean.GeckoAdapter
import mozilla.components.concept.engine.EngineSession.TrackingProtectionPolicy
import mozilla.components.concept.storage.LoginsStorage
import mozilla.components.lib.crash.handler.CrashHandlerService
import mozilla.components.service.sync.logins.GeckoLoginStorageDelegate
import org.mozilla.fenix.Config
import org.mozilla.fenix.ext.components
import org.mozilla.geckoview.GeckoRuntime
import org.mozilla.geckoview.GeckoRuntimeSettings
object GeckoProvider {
var testConfig: Bundle? = null
private var runtime: GeckoRuntime? = null
@Synchronized
fun getOrCreateRuntime(
context: Context,
storage: Lazy<LoginsStorage>,
trackingProtectionPolicy: TrackingProtectionPolicy
): GeckoRuntime {
if (runtime == null) {
runtime = createRuntime(context, storage, trackingProtectionPolicy)
}
return runtime!!
}
private fun createRuntime(
context: Context,
storage: Lazy<LoginsStorage>,
policy: TrackingProtectionPolicy
): GeckoRuntime {
val builder = GeckoRuntimeSettings.Builder()
testConfig?.let {
builder.extras(it)
.remoteDebuggingEnabled(true)
}
// Use meeee.
policy.hashCode()
val runtimeSettings = builder
.crashHandler(CrashHandlerService::class.java)
.telemetryDelegate(GeckoAdapter())
// TODO: Fix me!
// .contentBlocking(policy.toContentBlockingSetting())
.aboutConfigEnabled(Config.channel.isBeta)
.debugLogging(Config.channel.isDebug)
.build()
val settings = context.components.settings
if (!settings.shouldUseAutoSize) {
runtimeSettings.automaticFontSizeAdjustment = false
val fontSize = settings.fontSizeFactor
runtimeSettings.fontSizeFactor = fontSize
}
val geckoRuntime = GeckoRuntime.create(context, runtimeSettings)
val loginStorageDelegate = GeckoLoginStorageDelegate(storage)
geckoRuntime.loginStorageDelegate = GeckoLoginDelegateWrapper(loginStorageDelegate)
return geckoRuntime
}
}

View File

@ -17,6 +17,7 @@ enum class BrowserDirection(@IdRes val fragmentId: Int) {
FromGlobal(0), FromGlobal(0),
FromHome(R.id.homeFragment), FromHome(R.id.homeFragment),
FromSearch(R.id.searchFragment), FromSearch(R.id.searchFragment),
FromSearchDialog(R.id.searchDialogFragment),
FromSettings(R.id.settingsFragment), FromSettings(R.id.settingsFragment),
FromSyncedTabs(R.id.syncedTabsFragment), FromSyncedTabs(R.id.syncedTabsFragment),
FromBookmarks(R.id.bookmarkFragment), FromBookmarks(R.id.bookmarkFragment),

View File

@ -5,14 +5,14 @@
package org.mozilla.fenix package org.mozilla.fenix
enum class ReleaseChannel { enum class ReleaseChannel {
FenixDebug, Debug,
FenixProduction, Nightly,
FennecProduction, Beta,
FennecBeta; Release;
val isReleased: Boolean val isReleased: Boolean
get() = when (this) { get() = when (this) {
FenixDebug -> false Debug -> false
else -> true else -> true
} }
@ -27,51 +27,39 @@ enum class ReleaseChannel {
get() = !this.isReleased get() = !this.isReleased
val isReleaseOrBeta: Boolean val isReleaseOrBeta: Boolean
get() = when (this) { get() = this == Release || this == Beta
FennecProduction -> true
FennecBeta -> true
else -> false
}
val isRelease: Boolean val isRelease: Boolean
get() = when (this) { get() = when (this) {
FennecProduction -> true Release -> true
else -> false else -> false
} }
val isBeta: Boolean val isBeta: Boolean
get() = when (this) { get() = this == Beta
FennecBeta -> true
else -> false
}
val isNightlyOrDebug: Boolean val isNightlyOrDebug: Boolean
get() = when (this) { get() = this == Debug || this == Nightly
FenixDebug -> true
FenixProduction -> true
else -> false
}
/**
* Is this a build for a release channel that we used to ship Fennec on?
*/
val isFennec: Boolean val isFennec: Boolean
get() = this in fennecChannels get() = this in fennecChannels
/**
* Is this build for a "pure" Fenix channel that we never shipped Fennec on?
*/
val isFenix: Boolean val isFenix: Boolean
get() = !isFennec get() = !isFennec
} }
object Config { object Config {
val channel = when (BuildConfig.BUILD_TYPE) { val channel = when (BuildConfig.BUILD_TYPE) {
"fenixProduction" -> ReleaseChannel.FenixProduction "debug" -> ReleaseChannel.Debug
"debug" -> ReleaseChannel.FenixDebug "nightly" -> ReleaseChannel.Nightly
"fennecProduction" -> ReleaseChannel.FennecProduction "beta" -> ReleaseChannel.Beta
"fennecBeta" -> ReleaseChannel.FennecBeta "release" -> ReleaseChannel.Release
// Builds for local performance analysis, recording benchmarks, automation, etc.
// This should be treated like a released channel because we want to test
// what users experience and there are performance-impacting changes in debug
// release channels (e.g. logging) that are never intended to be shipped.
"forPerformanceTest" -> ReleaseChannel.FenixProduction
else -> { else -> {
throw IllegalStateException("Unknown build type: ${BuildConfig.BUILD_TYPE}") throw IllegalStateException("Unknown build type: ${BuildConfig.BUILD_TYPE}")
} }
@ -79,6 +67,6 @@ object Config {
} }
private val fennecChannels: List<ReleaseChannel> = listOf( private val fennecChannels: List<ReleaseChannel> = listOf(
ReleaseChannel.FennecBeta, ReleaseChannel.Beta,
ReleaseChannel.FennecProduction ReleaseChannel.Release
) )

View File

@ -86,6 +86,7 @@ import org.mozilla.fenix.library.history.HistoryFragmentDirections
import org.mozilla.fenix.perf.Performance import org.mozilla.fenix.perf.Performance
import org.mozilla.fenix.perf.StartupTimeline import org.mozilla.fenix.perf.StartupTimeline
import org.mozilla.fenix.search.SearchFragmentDirections import org.mozilla.fenix.search.SearchFragmentDirections
import org.mozilla.fenix.searchdialog.SearchDialogFragmentDirections
import org.mozilla.fenix.session.NotificationSessionObserver import org.mozilla.fenix.session.NotificationSessionObserver
import org.mozilla.fenix.settings.SettingsFragmentDirections import org.mozilla.fenix.settings.SettingsFragmentDirections
import org.mozilla.fenix.settings.TrackingProtectionFragmentDirections import org.mozilla.fenix.settings.TrackingProtectionFragmentDirections
@ -553,9 +554,11 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
BrowserDirection.FromGlobal -> BrowserDirection.FromGlobal ->
NavGraphDirections.actionGlobalBrowser(customTabSessionId) NavGraphDirections.actionGlobalBrowser(customTabSessionId)
BrowserDirection.FromHome -> BrowserDirection.FromHome ->
HomeFragmentDirections.actionHomeFragmentToBrowserFragment(customTabSessionId, true) HomeFragmentDirections.actionGlobalBrowser(customTabSessionId)
BrowserDirection.FromSearch -> BrowserDirection.FromSearch ->
SearchFragmentDirections.actionGlobalBrowser(customTabSessionId) SearchFragmentDirections.actionGlobalBrowser(customTabSessionId)
BrowserDirection.FromSearchDialog ->
SearchDialogFragmentDirections.actionGlobalBrowser(customTabSessionId)
BrowserDirection.FromSettings -> BrowserDirection.FromSettings ->
SettingsFragmentDirections.actionGlobalBrowser(customTabSessionId) SettingsFragmentDirections.actionGlobalBrowser(customTabSessionId)
BrowserDirection.FromSyncedTabs -> BrowserDirection.FromSyncedTabs ->

View File

@ -10,6 +10,7 @@ import android.os.Bundle
import android.os.StrictMode import android.os.StrictMode
import androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting
import mozilla.components.feature.intent.processing.IntentProcessor import mozilla.components.feature.intent.processing.IntentProcessor
import org.mozilla.fenix.HomeActivity.Companion.PRIVATE_BROWSING_MODE
import org.mozilla.fenix.components.IntentProcessorType import org.mozilla.fenix.components.IntentProcessorType
import org.mozilla.fenix.components.getType import org.mozilla.fenix.components.getType
import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.components.metrics.Event
@ -43,7 +44,15 @@ class IntentReceiverActivity : Activity() {
fun processIntent(intent: Intent) { fun processIntent(intent: Intent) {
// Call process for side effects, short on the first that returns true // Call process for side effects, short on the first that returns true
val processor = getIntentProcessors().firstOrNull { it.process(intent) } val private = settings().openLinksInAPrivateTab
intent.putExtra(PRIVATE_BROWSING_MODE, private)
if (private) {
components.analytics.metrics.track(Event.OpenedLink(Event.OpenedLink.Mode.PRIVATE))
} else {
components.analytics.metrics.track(Event.OpenedLink(Event.OpenedLink.Mode.NORMAL))
}
val processor = getIntentProcessors(private).firstOrNull { it.process(intent) }
val intentProcessorType = components.intentProcessors.getType(processor) val intentProcessorType = components.intentProcessors.getType(processor)
launch(intent, intentProcessorType) launch(intent, intentProcessorType)
@ -65,17 +74,14 @@ class IntentReceiverActivity : Activity() {
finish() // must finish() after starting the other activity finish() // must finish() after starting the other activity
} }
private fun getIntentProcessors(): List<IntentProcessor> { private fun getIntentProcessors(private: Boolean): List<IntentProcessor> {
val modeDependentProcessors = if (settings().openLinksInAPrivateTab) { val modeDependentProcessors = if (private) {
components.analytics.metrics.track(Event.OpenedLink(Event.OpenedLink.Mode.PRIVATE))
intent.putExtra(HomeActivity.PRIVATE_BROWSING_MODE, true)
listOf( listOf(
components.intentProcessors.privateCustomTabIntentProcessor, components.intentProcessors.privateCustomTabIntentProcessor,
components.intentProcessors.privateIntentProcessor components.intentProcessors.privateIntentProcessor
) )
} else { } else {
components.analytics.metrics.track(Event.OpenedLink(Event.OpenedLink.Mode.NORMAL)) components.analytics.metrics.track(Event.OpenedLink(Event.OpenedLink.Mode.NORMAL))
intent.putExtra(HomeActivity.PRIVATE_BROWSING_MODE, false)
listOf( listOf(
components.intentProcessors.customTabIntentProcessor, components.intentProcessors.customTabIntentProcessor,
components.intentProcessors.intentProcessor components.intentProcessors.intentProcessor

View File

@ -198,8 +198,7 @@ class InstalledAddonDetailsFragment : Fragment() {
components.useCases.tabsUseCases.addTab(settingUrl) components.useCases.tabsUseCases.addTab(settingUrl)
} }
InstalledAddonDetailsFragmentDirections InstalledAddonDetailsFragmentDirections.actionGlobalBrowser(null)
.actionGlobalBrowser(null, false)
} else { } else {
InstalledAddonDetailsFragmentDirections InstalledAddonDetailsFragmentDirections
.actionInstalledAddonFragmentToAddonInternalSettingsFragment(addon) .actionInstalledAddonFragmentToAddonInternalSettingsFragment(addon)

View File

@ -204,7 +204,6 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session
engineView = WeakReference(engineView), engineView = WeakReference(engineView),
swipeRefresh = WeakReference(swipeRefresh), swipeRefresh = WeakReference(swipeRefresh),
viewLifecycleScope = WeakReference(viewLifecycleOwner.lifecycleScope), viewLifecycleScope = WeakReference(viewLifecycleOwner.lifecycleScope),
arguments = requireArguments(),
firstContentfulHappened = ::didFirstContentfulHappen firstContentfulHappened = ::didFirstContentfulHappen
).apply { ).apply {
beginAnimateInIfNecessary() beginAnimateInIfNecessary()
@ -586,8 +585,15 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session
} }
.ifChanged { it.content.firstContentfulPaint } .ifChanged { it.content.firstContentfulPaint }
.collect { .collect {
engineView?.asView()?.isVisible = val showEngineView =
it.content.firstContentfulPaint || it.content.progress == 100 it.content.firstContentfulPaint || it.content.progress == 100
if (showEngineView) {
engineView?.asView()?.isVisible = true
swipeRefresh.alpha = 1f
} else {
engineView?.asView()?.isVisible = false
}
} }
} }
} }

View File

@ -4,19 +4,14 @@
package org.mozilla.fenix.browser package org.mozilla.fenix.browser
import android.animation.ValueAnimator
import android.content.Context import android.content.Context
import android.graphics.Color import android.graphics.Color
import android.graphics.drawable.ColorDrawable import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.view.View import android.view.View
import android.view.animation.DecelerateInterpolator
import androidx.core.animation.doOnEnd
import androidx.core.graphics.drawable.toDrawable import androidx.core.graphics.drawable.toDrawable
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.LifecycleCoroutineScope import androidx.lifecycle.LifecycleCoroutineScope
import androidx.navigation.NavOptions import androidx.navigation.NavOptions
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import mozilla.components.concept.engine.EngineView import mozilla.components.concept.engine.EngineView
@ -34,7 +29,6 @@ class BrowserAnimator(
private val engineView: WeakReference<EngineView>, private val engineView: WeakReference<EngineView>,
private val swipeRefresh: WeakReference<View>, private val swipeRefresh: WeakReference<View>,
private val viewLifecycleScope: WeakReference<LifecycleCoroutineScope>, private val viewLifecycleScope: WeakReference<LifecycleCoroutineScope>,
private val arguments: Bundle,
private val firstContentfulHappened: () -> Boolean private val firstContentfulHappened: () -> Boolean
) { ) {
@ -44,46 +38,19 @@ class BrowserAnimator(
private val unwrappedSwipeRefresh: View? private val unwrappedSwipeRefresh: View?
get() = swipeRefresh.get() get() = swipeRefresh.get()
private val browserZoomInValueAnimator = ValueAnimator.ofFloat(0f, END_ANIMATOR_VALUE).apply {
addUpdateListener {
unwrappedSwipeRefresh?.scaleX =
STARTING_XY_SCALE + XY_SCALE_MULTIPLIER * it.animatedFraction
unwrappedSwipeRefresh?.scaleY =
STARTING_XY_SCALE + XY_SCALE_MULTIPLIER * it.animatedFraction
unwrappedSwipeRefresh?.alpha = it.animatedFraction
}
doOnEnd {
if (firstContentfulHappened()) {
unwrappedEngineView?.asView()?.visibility = View.VISIBLE
}
unwrappedSwipeRefresh?.background = null
arguments.putBoolean(SHOULD_ANIMATE_FLAG, false)
}
interpolator = DecelerateInterpolator()
duration = ANIMATION_DURATION
}
/**
* Triggers the *zoom in* browser animation to run if necessary (based on the SHOULD_ANIMATE_FLAG).
* Also removes the flag from the bundle so it is not played on future entries into the fragment.
*/
fun beginAnimateInIfNecessary() { fun beginAnimateInIfNecessary() {
val shouldAnimate = arguments.getBoolean(SHOULD_ANIMATE_FLAG, false) if (unwrappedSwipeRefresh?.context?.settings()?.waitToShowPageUntilFirstPaint == true) {
if (shouldAnimate) { if (firstContentfulHappened()) {
viewLifecycleScope.get()?.launch(Dispatchers.Main) { viewLifecycleScope.get()?.launch {
delay(ANIMATION_DELAY) delay(100)
captureEngineViewAndDrawStatically { unwrappedEngineView?.asView()?.visibility = View.VISIBLE
unwrappedSwipeRefresh?.alpha = 0f unwrappedSwipeRefresh?.background = null
browserZoomInValueAnimator.start() unwrappedSwipeRefresh?.alpha = 1f
} }
} }
} else { } else {
unwrappedSwipeRefresh?.alpha = 1f unwrappedSwipeRefresh?.alpha = 1f
if (firstContentfulHappened()) { unwrappedEngineView?.asView()?.visibility = View.VISIBLE
unwrappedEngineView?.asView()?.visibility = View.VISIBLE
}
unwrappedSwipeRefresh?.background = null unwrappedSwipeRefresh?.background = null
} }
} }
@ -124,13 +91,6 @@ class BrowserAnimator(
} }
companion object { companion object {
private const val SHOULD_ANIMATE_FLAG = "shouldAnimate"
private const val ANIMATION_DELAY = 50L
private const val ANIMATION_DURATION = 115L
private const val END_ANIMATOR_VALUE = 500f
private const val XY_SCALE_MULTIPLIER = .05f
private const val STARTING_XY_SCALE = .95f
fun getToolbarNavOptions(context: Context): NavOptions { fun getToolbarNavOptions(context: Context): NavOptions {
val navOptions = NavOptions.Builder() val navOptions = NavOptions.Builder()

View File

@ -102,9 +102,9 @@ fun isSentryEnabled() = !BuildConfig.SENTRY_TOKEN.isNullOrEmpty()
private fun getSentryProjectUrl(): String? { private fun getSentryProjectUrl(): String? {
val baseUrl = "https://sentry.prod.mozaws.net/operations" val baseUrl = "https://sentry.prod.mozaws.net/operations"
return when (Config.channel) { return when (Config.channel) {
ReleaseChannel.FenixProduction -> "$baseUrl/fenix" ReleaseChannel.Nightly -> "$baseUrl/fenix"
ReleaseChannel.FennecProduction -> "$baseUrl/fenix-fennec" ReleaseChannel.Release -> "$baseUrl/fenix-fennec"
ReleaseChannel.FennecBeta -> "$baseUrl/fenix-fennec-beta" ReleaseChannel.Beta -> "$baseUrl/fenix-fennec-beta"
else -> null else -> null
} }
} }

View File

@ -81,6 +81,9 @@ class DefaultBrowserToolbarController(
private val onCloseTab: (Session) -> Unit private val onCloseTab: (Session) -> Unit
) : BrowserToolbarController { ) : BrowserToolbarController {
private val useNewSearchExperience
get() = activity.settings().useNewSearchExperience
private val currentSession private val currentSession
get() = customTabSession ?: activity.components.core.sessionManager.selectedSession get() = customTabSession ?: activity.components.core.sessionManager.selectedSession
@ -91,11 +94,25 @@ class DefaultBrowserToolbarController(
internal var ioScope: CoroutineScope = CoroutineScope(Dispatchers.IO) internal var ioScope: CoroutineScope = CoroutineScope(Dispatchers.IO)
override fun handleToolbarPaste(text: String) { override fun handleToolbarPaste(text: String) {
val directions = BrowserFragmentDirections.actionBrowserFragmentToSearchFragment( if (useNewSearchExperience) {
sessionId = currentSession?.id, navController.nav(
pastedText = text R.id.browserFragment, BrowserFragmentDirections.actionGlobalSearchDialog(
) sessionId = currentSession?.id,
navController.nav(R.id.browserFragment, directions, getToolbarNavOptions(activity)) pastedText = text
), getToolbarNavOptions(activity)
)
} else {
browserAnimator.captureEngineViewAndDrawStatically {
navController.nav(
R.id.browserFragment,
BrowserFragmentDirections.actionBrowserFragmentToSearchFragment(
sessionId = currentSession?.id,
pastedText = text
),
getToolbarNavOptions(activity)
)
}
}
} }
override fun handleToolbarPasteAndGo(text: String) { override fun handleToolbarPasteAndGo(text: String) {
@ -117,11 +134,23 @@ class DefaultBrowserToolbarController(
Event.SearchBarTapped(Event.SearchBarTapped.Source.BROWSER) Event.SearchBarTapped(Event.SearchBarTapped.Source.BROWSER)
) )
val directions = BrowserFragmentDirections.actionBrowserFragmentToSearchFragment( if (useNewSearchExperience) {
currentSession?.id navController.nav(
R.id.browserFragment, BrowserFragmentDirections.actionGlobalSearchDialog(
currentSession?.id
), getToolbarNavOptions(activity)
) )
} else {
navController.nav(R.id.browserFragment, directions, getToolbarNavOptions(activity)) browserAnimator.captureEngineViewAndDrawStatically {
navController.nav(
R.id.browserFragment,
BrowserFragmentDirections.actionBrowserFragmentToSearchFragment(
currentSession?.id
),
getToolbarNavOptions(activity)
)
}
}
} }
override fun handleTabCounterClick() { override fun handleTabCounterClick() {
@ -146,7 +175,11 @@ class DefaultBrowserToolbarController(
if (sessionManager.sessionsOfType(it.private).count() == 1) { if (sessionManager.sessionsOfType(it.private).count() == 1) {
// The tab tray always returns to normal mode so do that here too // The tab tray always returns to normal mode so do that here too
activity.browsingModeManager.mode = BrowsingMode.Normal activity.browsingModeManager.mode = BrowsingMode.Normal
navController.navigate(BrowserFragmentDirections.actionGlobalHome(sessionToDelete = it.id)) navController.navigate(
BrowserFragmentDirections.actionGlobalHome(
sessionToDelete = it.id
)
)
} else { } else {
onCloseTab.invoke(it) onCloseTab.invoke(it)
activity.components.useCases.tabsUseCases.removeTab.invoke(it) activity.components.useCases.tabsUseCases.removeTab.invoke(it)
@ -195,7 +228,7 @@ class DefaultBrowserToolbarController(
val directions = BrowserFragmentDirections.actionBrowserFragmentToSettingsFragment() val directions = BrowserFragmentDirections.actionBrowserFragmentToSettingsFragment()
navController.nav(R.id.browserFragment, directions) navController.nav(R.id.browserFragment, directions)
} }
ToolbarMenu.Item.SyncedTabs -> { ToolbarMenu.Item.SyncedTabs -> browserAnimator.captureEngineViewAndDrawStatically {
navController.nav( navController.nav(
R.id.browserFragment, R.id.browserFragment,
BrowserFragmentDirections.actionBrowserFragmentToSyncedTabsFragment() BrowserFragmentDirections.actionBrowserFragmentToSyncedTabsFragment()
@ -256,7 +289,7 @@ class DefaultBrowserToolbarController(
activity.components.analytics.metrics.track(Event.FindInPageOpened) activity.components.analytics.metrics.track(Event.FindInPageOpened)
} }
ToolbarMenu.Item.AddonsManager -> { ToolbarMenu.Item.AddonsManager -> browserAnimator.captureEngineViewAndDrawStatically {
navController.nav( navController.nav(
R.id.browserFragment, R.id.browserFragment,
BrowserFragmentDirections.actionGlobalAddonsManagementFragment() BrowserFragmentDirections.actionGlobalAddonsManagementFragment()
@ -332,13 +365,13 @@ class DefaultBrowserToolbarController(
bookmarkTapped(it) bookmarkTapped(it)
} }
} }
ToolbarMenu.Item.Bookmarks -> { ToolbarMenu.Item.Bookmarks -> browserAnimator.captureEngineViewAndDrawStatically {
navController.nav( navController.nav(
R.id.browserFragment, R.id.browserFragment,
BrowserFragmentDirections.actionGlobalBookmarkFragment(BookmarkRoot.Mobile.id) BrowserFragmentDirections.actionGlobalBookmarkFragment(BookmarkRoot.Mobile.id)
) )
} }
ToolbarMenu.Item.History -> { ToolbarMenu.Item.History -> browserAnimator.captureEngineViewAndDrawStatically {
navController.nav( navController.nav(
R.id.browserFragment, R.id.browserFragment,
BrowserFragmentDirections.actionGlobalHistoryFragment() BrowserFragmentDirections.actionGlobalHistoryFragment()

View File

@ -478,9 +478,7 @@ class HomeFragment : Fragment() {
engineSessionState = state engineSessionState = state
) )
findNavController().navigate( findNavController().navigate(
HomeFragmentDirections.actionHomeFragmentToBrowserFragment( HomeFragmentDirections.actionGlobalBrowser(null)
null
)
) )
}, },
operation = { }, operation = { },
@ -674,7 +672,9 @@ class HomeFragment : Fragment() {
private fun navigateToSearch() { private fun navigateToSearch() {
val directions = if (requireContext().settings().useNewSearchExperience) { val directions = if (requireContext().settings().useNewSearchExperience) {
HomeFragmentDirections.actionGlobalSearchDialog() HomeFragmentDirections.actionGlobalSearchDialog(
sessionId = null
)
} else { } else {
HomeFragmentDirections.actionGlobalSearch( HomeFragmentDirections.actionGlobalSearch(
sessionId = null sessionId = null

View File

@ -91,7 +91,7 @@ class BookmarkAdapter(val emptyView: View, val interactor: BookmarkViewInteracto
override fun getItemCount(): Int = tree.size override fun getItemCount(): Int = tree.size
override fun onBindViewHolder(holder: BookmarkNodeViewHolder, position: Int) { override fun onBindViewHolder(holder: BookmarkNodeViewHolder, position: Int) {
holder.bind(tree[position]) holder.bind(tree[position], mode)
} }
} }

View File

@ -139,6 +139,7 @@ class BookmarkView(
} }
fun update(state: BookmarkFragmentState) { fun update(state: BookmarkFragmentState) {
val oldMode = mode
tree = state.tree tree = state.tree
if (state.mode != mode) { if (state.mode != mode) {
mode = state.mode mode = state.mode
@ -148,6 +149,10 @@ class BookmarkView(
} }
bookmarkAdapter.updateData(state.tree, mode) bookmarkAdapter.updateData(state.tree, mode)
if (state.mode != oldMode) {
bookmarkAdapter.notifyDataSetChanged()
}
when (mode) { when (mode) {
is BookmarkFragmentState.Mode.Normal -> { is BookmarkFragmentState.Mode.Normal -> {
setUiForNormalMode(state.tree) setUiForNormalMode(state.tree)

View File

@ -13,6 +13,7 @@ import org.mozilla.fenix.ext.hideAndDisable
import org.mozilla.fenix.ext.showAndEnable import org.mozilla.fenix.ext.showAndEnable
import org.mozilla.fenix.library.LibrarySiteItemView import org.mozilla.fenix.library.LibrarySiteItemView
import org.mozilla.fenix.library.SelectionHolder import org.mozilla.fenix.library.SelectionHolder
import org.mozilla.fenix.library.bookmarks.BookmarkFragmentState
import org.mozilla.fenix.library.bookmarks.BookmarkViewInteractor import org.mozilla.fenix.library.bookmarks.BookmarkViewInteractor
import org.mozilla.fenix.library.bookmarks.inRoots import org.mozilla.fenix.library.bookmarks.inRoots
@ -27,7 +28,10 @@ class BookmarkFolderViewHolder(
override var item: BookmarkNode? = null override var item: BookmarkNode? = null
override fun bind(item: BookmarkNode) { override fun bind(
item: BookmarkNode,
mode: BookmarkFragmentState.Mode
) {
this.item = item this.item = item
containerView.displayAs(LibrarySiteItemView.ItemType.FOLDER) containerView.displayAs(LibrarySiteItemView.ItemType.FOLDER)
@ -36,10 +40,10 @@ class BookmarkFolderViewHolder(
if (!item.inRoots()) { if (!item.inRoots()) {
setupMenu(item) setupMenu(item)
if (selectionHolder.selectedItems.isEmpty()) { if (mode is BookmarkFragmentState.Mode.Selecting) {
containerView.overflowView.showAndEnable()
} else {
containerView.overflowView.hideAndDisable() containerView.overflowView.hideAndDisable()
} else {
containerView.overflowView.showAndEnable()
} }
} else { } else {
containerView.overflowView.visibility = View.GONE containerView.overflowView.visibility = View.GONE

View File

@ -9,6 +9,7 @@ import org.mozilla.fenix.ext.hideAndDisable
import org.mozilla.fenix.ext.showAndEnable import org.mozilla.fenix.ext.showAndEnable
import org.mozilla.fenix.library.LibrarySiteItemView import org.mozilla.fenix.library.LibrarySiteItemView
import org.mozilla.fenix.library.SelectionHolder import org.mozilla.fenix.library.SelectionHolder
import org.mozilla.fenix.library.bookmarks.BookmarkFragmentState
import org.mozilla.fenix.library.bookmarks.BookmarkViewInteractor import org.mozilla.fenix.library.bookmarks.BookmarkViewInteractor
/** /**
@ -22,15 +23,18 @@ class BookmarkItemViewHolder(
override var item: BookmarkNode? = null override var item: BookmarkNode? = null
override fun bind(item: BookmarkNode) { override fun bind(
item: BookmarkNode,
mode: BookmarkFragmentState.Mode
) {
this.item = item this.item = item
containerView.displayAs(LibrarySiteItemView.ItemType.SITE) containerView.displayAs(LibrarySiteItemView.ItemType.SITE)
if (selectionHolder.selectedItems.isEmpty()) { if (mode is BookmarkFragmentState.Mode.Selecting) {
containerView.overflowView.showAndEnable()
} else {
containerView.overflowView.hideAndDisable() containerView.overflowView.hideAndDisable()
} else {
containerView.overflowView.showAndEnable()
} }
setupMenu(item) setupMenu(item)
containerView.titleView.text = if (item.title.isNullOrBlank()) item.url else item.title containerView.titleView.text = if (item.title.isNullOrBlank()) item.url else item.title

View File

@ -9,6 +9,7 @@ import kotlinx.android.extensions.LayoutContainer
import mozilla.components.concept.storage.BookmarkNode import mozilla.components.concept.storage.BookmarkNode
import org.mozilla.fenix.library.LibrarySiteItemView import org.mozilla.fenix.library.LibrarySiteItemView
import org.mozilla.fenix.library.SelectionHolder import org.mozilla.fenix.library.SelectionHolder
import org.mozilla.fenix.library.bookmarks.BookmarkFragmentState
import org.mozilla.fenix.library.bookmarks.BookmarkItemMenu import org.mozilla.fenix.library.bookmarks.BookmarkItemMenu
import org.mozilla.fenix.library.bookmarks.BookmarkViewInteractor import org.mozilla.fenix.library.bookmarks.BookmarkViewInteractor
@ -22,7 +23,10 @@ abstract class BookmarkNodeViewHolder(
abstract var item: BookmarkNode? abstract var item: BookmarkNode?
abstract fun bind(item: BookmarkNode) abstract fun bind(
item: BookmarkNode,
mode: BookmarkFragmentState.Mode
)
protected fun setSelectionListeners(item: BookmarkNode, selectionHolder: SelectionHolder<BookmarkNode>) { protected fun setSelectionListeners(item: BookmarkNode, selectionHolder: SelectionHolder<BookmarkNode>) {
containerView.setSelectionInteractor(item, selectionHolder, interactor) containerView.setSelectionInteractor(item, selectionHolder, interactor)

View File

@ -6,6 +6,7 @@ package org.mozilla.fenix.library.bookmarks.viewholders
import mozilla.components.concept.storage.BookmarkNode import mozilla.components.concept.storage.BookmarkNode
import org.mozilla.fenix.library.LibrarySiteItemView import org.mozilla.fenix.library.LibrarySiteItemView
import org.mozilla.fenix.library.bookmarks.BookmarkFragmentState
import org.mozilla.fenix.library.bookmarks.BookmarkViewInteractor import org.mozilla.fenix.library.bookmarks.BookmarkViewInteractor
/** /**
@ -18,7 +19,10 @@ class BookmarkSeparatorViewHolder(
override var item: BookmarkNode? = null override var item: BookmarkNode? = null
override fun bind(item: BookmarkNode) { override fun bind(
item: BookmarkNode,
mode: BookmarkFragmentState.Mode
) {
this.item = item this.item = item
containerView.displayAs(LibrarySiteItemView.ItemType.SEPARATOR) containerView.displayAs(LibrarySiteItemView.ItemType.SEPARATOR)
setupMenu(item) setupMenu(item)

View File

@ -0,0 +1,180 @@
/* 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.searchdialog
import android.content.Intent
import androidx.navigation.NavController
import mozilla.components.browser.search.SearchEngine
import mozilla.components.browser.session.Session
import mozilla.components.browser.session.SessionManager
import mozilla.components.support.ktx.kotlin.isUrl
import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.components.metrics.MetricController
import org.mozilla.fenix.components.metrics.MetricsUtils
import org.mozilla.fenix.components.searchengine.CustomSearchEngineStore
import org.mozilla.fenix.crashes.CrashListActivity
import org.mozilla.fenix.ext.navigateSafe
import org.mozilla.fenix.search.SearchController
import org.mozilla.fenix.search.SearchFragmentAction
import org.mozilla.fenix.search.SearchFragmentStore
import org.mozilla.fenix.settings.SupportUtils
import org.mozilla.fenix.utils.Settings
@Suppress("TooManyFunctions", "LongParameterList")
class SearchDialogController(
private val activity: HomeActivity,
private val sessionManager: SessionManager,
private val store: SearchFragmentStore,
private val navController: NavController,
private val settings: Settings,
private val metrics: MetricController,
private val clearToolbarFocus: () -> Unit
) : SearchController {
override fun handleUrlCommitted(url: String) {
when (url) {
"about:crashes" -> {
// The list of past crashes can be accessed via "settings > about", but desktop and
// fennec users may be used to navigating to "about:crashes". So we intercept this here
// and open the crash list activity instead.
activity.startActivity(Intent(activity, CrashListActivity::class.java))
}
"moz://a" -> openSearchOrUrl(SupportUtils.getMozillaPageUrl(SupportUtils.MozillaPage.MANIFESTO))
else -> if (url.isNotBlank()) {
openSearchOrUrl(url)
}
}
}
private fun openSearchOrUrl(url: String) {
activity.openToBrowserAndLoad(
searchTermOrURL = url,
newTab = store.state.tabId == null,
from = BrowserDirection.FromSearchDialog,
engine = store.state.searchEngineSource.searchEngine
)
val event = if (url.isUrl()) {
Event.EnteredUrl(false)
} else {
settings.incrementActiveSearchCount()
val searchAccessPoint = when (store.state.searchAccessPoint) {
Event.PerformedSearch.SearchAccessPoint.NONE -> Event.PerformedSearch.SearchAccessPoint.ACTION
else -> store.state.searchAccessPoint
}
searchAccessPoint?.let { sap ->
MetricsUtils.createSearchEvent(
store.state.searchEngineSource.searchEngine,
activity,
sap
)
}
}
event?.let { metrics.track(it) }
}
override fun handleEditingCancelled() {
clearToolbarFocus()
}
override fun handleTextChanged(text: String) {
// Display the search shortcuts on each entry of the search fragment (see #5308)
val textMatchesCurrentUrl = store.state.url == text
val textMatchesCurrentSearch = store.state.searchTerms == text
store.dispatch(SearchFragmentAction.UpdateQuery(text))
store.dispatch(
SearchFragmentAction.ShowSearchShortcutEnginePicker(
(textMatchesCurrentUrl || textMatchesCurrentSearch || text.isEmpty()) &&
settings.shouldShowSearchShortcuts
)
)
store.dispatch(
SearchFragmentAction.AllowSearchSuggestionsInPrivateModePrompt(
text.isNotEmpty() &&
activity.browsingModeManager.mode.isPrivate &&
!settings.shouldShowSearchSuggestionsInPrivate &&
!settings.showSearchSuggestionsInPrivateOnboardingFinished
)
)
}
override fun handleUrlTapped(url: String) {
clearToolbarFocus()
activity.openToBrowserAndLoad(
searchTermOrURL = url,
newTab = store.state.tabId == null,
from = BrowserDirection.FromSearchDialog
)
metrics.track(Event.EnteredUrl(false))
}
override fun handleSearchTermsTapped(searchTerms: String) {
settings.incrementActiveSearchCount()
clearToolbarFocus()
activity.openToBrowserAndLoad(
searchTermOrURL = searchTerms,
newTab = store.state.tabId == null,
from = BrowserDirection.FromSearchDialog,
engine = store.state.searchEngineSource.searchEngine,
forceSearch = true
)
val searchAccessPoint = when (store.state.searchAccessPoint) {
Event.PerformedSearch.SearchAccessPoint.NONE -> Event.PerformedSearch.SearchAccessPoint.SUGGESTION
else -> store.state.searchAccessPoint
}
val event = searchAccessPoint?.let { sap ->
MetricsUtils.createSearchEvent(
store.state.searchEngineSource.searchEngine,
activity,
sap
)
}
event?.let { metrics.track(it) }
}
override fun handleSearchShortcutEngineSelected(searchEngine: SearchEngine) {
store.dispatch(SearchFragmentAction.SearchShortcutEngineSelected(searchEngine))
val isCustom =
CustomSearchEngineStore.isCustomSearchEngine(activity, searchEngine.identifier)
metrics.track(Event.SearchShortcutSelected(searchEngine, isCustom))
}
override fun handleSearchShortcutsButtonClicked() {
val isOpen = store.state.showSearchShortcuts
store.dispatch(SearchFragmentAction.ShowSearchShortcutEnginePicker(!isOpen))
}
override fun handleClickSearchEngineSettings() {
val directions = SearchDialogFragmentDirections.actionGlobalSearchEngineFragment()
navController.navigateSafe(R.id.searchDialogFragment, directions)
}
override fun handleExistingSessionSelected(session: Session) {
clearToolbarFocus()
sessionManager.select(session)
activity.openToBrowser(
from = BrowserDirection.FromSearchDialog
)
}
override fun handleExistingSessionSelected(tabId: String) {
val session = sessionManager.findSessionById(tabId)
if (session != null) {
handleExistingSessionSelected(session)
}
}
}

View File

@ -9,88 +9,43 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AppCompatDialogFragment import androidx.appcompat.app.AppCompatDialogFragment
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import kotlinx.android.synthetic.main.fragment_search.view.* import kotlinx.android.synthetic.main.fragment_search.view.*
import mozilla.components.browser.search.SearchEngine import kotlinx.android.synthetic.main.fragment_search_dialog.*
import mozilla.components.browser.session.Session import kotlinx.coroutines.ExperimentalCoroutinesApi
import mozilla.components.browser.state.selector.findTab
import mozilla.components.lib.state.ext.consumeFrom
import mozilla.components.support.ktx.android.view.hideKeyboard
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.ext.logDebug import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.search.SearchEngineSource import org.mozilla.fenix.search.SearchEngineSource
import org.mozilla.fenix.search.SearchFragmentState import org.mozilla.fenix.search.SearchFragmentState
import org.mozilla.fenix.search.awesomebar.AwesomeBarInteractor import org.mozilla.fenix.search.SearchFragmentStore
import org.mozilla.fenix.search.SearchInteractor
import org.mozilla.fenix.search.awesomebar.AwesomeBarView import org.mozilla.fenix.search.awesomebar.AwesomeBarView
import org.mozilla.fenix.search.toolbar.ToolbarInteractor
import org.mozilla.fenix.search.toolbar.ToolbarView import org.mozilla.fenix.search.toolbar.ToolbarView
import org.mozilla.fenix.utils.Settings
class TempSearchInteractor(val onTextChangedCallback: (String) -> Unit) : ToolbarInteractor, AwesomeBarInteractor { typealias SearchDialogFragmentStore = SearchFragmentStore
override fun onUrlCommitted(url: String) { typealias SearchDialogInteractor = SearchInteractor
logDebug("boek", "onUrlCommitted $url") fun Settings.shouldShowSearchSuggestions(isPrivate: Boolean): Boolean {
} return if (isPrivate) {
shouldShowSearchSuggestions && shouldShowSearchSuggestionsInPrivate
override fun onEditingCanceled() { } else {
logDebug("boek", "onEditingCanceled") shouldShowSearchSuggestions
}
override fun onTextChanged(text: String) {
onTextChangedCallback.invoke(text)
}
override fun onUrlTapped(url: String) {
logDebug("boek", "onEditingCanceled")
}
override fun onSearchTermsTapped(searchTerms: String) {
logDebug("boek", "onEditingCanceled")
}
override fun onSearchShortcutEngineSelected(searchEngine: SearchEngine) {
logDebug("boek", "onEditingCanceled")
}
override fun onClickSearchEngineSettings() {
logDebug("boek", "onEditingCanceled")
}
override fun onExistingSessionSelected(session: Session) {
logDebug("boek", "onEditingCanceled")
}
override fun onExistingSessionSelected(tabId: String) {
logDebug("boek", "onEditingCanceled")
}
override fun onSearchShortcutsButtonClicked() {
logDebug("boek", "onEditingCanceled")
} }
} }
class SearchDialogFragment : AppCompatDialogFragment() { class SearchDialogFragment : AppCompatDialogFragment() {
private lateinit var interactor: SearchDialogInteractor
private lateinit var store: SearchDialogFragmentStore
private lateinit var toolbarView: ToolbarView private lateinit var toolbarView: ToolbarView
private lateinit var awesomeBarView: AwesomeBarView private lateinit var awesomeBarView: AwesomeBarView
private val tempInteractor = TempSearchInteractor {
view?.awesomeBar?.visibility = if (it.isEmpty()) View.INVISIBLE else View.VISIBLE
awesomeBarView.update(
SearchFragmentState(
query = it,
url = "",
searchTerms = "",
searchEngineSource = SearchEngineSource.Default(requireComponents.search.provider.getDefaultEngine(requireContext())),
defaultEngineSource = SearchEngineSource.Default(requireComponents.search.provider.getDefaultEngine(requireContext())),
showSearchSuggestions = true,
showSearchSuggestionsHint = false,
showSearchShortcuts = false,
areShortcutsAvailable = false,
showClipboardSuggestions = true,
showHistorySuggestions = true,
showBookmarkSuggestions = true,
tabId = null,
pastedText = null,
searchAccessPoint = null
)
)
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -103,10 +58,26 @@ class SearchDialogFragment : AppCompatDialogFragment() {
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View? {
val view = inflater.inflate(R.layout.fragment_search_dialog, container, false) val view = inflater.inflate(R.layout.fragment_search_dialog, container, false)
store = SearchDialogFragmentStore(setUpState())
interactor = SearchDialogInteractor(
SearchDialogController(
activity = requireActivity() as HomeActivity,
sessionManager = requireComponents.core.sessionManager,
store = store,
navController = findNavController(),
settings = requireContext().settings(),
metrics = requireComponents.analytics.metrics,
clearToolbarFocus = {
toolbarView.view.hideKeyboard()
toolbarView.view.clearFocus()
}
)
)
toolbarView = ToolbarView( toolbarView = ToolbarView(
requireContext(), requireContext(),
tempInteractor, interactor,
null, null,
false, false,
view.toolbar, view.toolbar,
@ -115,10 +86,60 @@ class SearchDialogFragment : AppCompatDialogFragment() {
awesomeBarView = AwesomeBarView( awesomeBarView = AwesomeBarView(
requireContext(), requireContext(),
tempInteractor, interactor,
view.awesomeBar view.awesomeBar
) )
return view return view
} }
@ExperimentalCoroutinesApi
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
consumeFrom(store) {
awesomeBar?.visibility = if (it.query.isEmpty()) View.INVISIBLE else View.VISIBLE
toolbarView.update(it)
awesomeBarView.update(it)
}
}
private fun setUpState(): SearchFragmentState {
val activity = activity as HomeActivity
val settings = activity.settings()
val args by navArgs<SearchDialogFragmentArgs>()
val tabId = args.sessionId
val tab = tabId?.let { requireComponents.core.store.state.findTab(it) }
val url = tab?.content?.url.orEmpty()
val currentSearchEngine = SearchEngineSource.Default(
requireComponents.search.provider.getDefaultEngine(requireContext())
)
val isPrivate = activity.browsingModeManager.mode.isPrivate
val areShortcutsAvailable =
requireContext().components.search.provider.installedSearchEngines(requireContext())
.list.size >= MINIMUM_SEARCH_ENGINES_NUMBER_TO_SHOW_SHORTCUTS
return SearchFragmentState(
query = url,
url = url,
searchTerms = tab?.content?.searchTerms.orEmpty(),
searchEngineSource = currentSearchEngine,
defaultEngineSource = currentSearchEngine,
showSearchSuggestions = settings.shouldShowSearchSuggestions(isPrivate),
showSearchSuggestionsHint = false,
showSearchShortcuts = settings.shouldShowSearchShortcuts &&
url.isEmpty() &&
areShortcutsAvailable,
areShortcutsAvailable = areShortcutsAvailable,
showClipboardSuggestions = settings.shouldShowClipboardSuggestions,
showHistorySuggestions = settings.shouldShowHistorySuggestions,
showBookmarkSuggestions = settings.shouldShowBookmarkSuggestions,
tabId = tabId,
pastedText = args.pastedText,
searchAccessPoint = args.searchAccessPoint
)
}
companion object {
private const val MINIMUM_SEARCH_ENGINES_NUMBER_TO_SHOW_SHORTCUTS = 2
}
} }

View File

@ -83,6 +83,8 @@ class FenixTabsAdapter(
} }
true true
} }
} else {
holder.itemView.setOnLongClickListener(null)
} }
holder.itemView.setOnClickListener { holder.itemView.setOnClickListener {

View File

@ -26,7 +26,7 @@
android:layout_marginStart="24dp" android:layout_marginStart="24dp"
android:layout_marginEnd="24dp" android:layout_marginEnd="24dp"
android:backgroundTint="?neutral" android:backgroundTint="?neutral"
android:inputType="text" android:inputType="textCapSentences"
android:singleLine="true" android:singleLine="true"
android:textAlignment="viewStart" /> android:textAlignment="viewStart" />
</LinearLayout> </LinearLayout>

Some files were not shown because too many files have changed in this diff Show More