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:
|
||||
|
||||
```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
|
||||
variable pointing to the right path.
|
||||
|
||||
3. Make sure to select the correct build variant in Android Studio. See the next section.
|
||||
|
||||
### Guide to 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
|
||||
which app id and settings to use. Here is a description of what each means:
|
||||
### Build Variants
|
||||
For general development, we recommend the **debug** build variant. Here's an explanation of each variant:
|
||||
|
||||
- **geckoBeta** (recommended) uses the Beta variant of the Gecko rendering engine, which corresponds to the next version of Gecko which will go to production
|
||||
- **geckoNightly** uses the Nightly variant of the Gecko rendering engine, which is the version which will arrive after beta and is less stable
|
||||
- **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.
|
||||
- **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 />
|
||||
<br />
|
||||
|
||||
- **debug** uses debug symbols and debug signing, adds tools like LeakCanary for troubleshooting, and does not strip unused or wasteful code
|
||||
- **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.
|
||||
nightly, beta, and release are unsigned and `debuggable=false` by default. If
|
||||
you want these variants to be:
|
||||
- automatically signed, see [Automatically signing release builds](#automatically-sign-release-builds)
|
||||
- `debuggable=true`, see [Building debuggable release variants](#building-debuggable-release-variants)
|
||||
|
||||
#### Performance Build Variants
|
||||
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):
|
||||
- Recommendation: use a `forPerformanceTest` variant 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.
|
||||
- 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: 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:
|
||||
- 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).
|
||||
- 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:
|
||||
- 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.
|
||||
|
||||
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
|
||||
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.
|
||||
|
||||
### 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
|
||||
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.
|
||||
|
|
145
app/build.gradle
|
@ -6,7 +6,6 @@ apply plugin: 'com.android.application'
|
|||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
apply plugin: 'jacoco'
|
||||
apply from: "$project.rootDir/automation/gradle/versionCode.gradle"
|
||||
apply plugin: 'androidx.navigation.safeargs.kotlin'
|
||||
apply plugin: 'com.google.android.gms.oss-licenses-plugin'
|
||||
|
||||
|
@ -48,6 +47,10 @@ android {
|
|||
if (gradle.hasProperty("localProperties.autosignReleaseWithDebugKey")) {
|
||||
signingConfig signingConfigs.debug
|
||||
}
|
||||
|
||||
if (gradle.hasProperty("localProperties.debuggable")) {
|
||||
debuggable true
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
|
@ -58,25 +61,31 @@ android {
|
|||
resValue "bool", "IS_DEBUG", "true"
|
||||
pseudoLocalesEnabled true
|
||||
}
|
||||
forPerformanceTest releaseTemplate >> { // the ">>" concatenates the raptor-specific options with the template
|
||||
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 >> {
|
||||
nightly releaseTemplate >> {
|
||||
applicationIdSuffix ".fenix"
|
||||
buildConfigField "boolean", "USE_RELEASE_VERSIONING", "true"
|
||||
def deepLinkSchemeValue = "fenix-nightly"
|
||||
buildConfigField "String", "DEEP_LINK_SCHEME", "\"$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"
|
||||
applicationIdSuffix ".firefox"
|
||||
def deepLinkSchemeValue = "fenix"
|
||||
|
@ -93,49 +102,6 @@ android {
|
|||
"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 {
|
||||
|
@ -162,30 +128,20 @@ android {
|
|||
androidTest {
|
||||
resources.srcDirs += ['src/androidTest/resources']
|
||||
}
|
||||
fennecBeta {
|
||||
java.srcDirs = ['src/migration/java']
|
||||
debug {
|
||||
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"
|
||||
}
|
||||
fennecProduction {
|
||||
java.srcDirs = ['src/migration/java']
|
||||
release {
|
||||
java.srcDirs = ['src/migration/java', 'src/geckoRelease/java']
|
||||
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 {
|
||||
|
@ -216,8 +172,6 @@ android {
|
|||
}
|
||||
}
|
||||
|
||||
def baseVersionCode = generatedVersionCode
|
||||
|
||||
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
|
||||
// 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 ->
|
||||
def abi = output.getFilter(OutputFile.ABI)
|
||||
|
||||
def versionCodeOverride
|
||||
if (variant.name.contains("Fennec")) {
|
||||
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")
|
||||
}
|
||||
// We use the same version code generator, that we inherited from Fennec, across all channels - even on
|
||||
// channels that never shipped a Fennec build.
|
||||
def versionCodeOverride = Config.generateFennecVersionCode(abi)
|
||||
|
||||
println("versionCode for $abi = $versionCodeOverride")
|
||||
|
||||
|
@ -377,8 +315,11 @@ ext.gleanDocsDirectory = "$rootDir/docs"
|
|||
apply plugin: "org.mozilla.telemetry.glean-gradle-plugin"
|
||||
|
||||
dependencies {
|
||||
geckoNightlyImplementation Deps.mozilla_browser_engine_gecko_nightly
|
||||
geckoBetaImplementation Deps.mozilla_browser_engine_gecko_beta
|
||||
debugImplementation Deps.mozilla_browser_engine_gecko_nightly
|
||||
|
||||
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_coroutines
|
||||
|
@ -604,17 +545,15 @@ task printVariants {
|
|||
fileName: it.outputFileName,
|
||||
]},
|
||||
build_type: it.buildType.name,
|
||||
engine: it.productFlavors.find { it.dimension == 'engine' }.name,
|
||||
name: it.name,
|
||||
]}
|
||||
// AndroidTest is a special case not included above
|
||||
variants.add([
|
||||
apks: [[
|
||||
abi: 'noarch',
|
||||
fileName: 'app-geckoNightly-debug-androidTest.apk',
|
||||
fileName: 'app-debug-androidTest.apk',
|
||||
]],
|
||||
build_type: 'androidTest',
|
||||
engine: 'geckoNightly',
|
||||
name: 'androidTest',
|
||||
])
|
||||
println 'variants: ' + groovy.json.JsonOutput.toJson(variants)
|
||||
|
|
|
@ -10,7 +10,6 @@ import okhttp3.mockwebserver.MockWebServer
|
|||
import org.junit.Rule
|
||||
import org.junit.Before
|
||||
import org.junit.After
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
|
||||
import org.mozilla.fenix.helpers.HomeActivityTestRule
|
||||
|
@ -44,7 +43,6 @@ class SettingsAddonsTest {
|
|||
}
|
||||
|
||||
// Walks through settings add-ons menu to ensure all items are present
|
||||
@Ignore("Failing, see: https://github.com/mozilla-mobile/fenix/issues/13218")
|
||||
@Test
|
||||
fun settingsAddonsItemsTest() {
|
||||
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
|
||||
@Test
|
||||
fun verifyAddonsCanBeUninstalled() {
|
||||
|
|
|
@ -2,8 +2,9 @@ package org.mozilla.fenix.ui.robots
|
|||
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed
|
||||
import androidx.test.espresso.matcher.ViewMatchers
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||
import org.hamcrest.CoreMatchers.allOf
|
||||
import org.mozilla.fenix.R
|
||||
|
@ -35,11 +36,11 @@ class SettingsSubMenuAddonsManagerAddonDetailedMenuRobot {
|
|||
}
|
||||
|
||||
private fun assertAddonMenuItems() {
|
||||
enableSwitchButton().check(matches(isCompletelyDisplayed()))
|
||||
settingsButton().check(matches(isCompletelyDisplayed()))
|
||||
detailsButton().check(matches(isCompletelyDisplayed()))
|
||||
permissionsButton().check(matches(isCompletelyDisplayed()))
|
||||
removeAddonButton().check(matches(isCompletelyDisplayed()))
|
||||
enableSwitchButton().check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
|
||||
settingsButton().check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
|
||||
detailsButton().check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
|
||||
permissionsButton().check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
|
||||
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.uiautomator.By
|
||||
import androidx.test.uiautomator.UiDevice
|
||||
import androidx.test.uiautomator.UiSelector
|
||||
import androidx.test.uiautomator.Until
|
||||
import org.hamcrest.Matcher
|
||||
import org.hamcrest.Matchers.allOf
|
||||
|
@ -350,10 +351,9 @@ class ThreeDotMenuMainRobot {
|
|||
|
||||
fun openAddonsManagerMenu(interact: SettingsSubMenuAddonsManagerRobot.() -> Unit): SettingsSubMenuAddonsManagerRobot.Transition {
|
||||
clickAddonsManagerButton()
|
||||
mDevice.waitNotNull(
|
||||
Until.findObject(By.text("Recommended")),
|
||||
waitingTime
|
||||
)
|
||||
mDevice.findObject(
|
||||
UiSelector().text("Recommended")
|
||||
).waitForExists(waitingTime)
|
||||
|
||||
SettingsSubMenuAddonsManagerRobot().interact()
|
||||
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),
|
||||
FromHome(R.id.homeFragment),
|
||||
FromSearch(R.id.searchFragment),
|
||||
FromSearchDialog(R.id.searchDialogFragment),
|
||||
FromSettings(R.id.settingsFragment),
|
||||
FromSyncedTabs(R.id.syncedTabsFragment),
|
||||
FromBookmarks(R.id.bookmarkFragment),
|
||||
|
|
|
@ -5,14 +5,14 @@
|
|||
package org.mozilla.fenix
|
||||
|
||||
enum class ReleaseChannel {
|
||||
FenixDebug,
|
||||
FenixProduction,
|
||||
FennecProduction,
|
||||
FennecBeta;
|
||||
Debug,
|
||||
Nightly,
|
||||
Beta,
|
||||
Release;
|
||||
|
||||
val isReleased: Boolean
|
||||
get() = when (this) {
|
||||
FenixDebug -> false
|
||||
Debug -> false
|
||||
else -> true
|
||||
}
|
||||
|
||||
|
@ -27,51 +27,39 @@ enum class ReleaseChannel {
|
|||
get() = !this.isReleased
|
||||
|
||||
val isReleaseOrBeta: Boolean
|
||||
get() = when (this) {
|
||||
FennecProduction -> true
|
||||
FennecBeta -> true
|
||||
else -> false
|
||||
}
|
||||
get() = this == Release || this == Beta
|
||||
|
||||
val isRelease: Boolean
|
||||
get() = when (this) {
|
||||
FennecProduction -> true
|
||||
Release -> true
|
||||
else -> false
|
||||
}
|
||||
|
||||
val isBeta: Boolean
|
||||
get() = when (this) {
|
||||
FennecBeta -> true
|
||||
else -> false
|
||||
}
|
||||
get() = this == Beta
|
||||
|
||||
val isNightlyOrDebug: Boolean
|
||||
get() = when (this) {
|
||||
FenixDebug -> true
|
||||
FenixProduction -> true
|
||||
else -> false
|
||||
}
|
||||
get() = this == Debug || this == Nightly
|
||||
|
||||
/**
|
||||
* Is this a build for a release channel that we used to ship Fennec on?
|
||||
*/
|
||||
val isFennec: Boolean
|
||||
get() = this in fennecChannels
|
||||
|
||||
/**
|
||||
* Is this build for a "pure" Fenix channel that we never shipped Fennec on?
|
||||
*/
|
||||
val isFenix: Boolean
|
||||
get() = !isFennec
|
||||
}
|
||||
|
||||
object Config {
|
||||
val channel = when (BuildConfig.BUILD_TYPE) {
|
||||
"fenixProduction" -> ReleaseChannel.FenixProduction
|
||||
"debug" -> ReleaseChannel.FenixDebug
|
||||
"fennecProduction" -> ReleaseChannel.FennecProduction
|
||||
"fennecBeta" -> ReleaseChannel.FennecBeta
|
||||
|
||||
// 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
|
||||
|
||||
"debug" -> ReleaseChannel.Debug
|
||||
"nightly" -> ReleaseChannel.Nightly
|
||||
"beta" -> ReleaseChannel.Beta
|
||||
"release" -> ReleaseChannel.Release
|
||||
else -> {
|
||||
throw IllegalStateException("Unknown build type: ${BuildConfig.BUILD_TYPE}")
|
||||
}
|
||||
|
@ -79,6 +67,6 @@ object Config {
|
|||
}
|
||||
|
||||
private val fennecChannels: List<ReleaseChannel> = listOf(
|
||||
ReleaseChannel.FennecBeta,
|
||||
ReleaseChannel.FennecProduction
|
||||
ReleaseChannel.Beta,
|
||||
ReleaseChannel.Release
|
||||
)
|
||||
|
|
|
@ -86,6 +86,7 @@ import org.mozilla.fenix.library.history.HistoryFragmentDirections
|
|||
import org.mozilla.fenix.perf.Performance
|
||||
import org.mozilla.fenix.perf.StartupTimeline
|
||||
import org.mozilla.fenix.search.SearchFragmentDirections
|
||||
import org.mozilla.fenix.searchdialog.SearchDialogFragmentDirections
|
||||
import org.mozilla.fenix.session.NotificationSessionObserver
|
||||
import org.mozilla.fenix.settings.SettingsFragmentDirections
|
||||
import org.mozilla.fenix.settings.TrackingProtectionFragmentDirections
|
||||
|
@ -553,9 +554,11 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
|
|||
BrowserDirection.FromGlobal ->
|
||||
NavGraphDirections.actionGlobalBrowser(customTabSessionId)
|
||||
BrowserDirection.FromHome ->
|
||||
HomeFragmentDirections.actionHomeFragmentToBrowserFragment(customTabSessionId, true)
|
||||
HomeFragmentDirections.actionGlobalBrowser(customTabSessionId)
|
||||
BrowserDirection.FromSearch ->
|
||||
SearchFragmentDirections.actionGlobalBrowser(customTabSessionId)
|
||||
BrowserDirection.FromSearchDialog ->
|
||||
SearchDialogFragmentDirections.actionGlobalBrowser(customTabSessionId)
|
||||
BrowserDirection.FromSettings ->
|
||||
SettingsFragmentDirections.actionGlobalBrowser(customTabSessionId)
|
||||
BrowserDirection.FromSyncedTabs ->
|
||||
|
|
|
@ -10,6 +10,7 @@ import android.os.Bundle
|
|||
import android.os.StrictMode
|
||||
import androidx.annotation.VisibleForTesting
|
||||
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.getType
|
||||
import org.mozilla.fenix.components.metrics.Event
|
||||
|
@ -43,7 +44,15 @@ class IntentReceiverActivity : Activity() {
|
|||
|
||||
fun processIntent(intent: Intent) {
|
||||
// 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)
|
||||
|
||||
launch(intent, intentProcessorType)
|
||||
|
@ -65,17 +74,14 @@ class IntentReceiverActivity : Activity() {
|
|||
finish() // must finish() after starting the other activity
|
||||
}
|
||||
|
||||
private fun getIntentProcessors(): List<IntentProcessor> {
|
||||
val modeDependentProcessors = if (settings().openLinksInAPrivateTab) {
|
||||
components.analytics.metrics.track(Event.OpenedLink(Event.OpenedLink.Mode.PRIVATE))
|
||||
intent.putExtra(HomeActivity.PRIVATE_BROWSING_MODE, true)
|
||||
private fun getIntentProcessors(private: Boolean): List<IntentProcessor> {
|
||||
val modeDependentProcessors = if (private) {
|
||||
listOf(
|
||||
components.intentProcessors.privateCustomTabIntentProcessor,
|
||||
components.intentProcessors.privateIntentProcessor
|
||||
)
|
||||
} else {
|
||||
components.analytics.metrics.track(Event.OpenedLink(Event.OpenedLink.Mode.NORMAL))
|
||||
intent.putExtra(HomeActivity.PRIVATE_BROWSING_MODE, false)
|
||||
listOf(
|
||||
components.intentProcessors.customTabIntentProcessor,
|
||||
components.intentProcessors.intentProcessor
|
||||
|
|
|
@ -198,8 +198,7 @@ class InstalledAddonDetailsFragment : Fragment() {
|
|||
components.useCases.tabsUseCases.addTab(settingUrl)
|
||||
}
|
||||
|
||||
InstalledAddonDetailsFragmentDirections
|
||||
.actionGlobalBrowser(null, false)
|
||||
InstalledAddonDetailsFragmentDirections.actionGlobalBrowser(null)
|
||||
} else {
|
||||
InstalledAddonDetailsFragmentDirections
|
||||
.actionInstalledAddonFragmentToAddonInternalSettingsFragment(addon)
|
||||
|
|
|
@ -204,7 +204,6 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session
|
|||
engineView = WeakReference(engineView),
|
||||
swipeRefresh = WeakReference(swipeRefresh),
|
||||
viewLifecycleScope = WeakReference(viewLifecycleOwner.lifecycleScope),
|
||||
arguments = requireArguments(),
|
||||
firstContentfulHappened = ::didFirstContentfulHappen
|
||||
).apply {
|
||||
beginAnimateInIfNecessary()
|
||||
|
@ -586,8 +585,15 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session
|
|||
}
|
||||
.ifChanged { it.content.firstContentfulPaint }
|
||||
.collect {
|
||||
engineView?.asView()?.isVisible =
|
||||
val showEngineView =
|
||||
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
|
||||
|
||||
import android.animation.ValueAnimator
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.animation.DecelerateInterpolator
|
||||
import androidx.core.animation.doOnEnd
|
||||
import androidx.core.graphics.drawable.toDrawable
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.LifecycleCoroutineScope
|
||||
import androidx.navigation.NavOptions
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import mozilla.components.concept.engine.EngineView
|
||||
|
@ -34,7 +29,6 @@ class BrowserAnimator(
|
|||
private val engineView: WeakReference<EngineView>,
|
||||
private val swipeRefresh: WeakReference<View>,
|
||||
private val viewLifecycleScope: WeakReference<LifecycleCoroutineScope>,
|
||||
private val arguments: Bundle,
|
||||
private val firstContentfulHappened: () -> Boolean
|
||||
) {
|
||||
|
||||
|
@ -44,46 +38,19 @@ class BrowserAnimator(
|
|||
private val unwrappedSwipeRefresh: View?
|
||||
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() {
|
||||
val shouldAnimate = arguments.getBoolean(SHOULD_ANIMATE_FLAG, false)
|
||||
if (shouldAnimate) {
|
||||
viewLifecycleScope.get()?.launch(Dispatchers.Main) {
|
||||
delay(ANIMATION_DELAY)
|
||||
captureEngineViewAndDrawStatically {
|
||||
unwrappedSwipeRefresh?.alpha = 0f
|
||||
browserZoomInValueAnimator.start()
|
||||
if (unwrappedSwipeRefresh?.context?.settings()?.waitToShowPageUntilFirstPaint == true) {
|
||||
if (firstContentfulHappened()) {
|
||||
viewLifecycleScope.get()?.launch {
|
||||
delay(100)
|
||||
unwrappedEngineView?.asView()?.visibility = View.VISIBLE
|
||||
unwrappedSwipeRefresh?.background = null
|
||||
unwrappedSwipeRefresh?.alpha = 1f
|
||||
}
|
||||
}
|
||||
} else {
|
||||
unwrappedSwipeRefresh?.alpha = 1f
|
||||
if (firstContentfulHappened()) {
|
||||
unwrappedEngineView?.asView()?.visibility = View.VISIBLE
|
||||
}
|
||||
unwrappedSwipeRefresh?.background = null
|
||||
}
|
||||
}
|
||||
|
@ -124,13 +91,6 @@ class BrowserAnimator(
|
|||
}
|
||||
|
||||
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 {
|
||||
val navOptions = NavOptions.Builder()
|
||||
|
||||
|
|
|
@ -102,9 +102,9 @@ fun isSentryEnabled() = !BuildConfig.SENTRY_TOKEN.isNullOrEmpty()
|
|||
private fun getSentryProjectUrl(): String? {
|
||||
val baseUrl = "https://sentry.prod.mozaws.net/operations"
|
||||
return when (Config.channel) {
|
||||
ReleaseChannel.FenixProduction -> "$baseUrl/fenix"
|
||||
ReleaseChannel.FennecProduction -> "$baseUrl/fenix-fennec"
|
||||
ReleaseChannel.FennecBeta -> "$baseUrl/fenix-fennec-beta"
|
||||
ReleaseChannel.Nightly -> "$baseUrl/fenix"
|
||||
ReleaseChannel.Release -> "$baseUrl/fenix-fennec"
|
||||
ReleaseChannel.Beta -> "$baseUrl/fenix-fennec-beta"
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
|
|
@ -81,6 +81,9 @@ class DefaultBrowserToolbarController(
|
|||
private val onCloseTab: (Session) -> Unit
|
||||
) : BrowserToolbarController {
|
||||
|
||||
private val useNewSearchExperience
|
||||
get() = activity.settings().useNewSearchExperience
|
||||
|
||||
private val currentSession
|
||||
get() = customTabSession ?: activity.components.core.sessionManager.selectedSession
|
||||
|
||||
|
@ -91,11 +94,25 @@ class DefaultBrowserToolbarController(
|
|||
internal var ioScope: CoroutineScope = CoroutineScope(Dispatchers.IO)
|
||||
|
||||
override fun handleToolbarPaste(text: String) {
|
||||
val directions = BrowserFragmentDirections.actionBrowserFragmentToSearchFragment(
|
||||
if (useNewSearchExperience) {
|
||||
navController.nav(
|
||||
R.id.browserFragment, BrowserFragmentDirections.actionGlobalSearchDialog(
|
||||
sessionId = currentSession?.id,
|
||||
pastedText = text
|
||||
), getToolbarNavOptions(activity)
|
||||
)
|
||||
navController.nav(R.id.browserFragment, directions, getToolbarNavOptions(activity))
|
||||
} else {
|
||||
browserAnimator.captureEngineViewAndDrawStatically {
|
||||
navController.nav(
|
||||
R.id.browserFragment,
|
||||
BrowserFragmentDirections.actionBrowserFragmentToSearchFragment(
|
||||
sessionId = currentSession?.id,
|
||||
pastedText = text
|
||||
),
|
||||
getToolbarNavOptions(activity)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun handleToolbarPasteAndGo(text: String) {
|
||||
|
@ -117,11 +134,23 @@ class DefaultBrowserToolbarController(
|
|||
Event.SearchBarTapped(Event.SearchBarTapped.Source.BROWSER)
|
||||
)
|
||||
|
||||
val directions = BrowserFragmentDirections.actionBrowserFragmentToSearchFragment(
|
||||
if (useNewSearchExperience) {
|
||||
navController.nav(
|
||||
R.id.browserFragment, BrowserFragmentDirections.actionGlobalSearchDialog(
|
||||
currentSession?.id
|
||||
), getToolbarNavOptions(activity)
|
||||
)
|
||||
|
||||
navController.nav(R.id.browserFragment, directions, getToolbarNavOptions(activity))
|
||||
} else {
|
||||
browserAnimator.captureEngineViewAndDrawStatically {
|
||||
navController.nav(
|
||||
R.id.browserFragment,
|
||||
BrowserFragmentDirections.actionBrowserFragmentToSearchFragment(
|
||||
currentSession?.id
|
||||
),
|
||||
getToolbarNavOptions(activity)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun handleTabCounterClick() {
|
||||
|
@ -146,7 +175,11 @@ class DefaultBrowserToolbarController(
|
|||
if (sessionManager.sessionsOfType(it.private).count() == 1) {
|
||||
// The tab tray always returns to normal mode so do that here too
|
||||
activity.browsingModeManager.mode = BrowsingMode.Normal
|
||||
navController.navigate(BrowserFragmentDirections.actionGlobalHome(sessionToDelete = it.id))
|
||||
navController.navigate(
|
||||
BrowserFragmentDirections.actionGlobalHome(
|
||||
sessionToDelete = it.id
|
||||
)
|
||||
)
|
||||
} else {
|
||||
onCloseTab.invoke(it)
|
||||
activity.components.useCases.tabsUseCases.removeTab.invoke(it)
|
||||
|
@ -195,7 +228,7 @@ class DefaultBrowserToolbarController(
|
|||
val directions = BrowserFragmentDirections.actionBrowserFragmentToSettingsFragment()
|
||||
navController.nav(R.id.browserFragment, directions)
|
||||
}
|
||||
ToolbarMenu.Item.SyncedTabs -> {
|
||||
ToolbarMenu.Item.SyncedTabs -> browserAnimator.captureEngineViewAndDrawStatically {
|
||||
navController.nav(
|
||||
R.id.browserFragment,
|
||||
BrowserFragmentDirections.actionBrowserFragmentToSyncedTabsFragment()
|
||||
|
@ -256,7 +289,7 @@ class DefaultBrowserToolbarController(
|
|||
activity.components.analytics.metrics.track(Event.FindInPageOpened)
|
||||
}
|
||||
|
||||
ToolbarMenu.Item.AddonsManager -> {
|
||||
ToolbarMenu.Item.AddonsManager -> browserAnimator.captureEngineViewAndDrawStatically {
|
||||
navController.nav(
|
||||
R.id.browserFragment,
|
||||
BrowserFragmentDirections.actionGlobalAddonsManagementFragment()
|
||||
|
@ -332,13 +365,13 @@ class DefaultBrowserToolbarController(
|
|||
bookmarkTapped(it)
|
||||
}
|
||||
}
|
||||
ToolbarMenu.Item.Bookmarks -> {
|
||||
ToolbarMenu.Item.Bookmarks -> browserAnimator.captureEngineViewAndDrawStatically {
|
||||
navController.nav(
|
||||
R.id.browserFragment,
|
||||
BrowserFragmentDirections.actionGlobalBookmarkFragment(BookmarkRoot.Mobile.id)
|
||||
)
|
||||
}
|
||||
ToolbarMenu.Item.History -> {
|
||||
ToolbarMenu.Item.History -> browserAnimator.captureEngineViewAndDrawStatically {
|
||||
navController.nav(
|
||||
R.id.browserFragment,
|
||||
BrowserFragmentDirections.actionGlobalHistoryFragment()
|
||||
|
|
|
@ -478,9 +478,7 @@ class HomeFragment : Fragment() {
|
|||
engineSessionState = state
|
||||
)
|
||||
findNavController().navigate(
|
||||
HomeFragmentDirections.actionHomeFragmentToBrowserFragment(
|
||||
null
|
||||
)
|
||||
HomeFragmentDirections.actionGlobalBrowser(null)
|
||||
)
|
||||
},
|
||||
operation = { },
|
||||
|
@ -674,7 +672,9 @@ class HomeFragment : Fragment() {
|
|||
|
||||
private fun navigateToSearch() {
|
||||
val directions = if (requireContext().settings().useNewSearchExperience) {
|
||||
HomeFragmentDirections.actionGlobalSearchDialog()
|
||||
HomeFragmentDirections.actionGlobalSearchDialog(
|
||||
sessionId = null
|
||||
)
|
||||
} else {
|
||||
HomeFragmentDirections.actionGlobalSearch(
|
||||
sessionId = null
|
||||
|
|
|
@ -91,7 +91,7 @@ class BookmarkAdapter(val emptyView: View, val interactor: BookmarkViewInteracto
|
|||
override fun getItemCount(): Int = tree.size
|
||||
|
||||
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) {
|
||||
val oldMode = mode
|
||||
tree = state.tree
|
||||
if (state.mode != mode) {
|
||||
mode = state.mode
|
||||
|
@ -148,6 +149,10 @@ class BookmarkView(
|
|||
}
|
||||
|
||||
bookmarkAdapter.updateData(state.tree, mode)
|
||||
if (state.mode != oldMode) {
|
||||
bookmarkAdapter.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
when (mode) {
|
||||
is BookmarkFragmentState.Mode.Normal -> {
|
||||
setUiForNormalMode(state.tree)
|
||||
|
|
|
@ -13,6 +13,7 @@ import org.mozilla.fenix.ext.hideAndDisable
|
|||
import org.mozilla.fenix.ext.showAndEnable
|
||||
import org.mozilla.fenix.library.LibrarySiteItemView
|
||||
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.inRoots
|
||||
|
||||
|
@ -27,7 +28,10 @@ class BookmarkFolderViewHolder(
|
|||
|
||||
override var item: BookmarkNode? = null
|
||||
|
||||
override fun bind(item: BookmarkNode) {
|
||||
override fun bind(
|
||||
item: BookmarkNode,
|
||||
mode: BookmarkFragmentState.Mode
|
||||
) {
|
||||
this.item = item
|
||||
|
||||
containerView.displayAs(LibrarySiteItemView.ItemType.FOLDER)
|
||||
|
@ -36,10 +40,10 @@ class BookmarkFolderViewHolder(
|
|||
|
||||
if (!item.inRoots()) {
|
||||
setupMenu(item)
|
||||
if (selectionHolder.selectedItems.isEmpty()) {
|
||||
containerView.overflowView.showAndEnable()
|
||||
} else {
|
||||
if (mode is BookmarkFragmentState.Mode.Selecting) {
|
||||
containerView.overflowView.hideAndDisable()
|
||||
} else {
|
||||
containerView.overflowView.showAndEnable()
|
||||
}
|
||||
} else {
|
||||
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.library.LibrarySiteItemView
|
||||
import org.mozilla.fenix.library.SelectionHolder
|
||||
import org.mozilla.fenix.library.bookmarks.BookmarkFragmentState
|
||||
import org.mozilla.fenix.library.bookmarks.BookmarkViewInteractor
|
||||
|
||||
/**
|
||||
|
@ -22,15 +23,18 @@ class BookmarkItemViewHolder(
|
|||
|
||||
override var item: BookmarkNode? = null
|
||||
|
||||
override fun bind(item: BookmarkNode) {
|
||||
override fun bind(
|
||||
item: BookmarkNode,
|
||||
mode: BookmarkFragmentState.Mode
|
||||
) {
|
||||
this.item = item
|
||||
|
||||
containerView.displayAs(LibrarySiteItemView.ItemType.SITE)
|
||||
|
||||
if (selectionHolder.selectedItems.isEmpty()) {
|
||||
containerView.overflowView.showAndEnable()
|
||||
} else {
|
||||
if (mode is BookmarkFragmentState.Mode.Selecting) {
|
||||
containerView.overflowView.hideAndDisable()
|
||||
} else {
|
||||
containerView.overflowView.showAndEnable()
|
||||
}
|
||||
setupMenu(item)
|
||||
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 org.mozilla.fenix.library.LibrarySiteItemView
|
||||
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.BookmarkViewInteractor
|
||||
|
||||
|
@ -22,7 +23,10 @@ abstract class BookmarkNodeViewHolder(
|
|||
|
||||
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>) {
|
||||
containerView.setSelectionInteractor(item, selectionHolder, interactor)
|
||||
|
|
|
@ -6,6 +6,7 @@ package org.mozilla.fenix.library.bookmarks.viewholders
|
|||
|
||||
import mozilla.components.concept.storage.BookmarkNode
|
||||
import org.mozilla.fenix.library.LibrarySiteItemView
|
||||
import org.mozilla.fenix.library.bookmarks.BookmarkFragmentState
|
||||
import org.mozilla.fenix.library.bookmarks.BookmarkViewInteractor
|
||||
|
||||
/**
|
||||
|
@ -18,7 +19,10 @@ class BookmarkSeparatorViewHolder(
|
|||
|
||||
override var item: BookmarkNode? = null
|
||||
|
||||
override fun bind(item: BookmarkNode) {
|
||||
override fun bind(
|
||||
item: BookmarkNode,
|
||||
mode: BookmarkFragmentState.Mode
|
||||
) {
|
||||
this.item = item
|
||||
containerView.displayAs(LibrarySiteItemView.ItemType.SEPARATOR)
|
||||
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.ViewGroup
|
||||
import androidx.appcompat.app.AppCompatDialogFragment
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import kotlinx.android.synthetic.main.fragment_search.view.*
|
||||
import mozilla.components.browser.search.SearchEngine
|
||||
import mozilla.components.browser.session.Session
|
||||
import kotlinx.android.synthetic.main.fragment_search_dialog.*
|
||||
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.ext.logDebug
|
||||
import org.mozilla.fenix.ext.components
|
||||
import org.mozilla.fenix.ext.requireComponents
|
||||
import org.mozilla.fenix.ext.settings
|
||||
import org.mozilla.fenix.search.SearchEngineSource
|
||||
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.toolbar.ToolbarInteractor
|
||||
import org.mozilla.fenix.search.toolbar.ToolbarView
|
||||
import org.mozilla.fenix.utils.Settings
|
||||
|
||||
class TempSearchInteractor(val onTextChangedCallback: (String) -> Unit) : ToolbarInteractor, AwesomeBarInteractor {
|
||||
override fun onUrlCommitted(url: String) {
|
||||
logDebug("boek", "onUrlCommitted $url")
|
||||
}
|
||||
|
||||
override fun onEditingCanceled() {
|
||||
logDebug("boek", "onEditingCanceled")
|
||||
}
|
||||
|
||||
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")
|
||||
typealias SearchDialogFragmentStore = SearchFragmentStore
|
||||
typealias SearchDialogInteractor = SearchInteractor
|
||||
fun Settings.shouldShowSearchSuggestions(isPrivate: Boolean): Boolean {
|
||||
return if (isPrivate) {
|
||||
shouldShowSearchSuggestions && shouldShowSearchSuggestionsInPrivate
|
||||
} else {
|
||||
shouldShowSearchSuggestions
|
||||
}
|
||||
}
|
||||
|
||||
class SearchDialogFragment : AppCompatDialogFragment() {
|
||||
|
||||
private lateinit var interactor: SearchDialogInteractor
|
||||
private lateinit var store: SearchDialogFragmentStore
|
||||
private lateinit var toolbarView: ToolbarView
|
||||
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?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
@ -103,10 +58,26 @@ class SearchDialogFragment : AppCompatDialogFragment() {
|
|||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
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(
|
||||
requireContext(),
|
||||
tempInteractor,
|
||||
interactor,
|
||||
null,
|
||||
false,
|
||||
view.toolbar,
|
||||
|
@ -115,10 +86,60 @@ class SearchDialogFragment : AppCompatDialogFragment() {
|
|||
|
||||
awesomeBarView = AwesomeBarView(
|
||||
requireContext(),
|
||||
tempInteractor,
|
||||
interactor,
|
||||
view.awesomeBar
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
} else {
|
||||
holder.itemView.setOnLongClickListener(null)
|
||||
}
|
||||
|
||||
holder.itemView.setOnClickListener {
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
android:layout_marginStart="24dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:backgroundTint="?neutral"
|
||||
android:inputType="text"
|
||||
android:inputType="textCapSentences"
|
||||
android:singleLine="true"
|
||||
android:textAlignment="viewStart" />
|
||||
</LinearLayout>
|
||||
|
|