Copione merged onto master
51
README.md
|
@ -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.
|
||||||
|
|
145
app/build.gradle
|
@ -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®exp=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®exp=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)
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 59 KiB |
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 59 KiB |
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.1 KiB |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 8.7 KiB |
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 8.2 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 6.6 KiB |
Before Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 5.6 KiB |
Before Width: | Height: | Size: 9.1 KiB |
Before Width: | Height: | Size: 9.4 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 22 KiB |
|
@ -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>
|
|
Before Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 6.4 KiB |
Before Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 5.9 KiB |
Before Width: | Height: | Size: 9.5 KiB |
Before Width: | Height: | Size: 9.8 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 21 KiB |
|
@ -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>
|
|
|
@ -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>
|
|
|
@ -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>
|
|
|
@ -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>
|
|
|
@ -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>
|
|
|
@ -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>
|
|
Before Width: | Height: | Size: 6.2 KiB |
Before Width: | Height: | Size: 8.6 KiB |
Before Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 4.8 KiB |
Before Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 9.3 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 9.2 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 22 KiB |
|
@ -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>
|
|
|
@ -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>
|
|
|
@ -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>
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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),
|
||||||
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
|
@ -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 ->
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,6 +83,8 @@ class FenixTabsAdapter(
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
holder.itemView.setOnLongClickListener(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
holder.itemView.setOnClickListener {
|
holder.itemView.setOnClickListener {
|
||||||
|
|
|
@ -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>
|
||||||
|
|