Copione merged onto master
commit
543bc849ad
|
@ -330,7 +330,6 @@ dependencies {
|
|||
implementation Deps.androidx_coordinatorlayout
|
||||
|
||||
implementation Deps.sentry
|
||||
implementation Deps.osslicenses_library
|
||||
|
||||
implementation Deps.leanplum_core
|
||||
implementation Deps.leanplum_fcm
|
||||
|
@ -429,7 +428,6 @@ dependencies {
|
|||
implementation Deps.androidx_lifecycle_viewmodel
|
||||
implementation Deps.androidx_core
|
||||
implementation Deps.androidx_core_ktx
|
||||
implementation Deps.androidx_dynamic_animation
|
||||
implementation Deps.androidx_transition
|
||||
implementation Deps.androidx_work_ktx
|
||||
implementation Deps.google_material
|
||||
|
|
524
app/metrics.yaml
524
app/metrics.yaml
File diff suppressed because it is too large
Load Diff
|
@ -16,6 +16,7 @@ import org.junit.Test
|
|||
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
|
||||
import org.mozilla.fenix.helpers.HomeActivityTestRule
|
||||
import org.mozilla.fenix.helpers.TestAssetHelper
|
||||
import org.mozilla.fenix.ui.robots.clickUrlbar
|
||||
import org.mozilla.fenix.ui.robots.homeScreen
|
||||
import org.mozilla.fenix.ui.robots.navigationToolbar
|
||||
|
||||
|
@ -254,4 +255,69 @@ class SmokeTest {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun verifySearchEngineCanBeChangedTemporarilyUsingShortcuts() {
|
||||
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
|
||||
|
||||
homeScreen {
|
||||
}.openSearch {
|
||||
verifyKeyboardVisibility()
|
||||
clickSearchEngineButton()
|
||||
verifySearchEngineList()
|
||||
changeDefaultSearchEngine("Amazon.com")
|
||||
verifySearchEngineIcon("Amazon.com")
|
||||
}.goToSearchEngine {
|
||||
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
|
||||
}.openTabDrawer {
|
||||
// Changing search engine to Bing
|
||||
}.openHomeScreen {
|
||||
}.openSearch {
|
||||
clickSearchEngineButton()
|
||||
mDevice.waitForIdle()
|
||||
changeDefaultSearchEngine("Bing")
|
||||
verifySearchEngineIcon("Bing")
|
||||
}.goToSearchEngine {
|
||||
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
|
||||
}.openTabDrawer {
|
||||
// Changing search engine to DuckDuckGo
|
||||
}.openHomeScreen {
|
||||
}.openSearch {
|
||||
clickSearchEngineButton()
|
||||
mDevice.waitForIdle()
|
||||
changeDefaultSearchEngine("DuckDuckGo")
|
||||
verifySearchEngineIcon("DuckDuckGo")
|
||||
}.goToSearchEngine {
|
||||
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
|
||||
}.openTabDrawer {
|
||||
// Changing search engine to Twitter
|
||||
}.openHomeScreen {
|
||||
}.openSearch {
|
||||
clickSearchEngineButton()
|
||||
mDevice.waitForIdle()
|
||||
changeDefaultSearchEngine("Twitter")
|
||||
verifySearchEngineIcon("Twitter")
|
||||
}.goToSearchEngine {
|
||||
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
|
||||
}.openTabDrawer {
|
||||
// Changing search engine to Wikipedia
|
||||
}.openHomeScreen {
|
||||
}.openSearch {
|
||||
clickSearchEngineButton()
|
||||
changeDefaultSearchEngine("Wikipedia")
|
||||
verifySearchEngineIcon("Wikipedia")
|
||||
}.goToSearchEngine {
|
||||
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
|
||||
}.openTabDrawer {
|
||||
// Checking whether the next search will be with default or not
|
||||
}.openHomeScreen {
|
||||
}.openSearch {
|
||||
}.goToSearchEngine {
|
||||
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
|
||||
}.openNavigationToolbar {
|
||||
clickUrlbar {
|
||||
verifyDefaultSearchEngine("Google")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,12 +12,14 @@ import android.net.Uri
|
|||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.Espresso.pressBack
|
||||
import androidx.test.espresso.action.ViewActions
|
||||
import androidx.test.espresso.action.ViewActions.click
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.intent.Intents
|
||||
import androidx.test.espresso.intent.matcher.BundleMatchers
|
||||
import androidx.test.espresso.intent.matcher.IntentMatchers
|
||||
import androidx.test.espresso.matcher.RootMatchers.isDialog
|
||||
import androidx.test.espresso.matcher.ViewMatchers
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
|
||||
import androidx.test.espresso.matcher.ViewMatchers.Visibility
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
|
||||
|
|
|
@ -239,6 +239,12 @@ fun navigationToolbar(interact: NavigationToolbarRobot.() -> Unit): NavigationTo
|
|||
return NavigationToolbarRobot.Transition()
|
||||
}
|
||||
|
||||
fun clickUrlbar(interact: SearchRobot.() -> Unit): SearchRobot.Transition {
|
||||
urlBar().click()
|
||||
SearchRobot().interact()
|
||||
return SearchRobot.Transition()
|
||||
}
|
||||
|
||||
private fun assertSuggestionsAreEqualTo(suggestionSize: Int, searchTerm: String) {
|
||||
mDevice.waitForIdle()
|
||||
awesomeBar().perform(typeText(searchTerm))
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
package org.mozilla.fenix.ui.robots
|
||||
|
||||
import android.widget.ToggleButton
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.ViewInteraction
|
||||
|
@ -16,7 +17,9 @@ import androidx.test.espresso.action.ViewActions.typeText
|
|||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.contrib.RecyclerViewActions
|
||||
import androidx.test.espresso.matcher.ViewMatchers
|
||||
import androidx.test.espresso.matcher.ViewMatchers.Visibility
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
|
@ -28,8 +31,11 @@ import androidx.test.uiautomator.Until
|
|||
import org.hamcrest.CoreMatchers.allOf
|
||||
import org.hamcrest.CoreMatchers.startsWith
|
||||
import org.hamcrest.Matchers
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.helpers.TestAssetHelper
|
||||
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime
|
||||
import org.mozilla.fenix.helpers.click
|
||||
import org.mozilla.fenix.helpers.ext.waitNotNull
|
||||
|
||||
/**
|
||||
|
@ -47,6 +53,24 @@ class SearchRobot {
|
|||
fun verifySearchSettings() = assertSearchSettings()
|
||||
fun verifySearchBarEmpty() = assertSearchBarEmpty()
|
||||
|
||||
fun verifyKeyboardVisibility() = assertKeyboardVisibility(isExpectedToBeVisible = true)
|
||||
fun verifySearchEngineList() = assertSearchEngineList()
|
||||
fun verifySearchEngineIcon(expectedText: String) {
|
||||
onView(withContentDescription(expectedText))
|
||||
}
|
||||
fun verifyDefaultSearchEngine(expectedText: String) = assertDefaultSearchEngine(expectedText)
|
||||
|
||||
fun changeDefaultSearchEngine(searchEngineName: String) =
|
||||
selectDefaultSearchEngine(searchEngineName)
|
||||
|
||||
fun clickSearchEngineButton() {
|
||||
val searchEngineButton = mDevice.findObject(UiSelector()
|
||||
.instance(1)
|
||||
.className(ToggleButton::class.java))
|
||||
searchEngineButton.waitForExists(waitingTime)
|
||||
searchEngineButton.click()
|
||||
}
|
||||
|
||||
fun clickScanButton() {
|
||||
scanButton().perform(click())
|
||||
}
|
||||
|
@ -106,6 +130,11 @@ class SearchRobot {
|
|||
BrowserRobot().interact()
|
||||
return BrowserRobot.Transition()
|
||||
}
|
||||
|
||||
fun goToSearchEngine(interact: NavigationToolbarRobot.() -> Unit): NavigationToolbarRobot.Transition {
|
||||
NavigationToolbarRobot().interact()
|
||||
return NavigationToolbarRobot.Transition()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -178,4 +207,46 @@ fun searchScreen(interact: SearchRobot.() -> Unit): SearchRobot.Transition {
|
|||
return SearchRobot.Transition()
|
||||
}
|
||||
|
||||
private fun assertKeyboardVisibility(isExpectedToBeVisible: Boolean) = {
|
||||
mDevice.waitNotNull(
|
||||
Until.findObject(
|
||||
By.text("Search Engine")
|
||||
), waitingTime
|
||||
)
|
||||
assertEquals(
|
||||
isExpectedToBeVisible,
|
||||
UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
|
||||
.executeShellCommand("dumpsys input_method | grep mInputShown")
|
||||
.contains("mInputShown=true")
|
||||
)
|
||||
}
|
||||
|
||||
private fun assertSearchEngineList() {
|
||||
onView(withId(R.id.mozac_browser_toolbar_edit_icon)).click()
|
||||
onView(withText("Google"))
|
||||
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
|
||||
onView(withText("Amazon.com"))
|
||||
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
|
||||
onView(withText("Bing"))
|
||||
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
|
||||
onView(withText("DuckDuckGo"))
|
||||
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
|
||||
onView(withText("Twitter"))
|
||||
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
|
||||
onView(withText("Wikipedia"))
|
||||
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
|
||||
}
|
||||
|
||||
private fun selectDefaultSearchEngine(searchEngine: String) {
|
||||
onView(withId(R.id.mozac_browser_toolbar_edit_icon)).click()
|
||||
onView(withText(searchEngine))
|
||||
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
|
||||
.perform(click())
|
||||
}
|
||||
|
||||
private fun assertDefaultSearchEngine(expectedText: String) {
|
||||
onView(allOf(withId(R.id.mozac_browser_toolbar_edit_icon), withContentDescription(expectedText)))
|
||||
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
|
||||
}
|
||||
|
||||
private fun goBackButton() = onView(allOf(withContentDescription("Navigate up")))
|
||||
|
|
|
@ -228,13 +228,8 @@
|
|||
<activity android:name=".settings.account.AuthIntentReceiverActivity"
|
||||
android:exported="false" />
|
||||
|
||||
<activity android:name="com.google.android.gms.oss.licenses.OssLicensesMenuActivity"
|
||||
android:exported="false"
|
||||
android:theme="@style/Theme.AppCompat.DayNight.DarkActionBar"/>
|
||||
|
||||
<activity android:name="com.google.android.gms.oss.licenses.OssLicensesActivity"
|
||||
android:exported="false"
|
||||
android:theme="@style/Theme.AppCompat.DayNight.DarkActionBar"/>
|
||||
<activity android:name=".settings.about.AboutLibrariesActivity"
|
||||
android:exported="false" />
|
||||
|
||||
<service android:name=".media.MediaService"
|
||||
android:exported="false" />
|
||||
|
|
|
@ -29,11 +29,6 @@ object FeatureFlags {
|
|||
*/
|
||||
val tabTray = Config.channel.isNightlyOrDebug
|
||||
|
||||
/**
|
||||
* Enables gestures on the browser chrome that depend on a [SwipeGestureLayout]
|
||||
*/
|
||||
val browserChromeGestures = Config.channel.isNightlyOrDebug
|
||||
|
||||
/**
|
||||
* Enables viewing tab history
|
||||
*/
|
||||
|
|
|
@ -168,11 +168,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session
|
|||
require(arguments != null)
|
||||
customTabSessionId = arguments?.getString(EXTRA_SESSION_ID)
|
||||
|
||||
val view = if (FeatureFlags.browserChromeGestures) {
|
||||
inflater.inflate(R.layout.browser_gesture_wrapper, container, false)
|
||||
} else {
|
||||
inflater.inflate(R.layout.fragment_browser, container, false)
|
||||
}
|
||||
val view = inflater.inflate(R.layout.fragment_browser, container, false)
|
||||
|
||||
val activity = activity as HomeActivity
|
||||
activity.themeManager.applyStatusBarTheme(activity)
|
||||
|
|
|
@ -15,7 +15,6 @@ import androidx.core.content.ContextCompat
|
|||
import androidx.lifecycle.Observer
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import kotlinx.android.synthetic.main.browser_gesture_wrapper.*
|
||||
import kotlinx.android.synthetic.main.fragment_browser.*
|
||||
import kotlinx.android.synthetic.main.fragment_browser.view.*
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
|
@ -32,7 +31,6 @@ import mozilla.components.feature.tab.collections.TabCollection
|
|||
import mozilla.components.feature.tabs.WindowFeature
|
||||
import mozilla.components.support.base.feature.UserInteractionHandler
|
||||
import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
|
||||
import org.mozilla.fenix.FeatureFlags
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.addons.runIfFragmentIsAttached
|
||||
import org.mozilla.fenix.components.FenixSnackbar
|
||||
|
@ -77,19 +75,15 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler {
|
|||
val components = context.components
|
||||
|
||||
return super.initializeUI(view)?.also {
|
||||
// We need to wrap this whole thing in an if here because gestureLayout will not exist
|
||||
// if the feature flag is off
|
||||
if (FeatureFlags.browserChromeGestures) {
|
||||
gestureLayout.addGestureListener(
|
||||
ToolbarGestureHandler(
|
||||
activity = requireActivity(),
|
||||
contentLayout = browserLayout,
|
||||
tabPreview = tabPreview,
|
||||
toolbarLayout = browserToolbarView.view,
|
||||
sessionManager = components.core.sessionManager
|
||||
)
|
||||
gestureLayout.addGestureListener(
|
||||
ToolbarGestureHandler(
|
||||
activity = requireActivity(),
|
||||
contentLayout = browserLayout,
|
||||
tabPreview = tabPreview,
|
||||
toolbarLayout = browserToolbarView.view,
|
||||
sessionManager = components.core.sessionManager
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
val readerModeAction =
|
||||
BrowserToolbar.ToggleButton(
|
||||
|
|
|
@ -6,19 +6,19 @@ package org.mozilla.fenix.browser
|
|||
|
||||
import android.animation.Animator
|
||||
import android.animation.AnimatorListenerAdapter
|
||||
import android.animation.ValueAnimator
|
||||
import android.app.Activity
|
||||
import android.graphics.PointF
|
||||
import android.graphics.Rect
|
||||
import android.util.TypedValue
|
||||
import android.view.View
|
||||
import android.view.ViewConfiguration
|
||||
import androidx.annotation.Dimension
|
||||
import androidx.annotation.Dimension.DP
|
||||
import androidx.core.animation.doOnEnd
|
||||
import androidx.core.graphics.contains
|
||||
import androidx.core.graphics.toPoint
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.dynamicanimation.animation.DynamicAnimation
|
||||
import androidx.dynamicanimation.animation.FlingAnimation
|
||||
import androidx.interpolator.view.animation.LinearOutSlowInInterpolator
|
||||
import mozilla.components.browser.session.Session
|
||||
import mozilla.components.browser.session.SessionManager
|
||||
import mozilla.components.support.ktx.android.util.dpToPx
|
||||
|
@ -61,11 +61,6 @@ class ToolbarGestureHandler(
|
|||
|
||||
private val touchSlop = ViewConfiguration.get(activity).scaledTouchSlop
|
||||
private val minimumFlingVelocity = ViewConfiguration.get(activity).scaledMinimumFlingVelocity
|
||||
private val defaultVelocity = TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP,
|
||||
MINIMUM_ANIMATION_VELOCITY,
|
||||
activity.resources.displayMetrics
|
||||
)
|
||||
|
||||
private var gestureDirection = GestureDirection.LEFT_TO_RIGHT
|
||||
|
||||
|
@ -143,25 +138,12 @@ class ToolbarGestureHandler(
|
|||
) {
|
||||
val destination = getDestination()
|
||||
if (destination is Destination.Tab && isGestureComplete(velocityX)) {
|
||||
animateToNextTab(velocityX, destination.session)
|
||||
animateToNextTab(destination.session)
|
||||
} else {
|
||||
animateCanceledGesture(velocityX)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createFlingAnimation(
|
||||
view: View,
|
||||
minValue: Float,
|
||||
maxValue: Float,
|
||||
startVelocity: Float
|
||||
): FlingAnimation =
|
||||
FlingAnimation(view, DynamicAnimation.TRANSLATION_X).apply {
|
||||
setMinValue(minValue)
|
||||
setMaxValue(maxValue)
|
||||
setStartVelocity(startVelocity)
|
||||
friction = ViewConfiguration.getScrollFriction()
|
||||
}
|
||||
|
||||
private fun getDestination(): Destination {
|
||||
val isLtr = activity.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_LTR
|
||||
val currentSession = sessionManager.selectedSession ?: return Destination.None
|
||||
|
@ -234,73 +216,59 @@ class ToolbarGestureHandler(
|
|||
abs(velocityX) >= minimumFlingVelocity)
|
||||
}
|
||||
|
||||
private fun getVelocityFromFling(velocityX: Float): Float {
|
||||
return max(abs(velocityX), defaultVelocity)
|
||||
private fun getAnimator(finalContextX: Float, duration: Long): ValueAnimator {
|
||||
return ValueAnimator.ofFloat(contentLayout.translationX, finalContextX).apply {
|
||||
this.duration = duration
|
||||
this.interpolator = LinearOutSlowInInterpolator()
|
||||
addUpdateListener { animator ->
|
||||
val value = animator.animatedValue as Float
|
||||
contentLayout.translationX = value
|
||||
tabPreview.translationX = when (gestureDirection) {
|
||||
GestureDirection.RIGHT_TO_LEFT -> value + windowWidth + previewOffset
|
||||
GestureDirection.LEFT_TO_RIGHT -> value - windowWidth - previewOffset
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun animateToNextTab(velocityX: Float, session: Session) {
|
||||
private fun animateToNextTab(session: Session) {
|
||||
val browserFinalXCoordinate: Float = when (gestureDirection) {
|
||||
GestureDirection.RIGHT_TO_LEFT -> -windowWidth.toFloat() - previewOffset
|
||||
GestureDirection.LEFT_TO_RIGHT -> windowWidth.toFloat() + previewOffset
|
||||
}
|
||||
val animationVelocity = when (gestureDirection) {
|
||||
GestureDirection.RIGHT_TO_LEFT -> -getVelocityFromFling(velocityX)
|
||||
GestureDirection.LEFT_TO_RIGHT -> getVelocityFromFling(velocityX)
|
||||
}
|
||||
|
||||
// Finish animating the contentLayout off screen and tabPreview on screen
|
||||
createFlingAnimation(
|
||||
view = contentLayout,
|
||||
minValue = min(0f, browserFinalXCoordinate),
|
||||
maxValue = max(0f, browserFinalXCoordinate),
|
||||
startVelocity = animationVelocity
|
||||
).addUpdateListener { _, value, _ ->
|
||||
tabPreview.translationX = when (gestureDirection) {
|
||||
GestureDirection.RIGHT_TO_LEFT -> value + windowWidth + previewOffset
|
||||
GestureDirection.LEFT_TO_RIGHT -> value - windowWidth - previewOffset
|
||||
}
|
||||
}.addEndListener { _, _, _, _ ->
|
||||
contentLayout.translationX = 0f
|
||||
sessionManager.select(session)
|
||||
getAnimator(browserFinalXCoordinate, FINISHED_GESTURE_ANIMATION_DURATION).apply {
|
||||
doOnEnd {
|
||||
contentLayout.translationX = 0f
|
||||
sessionManager.select(session)
|
||||
|
||||
// Fade out the tab preview to prevent flickering
|
||||
val shortAnimationDuration =
|
||||
activity.resources.getInteger(android.R.integer.config_shortAnimTime)
|
||||
tabPreview.animate()
|
||||
.alpha(0f)
|
||||
.setDuration(shortAnimationDuration.toLong())
|
||||
.setListener(object : AnimatorListenerAdapter() {
|
||||
override fun onAnimationEnd(animation: Animator?) {
|
||||
tabPreview.isVisible = false
|
||||
}
|
||||
})
|
||||
// Fade out the tab preview to prevent flickering
|
||||
val shortAnimationDuration =
|
||||
activity.resources.getInteger(android.R.integer.config_shortAnimTime)
|
||||
tabPreview.animate()
|
||||
.alpha(0f)
|
||||
.setDuration(shortAnimationDuration.toLong())
|
||||
.setListener(object : AnimatorListenerAdapter() {
|
||||
override fun onAnimationEnd(animation: Animator?) {
|
||||
tabPreview.isVisible = false
|
||||
}
|
||||
})
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
|
||||
private fun animateCanceledGesture(gestureVelocity: Float) {
|
||||
val velocity = if (getDestination() is Destination.None) {
|
||||
defaultVelocity
|
||||
private fun animateCanceledGesture(velocityX: Float) {
|
||||
val duration = if (abs(velocityX) >= minimumFlingVelocity) {
|
||||
CANCELED_FLING_ANIMATION_DURATION
|
||||
} else {
|
||||
getVelocityFromFling(gestureVelocity)
|
||||
}.let { v ->
|
||||
when (gestureDirection) {
|
||||
GestureDirection.RIGHT_TO_LEFT -> v
|
||||
GestureDirection.LEFT_TO_RIGHT -> -v
|
||||
}
|
||||
CANCELED_GESTURE_ANIMATION_DURATION
|
||||
}
|
||||
|
||||
createFlingAnimation(
|
||||
view = contentLayout,
|
||||
minValue = min(0f, contentLayout.translationX),
|
||||
maxValue = max(0f, contentLayout.translationX),
|
||||
startVelocity = velocity
|
||||
).addUpdateListener { _, value, _ ->
|
||||
tabPreview.translationX = when (gestureDirection) {
|
||||
GestureDirection.RIGHT_TO_LEFT -> value + windowWidth + previewOffset
|
||||
GestureDirection.LEFT_TO_RIGHT -> value - windowWidth - previewOffset
|
||||
getAnimator(0f, duration).apply {
|
||||
doOnEnd {
|
||||
tabPreview.isVisible = false
|
||||
}
|
||||
}.addEndListener { _, _, _, _ ->
|
||||
tabPreview.isVisible = false
|
||||
}.start()
|
||||
}
|
||||
|
||||
|
@ -336,16 +304,25 @@ class ToolbarGestureHandler(
|
|||
*/
|
||||
private const val OVERSCROLL_HIDE_PERCENT = 0.20
|
||||
|
||||
/**
|
||||
* The speed of the fling animation (in dp per second).
|
||||
*/
|
||||
@Dimension(unit = DP)
|
||||
private const val MINIMUM_ANIMATION_VELOCITY = 1500f
|
||||
|
||||
/**
|
||||
* The size of the gap between the tab preview and content layout.
|
||||
*/
|
||||
@Dimension(unit = DP)
|
||||
private const val PREVIEW_OFFSET = 48
|
||||
|
||||
/**
|
||||
* Animation duration when switching to another tab
|
||||
*/
|
||||
private const val FINISHED_GESTURE_ANIMATION_DURATION = 250L
|
||||
|
||||
/**
|
||||
* Animation duration gesture is canceled due to the swipe not being far enough
|
||||
*/
|
||||
private const val CANCELED_GESTURE_ANIMATION_DURATION = 200L
|
||||
|
||||
/**
|
||||
* Animation duration gesture is canceled due to a swipe in the opposite direction
|
||||
*/
|
||||
private const val CANCELED_FLING_ANIMATION_DURATION = 150L
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
package org.mozilla.fenix.components.metrics
|
||||
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import com.leanplum.Leanplum
|
||||
import mozilla.components.browser.awesomebar.facts.BrowserAwesomeBarFacts
|
||||
import mozilla.components.browser.menu.facts.BrowserMenuFacts
|
||||
import mozilla.components.browser.toolbar.facts.ToolbarFacts
|
||||
|
@ -193,6 +194,7 @@ internal class ReleaseMetricController(
|
|||
if (installedAddons is List<*>) {
|
||||
Addons.installedAddons.set(installedAddons.map { it.toString() })
|
||||
Addons.hasInstalledAddons.set(installedAddons.size > 0)
|
||||
Leanplum.setUserAttributes(mapOf("installed_addons" to installedAddons.size))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -200,6 +202,7 @@ internal class ReleaseMetricController(
|
|||
if (enabledAddons is List<*>) {
|
||||
Addons.enabledAddons.set(enabledAddons.map { it.toString() })
|
||||
Addons.hasEnabledAddons.set(enabledAddons.size > 0)
|
||||
Leanplum.setUserAttributes(mapOf("enabled_addons" to enabledAddons.size))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,6 @@ import android.view.ViewGroup
|
|||
import androidx.core.content.pm.PackageInfoCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
import com.google.android.gms.oss.licenses.OssLicensesMenuActivity
|
||||
import kotlinx.android.synthetic.main.fragment_about.*
|
||||
import org.mozilla.fenix.BrowserDirection
|
||||
import org.mozilla.fenix.BuildConfig
|
||||
|
@ -168,13 +167,8 @@ class AboutFragment : Fragment(), AboutPageListener {
|
|||
}
|
||||
|
||||
private fun openLibrariesPage() {
|
||||
startActivity(Intent(context, OssLicensesMenuActivity::class.java))
|
||||
OssLicensesMenuActivity.setActivityTitle(
|
||||
getString(
|
||||
R.string.open_source_licenses_title,
|
||||
appName
|
||||
)
|
||||
)
|
||||
val intent = Intent(requireContext(), AboutLibrariesActivity::class.java)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
override fun onAboutItemClicked(item: AboutItem) {
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.fenix.settings.about
|
||||
|
||||
import android.graphics.Typeface
|
||||
import android.os.Bundle
|
||||
import android.text.util.Linkify
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.ListView
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import org.mozilla.fenix.R
|
||||
import java.nio.charset.Charset
|
||||
import java.util.Locale
|
||||
|
||||
/**
|
||||
* Displays the licenses of all the libraries used by Fenix.
|
||||
*
|
||||
* This is a re-implementation of play-services-oss-licenses library.
|
||||
* We can't use the official implementation in the OSS flavor of Fenix
|
||||
* because it is proprietary and closed-source.
|
||||
*
|
||||
* There are popular FLOSS alternatives to Google's plugin and library
|
||||
* such as AboutLibraries (https://github.com/mikepenz/AboutLibraries)
|
||||
* but we considered the risk of introducing such third-party dependency
|
||||
* to Fenix too high. Therefore, we use Google's gradle plugin to
|
||||
* extract the dependencies and their licenses, and this activity
|
||||
* to show the extracted licenses to the end-user.
|
||||
*/
|
||||
class AboutLibrariesActivity : AppCompatActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
val appName = getString(R.string.app_name)
|
||||
title = getString(R.string.open_source_licenses_title, appName)
|
||||
setContentView(R.layout.about_libraries_activity)
|
||||
|
||||
setSupportActionBar(findViewById(R.id.toolbar))
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
supportActionBar?.setDisplayShowHomeEnabled(true)
|
||||
|
||||
setupLibrariesListView()
|
||||
}
|
||||
|
||||
override fun onSupportNavigateUp(): Boolean {
|
||||
onBackPressed()
|
||||
return true
|
||||
}
|
||||
|
||||
private fun setupLibrariesListView() {
|
||||
val libraries = parseLibraries()
|
||||
val listView = findViewById<ListView>(R.id.about_libraries_listview)
|
||||
listView.adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, libraries)
|
||||
listView.setOnItemClickListener { _, _, position, _ ->
|
||||
showLicenseDialog(libraries[position])
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseLibraries(): List<LibraryItem> {
|
||||
/*
|
||||
The gradle plugin "oss-licenses-plugin" creates two "raw" resources:
|
||||
|
||||
- third_party_licenses which is the binary concatenation of all the licenses text for
|
||||
all the libraries. License texts can either be an URL to a license file or just the
|
||||
raw text of the license.
|
||||
|
||||
- third_party_licenses_metadata which contains one dependency per line formatted in
|
||||
the following way: "[start_offset]:[length] [name]"
|
||||
|
||||
[start_offset] : first byte in third_party_licenses that contains the license
|
||||
text for this library.
|
||||
[length] : length of the license text for this library in
|
||||
third_party_licenses.
|
||||
[name] : either the name of the library, or its artifact name.
|
||||
|
||||
See https://github.com/google/play-services-plugins/tree/master/oss-licenses-plugin
|
||||
*/
|
||||
val licensesData = resources
|
||||
.openRawResource(R.raw.third_party_licenses)
|
||||
.readBytes()
|
||||
val licensesMetadataReader = resources
|
||||
.openRawResource(R.raw.third_party_license_metadata)
|
||||
.bufferedReader()
|
||||
|
||||
return licensesMetadataReader.use { reader -> reader.readLines() }.map { line ->
|
||||
val (section, name) = line.split(" ", limit = 2)
|
||||
val (startOffset, length) = section.split(":", limit = 2).map(String::toInt)
|
||||
val licenseData = licensesData.sliceArray(startOffset until startOffset + length)
|
||||
val licenseText = licenseData.toString(Charset.forName("UTF-8"))
|
||||
LibraryItem(name, licenseText)
|
||||
}.sortedBy { item -> item.name.toLowerCase(Locale.ROOT) }
|
||||
}
|
||||
|
||||
private fun showLicenseDialog(libraryItem: LibraryItem) {
|
||||
val dialog = AlertDialog.Builder(this)
|
||||
.setTitle(libraryItem.name)
|
||||
.setMessage(libraryItem.license)
|
||||
.create()
|
||||
dialog.show()
|
||||
|
||||
val textView = dialog.findViewById<TextView>(android.R.id.message)!!
|
||||
Linkify.addLinks(textView, Linkify.ALL)
|
||||
textView.linksClickable = true
|
||||
textView.textSize = LICENSE_TEXT_SIZE
|
||||
textView.typeface = Typeface.MONOSPACE
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val LICENSE_TEXT_SIZE = 10F
|
||||
}
|
||||
}
|
||||
|
||||
private class LibraryItem(val name: String, val license: String) {
|
||||
override fun toString(): String {
|
||||
return name
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
<?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/. -->
|
||||
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/about_libraries"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true"
|
||||
tools:context="org.mozilla.fenix.settings.about.AboutLibrariesActivity">
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="?attr/colorPrimary"
|
||||
android:elevation="4dp"
|
||||
android:theme="@style/ThemeOverlay.AppCompat.ActionBar" />
|
||||
<ListView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/about_libraries_listview" />
|
||||
</RelativeLayout>
|
|
@ -1,19 +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/. -->
|
||||
<org.mozilla.fenix.browser.SwipeGestureLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/gestureLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<include layout="@layout/fragment_browser" />
|
||||
|
||||
<org.mozilla.fenix.browser.TabPreview
|
||||
android:id="@+id/tabPreview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clickable="false"
|
||||
android:focusable="false"
|
||||
android:visibility="gone" />
|
||||
</org.mozilla.fenix.browser.SwipeGestureLayout>
|
|
@ -1,53 +1,66 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
<?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/. -->
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<org.mozilla.fenix.browser.SwipeGestureLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/browserLayout"
|
||||
android:id="@+id/gestureLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context="browser.BrowserFragment">
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:id="@+id/swipeRefresh"
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:id="@+id/browserLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:alpha="0"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||
tools:context="browser.BrowserFragment">
|
||||
|
||||
<mozilla.components.concept.engine.EngineView
|
||||
android:id="@+id/engineView"
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:id="@+id/swipeRefresh"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:alpha="0"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||
|
||||
<mozilla.components.concept.engine.EngineView
|
||||
android:id="@+id/engineView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="gone" />
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
|
||||
|
||||
<ViewStub
|
||||
android:id="@+id/stubFindInPage"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:layout_gravity="bottom"
|
||||
android:inflatedId="@+id/findInPageView"
|
||||
android:layout="@layout/stub_find_in_page" />
|
||||
|
||||
<include
|
||||
android:id="@+id/viewDynamicDownloadDialog"
|
||||
layout="@layout/download_dialog_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
android:visibility="gone" />
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
|
||||
<mozilla.components.feature.readerview.view.ReaderViewControlsBar
|
||||
android:id="@+id/readerViewControlsBar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
android:background="?foundation"
|
||||
android:elevation="24dp"
|
||||
android:visibility="gone" />
|
||||
|
||||
<ViewStub
|
||||
android:id="@+id/stubFindInPage"
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
<org.mozilla.fenix.browser.TabPreview
|
||||
android:id="@+id/tabPreview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:layout_gravity="bottom"
|
||||
android:inflatedId="@+id/findInPageView"
|
||||
android:layout="@layout/stub_find_in_page" />
|
||||
|
||||
<include
|
||||
android:id="@+id/viewDynamicDownloadDialog"
|
||||
layout="@layout/download_dialog_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
android:layout_height="match_parent"
|
||||
android:clickable="false"
|
||||
android:focusable="false"
|
||||
android:visibility="gone" />
|
||||
|
||||
<mozilla.components.feature.readerview.view.ReaderViewControlsBar
|
||||
android:id="@+id/readerViewControlsBar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
android:background="?foundation"
|
||||
android:elevation="24dp"
|
||||
android:visibility="gone" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
</org.mozilla.fenix.browser.SwipeGestureLayout>
|
||||
|
|
|
@ -306,6 +306,8 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara
|
|||
<!-- Preference for open links in third party apps -->
|
||||
<string name="preferences_open_links_in_apps">Ldi iseɣwan deg isnasen</string>
|
||||
|
||||
<!-- Preference for open download with an external download manager app -->
|
||||
<string name="preferences_external_download_manager">Amsefrak n usider azɣaray</string>
|
||||
<!-- Preference for add_ons -->
|
||||
<string name="preferences_addons">Izegrar</string>
|
||||
|
||||
|
|
|
@ -30,16 +30,16 @@
|
|||
<!-- Tab tray multi select title in app bar. The first parameter is the number of tabs selected -->
|
||||
<string name="tab_tray_multi_select_title">%1$d개 선택됨</string>
|
||||
<!-- Label of button in create collection dialog for creating a new collection -->
|
||||
<string name="tab_tray_add_new_collection">새 컬렉션 추가</string>
|
||||
<string name="tab_tray_add_new_collection">새 모음집 추가</string>
|
||||
<!-- Label of editable text in create collection dialog for naming a new collection -->
|
||||
<string name="tab_tray_add_new_collection_name">이름</string>
|
||||
<!-- Label of button in save to collection dialog for selecting a current collection -->
|
||||
<string name="tab_tray_select_collection">컬렉션 선택</string>
|
||||
<string name="tab_tray_select_collection">모음집 선택</string>
|
||||
|
||||
<!-- Content description for close button while in multiselect mode in tab tray -->
|
||||
<string name="tab_tray_close_multiselect_content_description">다중 선택 모드 종료</string>
|
||||
<!-- Content description for save to collection button while in multiselect mode in tab tray -->
|
||||
<string name="tab_tray_collection_button_multiselect_content_description">선택한 탭을 컬렉션에 저장</string>
|
||||
<string name="tab_tray_collection_button_multiselect_content_description">선택한 탭을 모음집에 저장</string>
|
||||
|
||||
<!-- Content description for checkmark while tab is selected while in multiselect mode in tab tray. The first parameter is the title of the tab selected -->
|
||||
<string name="tab_tray_item_selected_multiselect_content_description">%1$s 선택됨</string>
|
||||
|
@ -49,7 +49,7 @@
|
|||
<!-- Content description announcement when exiting multiselect mode in tab tray -->
|
||||
<string name="tab_tray_exit_multiselect_content_description">다중 선택 모드 종료됨</string>
|
||||
<!-- Content description announcement when entering multiselect mode in tab tray -->
|
||||
<string name="tab_tray_enter_multiselect_content_description">다중 선택 모드로 전환됨, 컬렉션에 저장할 탭을 선택하세요</string>
|
||||
<string name="tab_tray_enter_multiselect_content_description">다중 선택 모드로 전환됨, 모음집에 저장할 탭을 선택하세요</string>
|
||||
<!-- Content description on checkmark while tab is selected in multiselect mode in tab tray -->
|
||||
<string name="tab_tray_multiselect_selected_content_description">선택됨</string>
|
||||
|
||||
|
@ -137,7 +137,7 @@
|
|||
<!-- Browser menu button that creates a new tab -->
|
||||
<string name="browser_menu_new_tab">새 탭</string>
|
||||
<!-- Browser menu button that saves the current tab to a collection -->
|
||||
<string name="browser_menu_save_to_collection_2">컬렉션에 저장</string>
|
||||
<string name="browser_menu_save_to_collection_2">모음집에 저장</string>
|
||||
<!-- Browser menu button that open a share menu to share the current site -->
|
||||
<string name="browser_menu_share">공유</string>
|
||||
<!-- Share menu title, displayed when a user is sharing their current site -->
|
||||
|
@ -497,7 +497,7 @@
|
|||
<!-- Text shown as the title of the open tab tray -->
|
||||
<string name="tab_tray_title">탭 열기</string>
|
||||
<!-- Text shown in the menu for saving tabs to a collection -->
|
||||
<string name="tab_tray_menu_item_save">컬렉션에 저장</string>
|
||||
<string name="tab_tray_menu_item_save">모음집에 저장</string>
|
||||
<!-- Text shown in the menu for sharing all tabs -->
|
||||
<string name="tab_tray_menu_item_share">모든 탭 공유</string>
|
||||
<!-- Text shown in the menu for closing all tabs -->
|
||||
|
@ -509,7 +509,7 @@
|
|||
<!-- Shortcut action to toggle private mode -->
|
||||
<string name="tab_tray_menu_toggle">탭 모드 전환</string>
|
||||
<!-- Content description (not visible, for screen readers etc.): Removes tab from collection button. Removes the selected tab from collection when pressed -->
|
||||
<string name="remove_tab_from_collection">컬렉션에서 탭 삭제</string>
|
||||
<string name="remove_tab_from_collection">모음집에서 탭 삭제</string>
|
||||
<!-- Content description (not visible, for screen readers etc.): Close tab button. Closes the current session when pressed -->
|
||||
<string name="close_tab">탭 닫기</string>
|
||||
<!-- Content description (not visible, for screen readers etc.): Close tab <title> button. First parameter is tab title -->
|
||||
|
@ -521,7 +521,7 @@
|
|||
<!-- Open tabs menu item to share all tabs -->
|
||||
<string name="tabs_menu_share_tabs">탭 공유</string>
|
||||
<!-- Open tabs menu item to save tabs to collection -->
|
||||
<string name="tabs_menu_save_to_collection1">탭을 컬렉션에 저장</string>
|
||||
<string name="tabs_menu_save_to_collection1">탭을 모음집에 저장</string>
|
||||
<!-- Content description (not visible, for screen readers etc.): Opens the tab menu when pressed -->
|
||||
<string name="tab_menu">탭 메뉴</string>
|
||||
<!-- Tab menu item to share the tab -->
|
||||
|
@ -536,11 +536,11 @@
|
|||
<string name="current_session_image">현재 세션 이미지</string>
|
||||
|
||||
<!-- Button to save the current set of tabs into a collection -->
|
||||
<string name="save_to_collection">컬렉션에 저장</string>
|
||||
<string name="save_to_collection">모음집에 저장</string>
|
||||
<!-- Text for the menu button to delete a collection -->
|
||||
<string name="collection_delete">컬렉션 삭제</string>
|
||||
<string name="collection_delete">모음집 삭제</string>
|
||||
<!-- Text for the menu button to rename a collection -->
|
||||
<string name="collection_rename">컬렉션 이름 변경</string>
|
||||
<string name="collection_rename">모음집 이름 변경</string>
|
||||
<!-- Text for the button to open tabs of the selected collection -->
|
||||
<string name="collection_open_tabs">열린 탭</string>
|
||||
|
||||
|
@ -751,25 +751,25 @@
|
|||
|
||||
<!-- Collections -->
|
||||
<!-- Collections header on home fragment -->
|
||||
<string name="collections_header">컬렉션</string>
|
||||
<string name="collections_header">모음집</string>
|
||||
<!-- Content description (not visible, for screen readers etc.): Opens the collection menu when pressed -->
|
||||
<string name="collection_menu_button_content_description">컬렉션 메뉴</string>
|
||||
<string name="collection_menu_button_content_description">모음집 메뉴</string>
|
||||
|
||||
<!-- No Open Tabs Message Header -->
|
||||
<string name="no_collections_header1">중요한 것들을 모으세요</string>
|
||||
<string name="no_collections_header1">중요한 것들 수집하기</string>
|
||||
<!-- Label to describe what collections are to a new user without any collections -->
|
||||
<string name="no_collections_description1">나중에 빠르게 액세스 할 수 있도록 유사한 검색, 사이트 및 탭을 그룹화하세요.</string>
|
||||
<string name="no_collections_description1">나중에 빠르게 접근할 수 있도록 유사한 검색, 사이트 및 탭을 모아 보세요.</string>
|
||||
<!-- Title for the "select tabs" step of the collection creator -->
|
||||
<string name="create_collection_select_tabs">탭 선택</string>
|
||||
|
||||
<!-- Title for the "select collection" step of the collection creator -->
|
||||
<string name="create_collection_select_collection">컬렉션 선택</string>
|
||||
<string name="create_collection_select_collection">모음집 선택</string>
|
||||
|
||||
<!-- Title for the "name collection" step of the collection creator -->
|
||||
<string name="create_collection_name_collection">컬렉션 이름</string>
|
||||
<string name="create_collection_name_collection">모음집 이름</string>
|
||||
|
||||
<!-- Button to add new collection for the "select collection" step of the collection creator -->
|
||||
<string name="create_collection_add_new_collection">새 컬렉션 추가</string>
|
||||
<string name="create_collection_add_new_collection">새 모음집 추가</string>
|
||||
|
||||
<!-- Button to select all tabs in the "select tabs" step of the collection creator -->
|
||||
<string name="create_collection_select_all">모두 선택</string>
|
||||
|
@ -790,7 +790,7 @@
|
|||
<string name="create_collection_tabs_saved">탭이 저장되었습니다!</string>
|
||||
|
||||
<!-- Text shown in snackbar when one or multiple tabs have been saved in a new collection -->
|
||||
<string name="create_collection_tabs_saved_new_collection">컬렉션 저장됨!</string>
|
||||
<string name="create_collection_tabs_saved_new_collection">모음집 저장됨!</string>
|
||||
<!-- Text shown in snackbar when one tab has been saved in a collection -->
|
||||
<string name="create_collection_tab_saved">탭이 저장되었습니다!</string>
|
||||
|
||||
|
@ -804,7 +804,7 @@
|
|||
<string name="create_collection_view">보기</string>
|
||||
|
||||
<!-- Default name for a new collection in "name new collection" step of the collection creator. %d is a placeholder for the number of collections-->
|
||||
<string name="create_collection_default_name">컬렉션 %d개</string>
|
||||
<string name="create_collection_default_name">모음집 %d개</string>
|
||||
|
||||
<!-- Share -->
|
||||
<!-- Share screen header -->
|
||||
|
@ -864,10 +864,10 @@
|
|||
<!-- Name of the "Powered by Fenix" notification channel. Displayed in the "App notifications" system settings for the app -->
|
||||
<string name="notification_powered_by_channel_name">제공:</string>
|
||||
<!-- Text shown in snackbar when user deletes a collection -->
|
||||
<string name="snackbar_collection_deleted">컬렉션 삭제됨</string>
|
||||
<string name="snackbar_collection_deleted">모음집 삭제됨</string>
|
||||
|
||||
<!-- Text shown in snackbar when user renames a collection -->
|
||||
<string name="snackbar_collection_renamed">컬렉션 이름 변경됨</string>
|
||||
<string name="snackbar_collection_renamed">모음집 이름 변경됨</string>
|
||||
<!-- Text shown in snackbar when user deletes a tab -->
|
||||
<string name="snackbar_tab_deleted">탭 삭제됨</string>
|
||||
|
||||
|
@ -904,9 +904,9 @@
|
|||
<!-- Tab collection deletion prompt dialog message. Placeholder will be replaced with the collection name -->
|
||||
<string name="tab_collection_dialog_message">%1$s 파일을 삭제하시겠습니까?</string>
|
||||
<!-- Collection and tab deletion prompt dialog message. This will show when the last tab from a collection is deleted -->
|
||||
<string name="delete_tab_and_collection_dialog_message">이 탭을 삭제하면 전체 컬렉션이 삭제됩니다. 언제든지 새 컬렉션을 만들 수 있습니다.</string>
|
||||
<string name="delete_tab_and_collection_dialog_message">이 탭을 삭제하면 전체 모음집이 삭제됩니다. 언제든지 새 모음집을 만들 수 있습니다.</string>
|
||||
<!-- Collection and tab deletion prompt dialog title. Placeholder will be replaced with the collection name. This will show when the last tab from a collection is deleted -->
|
||||
<string name="delete_tab_and_collection_dialog_title">%1$s 컬렉션을 삭제하시겠습니까?</string>
|
||||
<string name="delete_tab_and_collection_dialog_title">%1$s 모음집을 삭제하시겠습니까?</string>
|
||||
<!-- Tab collection deletion prompt dialog option to delete the collection -->
|
||||
<string name="tab_collection_dialog_positive">삭제</string>
|
||||
<!-- Tab collection deletion prompt dialog option to cancel deleting the collection -->
|
||||
|
|
|
@ -40,6 +40,8 @@
|
|||
<string name="tab_tray_collection_button_multiselect_content_description">Geselecteerde tabbladen in collectie opslaan</string>
|
||||
<!-- Content description for checkmark while tab is selected while in multiselect mode in tab tray. The first parameter is the title of the tab selected -->
|
||||
<string name="tab_tray_item_selected_multiselect_content_description">%1$s geselecteerd</string>
|
||||
<!-- Content description when tab is unselected while in multiselect mode in tab tray. The first parameter is the title of the tab unselected -->
|
||||
<string name="tab_tray_item_unselected_multiselect_content_description">Selectie %1$s ongedaan gemaakt</string>
|
||||
<!-- Content description on checkmark while tab is selected in multiselect mode in tab tray -->
|
||||
<string name="tab_tray_multiselect_selected_content_description">Geselecteerd</string>
|
||||
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.fenix.settings.about
|
||||
|
||||
import android.widget.ListView
|
||||
import android.widget.TextView
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
|
||||
import org.robolectric.Robolectric
|
||||
import org.robolectric.Shadows.shadowOf
|
||||
import org.robolectric.shadows.ShadowAlertDialog
|
||||
|
||||
@RunWith(FenixRobolectricTestRunner::class)
|
||||
class AboutLibrariesActivityTest {
|
||||
@Test
|
||||
fun `activity should display licenses`() {
|
||||
val activity = Robolectric.buildActivity(AboutLibrariesActivity::class.java).create().get()
|
||||
val listView = activity.findViewById<ListView>(R.id.about_libraries_listview)
|
||||
|
||||
assertTrue(0 < listView.count)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `item click should open license dialog`() {
|
||||
val activity = Robolectric.buildActivity(AboutLibrariesActivity::class.java).create().get()
|
||||
|
||||
val listView = activity.findViewById<ListView>(R.id.about_libraries_listview)
|
||||
val listViewShadow = shadowOf(listView)
|
||||
listViewShadow.clickFirstItemContainingText("org.mozilla.geckoview:geckoview")
|
||||
|
||||
val alertDialogShadow = ShadowAlertDialog.getLatestDialog()
|
||||
assertTrue(alertDialogShadow.isShowing)
|
||||
|
||||
val alertDialogText = alertDialogShadow
|
||||
.findViewById<TextView>(android.R.id.message)
|
||||
.text
|
||||
.toString()
|
||||
assertTrue(alertDialogText.contains("MPL"))
|
||||
}
|
||||
}
|
|
@ -3,5 +3,5 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
object AndroidComponents {
|
||||
const val VERSION = "54.0.20200814130102"
|
||||
const val VERSION = "54.0.20200818130156"
|
||||
}
|
||||
|
|
|
@ -10,7 +10,6 @@ object Versions {
|
|||
const val leakcanary = "2.4"
|
||||
const val leanplum = "5.4.0"
|
||||
const val osslicenses_plugin = "0.9.5"
|
||||
const val osslicenses_library = "17.0.0"
|
||||
const val detekt = "1.9.1"
|
||||
|
||||
const val androidx_appcompat = "1.2.0-rc01"
|
||||
|
@ -28,7 +27,6 @@ object Versions {
|
|||
const val androidx_paging = "2.1.0"
|
||||
const val androidx_transition = "1.3.0"
|
||||
const val androidx_work = "2.2.0"
|
||||
const val androidx_dynamic_animation = "1.0.0"
|
||||
const val google_material = "1.1.0"
|
||||
const val google_flexbox = "2.0.1"
|
||||
|
||||
|
@ -59,7 +57,6 @@ object Deps {
|
|||
|
||||
const val allopen = "org.jetbrains.kotlin:kotlin-allopen:${Versions.kotlin}"
|
||||
const val osslicenses_plugin = "com.google.android.gms:oss-licenses-plugin:${Versions.osslicenses_plugin}"
|
||||
const val osslicenses_library = "com.google.android.gms:play-services-oss-licenses:${Versions.osslicenses_library}"
|
||||
|
||||
const val mozilla_concept_engine = "org.mozilla.components:concept-engine:${Versions.mozilla_android_components}"
|
||||
const val mozilla_concept_menu = "org.mozilla.components:concept-menu:${Versions.mozilla_android_components}"
|
||||
|
@ -174,7 +171,6 @@ object Deps {
|
|||
const val androidx_recyclerview = "androidx.recyclerview:recyclerview:${Versions.androidx_recyclerview}"
|
||||
const val androidx_core = "androidx.core:core:${Versions.androidx_core}"
|
||||
const val androidx_core_ktx = "androidx.core:core-ktx:${Versions.androidx_core}"
|
||||
const val androidx_dynamic_animation = "androidx.dynamicanimation:dynamicanimation:${Versions.androidx_dynamic_animation}"
|
||||
const val androidx_transition = "androidx.transition:transition:${Versions.androidx_transition}"
|
||||
const val androidx_work_ktx = "androidx.work:work-runtime-ktx:${Versions.androidx_work}"
|
||||
const val androidx_work_testing = "androidx.work:work-testing:${Versions.androidx_work}"
|
||||
|
|
10
docs/mma.md
10
docs/mma.md
|
@ -159,6 +159,16 @@ User Attributes
|
|||
<td>A boolean indicating that this is a Fenix installation</td>
|
||||
<td><a href="https://github.com/mozilla-mobile/fenix/pull/8208">#8208</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`installed_addons`</td>
|
||||
<td>A boolean indicating that there are addons installed</td>
|
||||
<td><a href="https://github.com/mozilla-mobile/fenix/pull/13233">#13233</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`enabled_addons`</td>
|
||||
<td>A boolean indicating that there are addons enabled</td>
|
||||
<td><a href="https://github.com/mozilla-mobile/fenix/pull/13233">#13233</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
Events
|
||||
|
|
Loading…
Reference in New Issue