summaryrefslogtreecommitdiffstats
path: root/mobile/android/android-components/components/ui/widgets/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'mobile/android/android-components/components/ui/widgets/src/main')
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/AndroidManifest.xml4
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/java/mozilla/components/ui/widgets/Extentions.kt37
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/java/mozilla/components/ui/widgets/SnackbarDelegate.kt55
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/java/mozilla/components/ui/widgets/VerticalSwipeRefreshLayout.kt216
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/java/mozilla/components/ui/widgets/WidgetSiteItemView.kt106
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/java/mozilla/components/ui/widgets/behavior/BrowserGestureDetector.kt192
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/java/mozilla/components/ui/widgets/behavior/EngineViewClippingBehavior.kt90
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/java/mozilla/components/ui/widgets/behavior/EngineViewScrollingBehavior.kt237
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/java/mozilla/components/ui/widgets/behavior/ViewYTranslationStrategy.kt189
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/java/mozilla/components/ui/widgets/behavior/ViewYTranslator.kt81
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/drawable/mozac_widget_favicon_background.xml17
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/drawable/rounded_button_background.xml18
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/layout/mozac_widget_site_item.xml77
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-am/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-ar/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-ast/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-azb/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-be/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-bg/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-br/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-bs/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-ca/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-cak/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-co/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-cs/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-cy/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-da/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-de/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-dsb/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-el/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-en-rCA/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-en-rGB/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-eo/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-es-rAR/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-es-rCL/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-es-rES/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-es-rMX/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-es/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-et/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-eu/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-fa/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-fi/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-fr/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-fur/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-fy-rNL/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-gd/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-gl/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-gn/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-hr/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-hsb/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-hu/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-hy-rAM/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-ia/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-in/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-is/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-it/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-iw/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-ja/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-ka/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-kaa/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-kab/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-kk/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-kmr/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-ko/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-lo/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-nb-rNO/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-nl/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-nn-rNO/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-oc/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-pa-rIN/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-pa-rPK/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-pl/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-pt-rBR/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-pt-rPT/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-rm/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-ro/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-ru/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-sat/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-sc/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-si/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-sk/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-skr/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-sl/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-sq/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-sr/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-su/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-sv-rSE/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-tg/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-th/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-tr/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-trs/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-tt/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-ug/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-uk/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-vi/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-zh-rCN/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values-zh-rTW/strings.xml5
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values/attrs.xml19
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values/colors.xml10
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values/dimens.xml14
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values/strings.xml8
-rw-r--r--mobile/android/android-components/components/ui/widgets/src/main/res/values/styles.xml63
102 files changed, 1853 insertions, 0 deletions
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/AndroidManifest.xml b/mobile/android/android-components/components/ui/widgets/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..e16cda1d34
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+<!-- 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/. -->
+<manifest />
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/java/mozilla/components/ui/widgets/Extentions.kt b/mobile/android/android-components/components/ui/widgets/src/main/java/mozilla/components/ui/widgets/Extentions.kt
new file mode 100644
index 0000000000..fb17b7eb5a
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/java/mozilla/components/ui/widgets/Extentions.kt
@@ -0,0 +1,37 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package mozilla.components.ui.widgets
+
+import android.widget.TextView
+import android.view.View.TEXT_ALIGNMENT_CENTER as CENTER
+
+/**
+ * A shortcut to align buttons' text to center inside AlertDialog.
+ */
+fun androidx.appcompat.app.AlertDialog.withCenterAlignedButtons(): androidx.appcompat.app.AlertDialog {
+ findViewById<TextView>(android.R.id.button1)?.let { it.textAlignment = CENTER }
+ findViewById<TextView>(android.R.id.button2)?.let { it.textAlignment = CENTER }
+ findViewById<TextView>(android.R.id.button3)?.let { it.textAlignment = CENTER }
+ return this
+}
+
+/**
+ * A shortcut to align buttons' text to center inside AlertDialog.
+ *
+ * Important: On Android API levels lower than 24, this method must be called only AFTER the dialog
+ * has been shown. Calling this method prior to displaying the dialog on those API levels will cause
+ * partial initialization of the view, leading to a crash.
+ *
+ * Usage example:
+ * dialog.setOnShowListener {
+ * dialog.withCenterAlignedButtons()
+ * }
+ */
+fun android.app.AlertDialog.withCenterAlignedButtons(): android.app.AlertDialog {
+ findViewById<TextView>(android.R.id.button1)?.let { it.textAlignment = CENTER }
+ findViewById<TextView>(android.R.id.button2)?.let { it.textAlignment = CENTER }
+ findViewById<TextView>(android.R.id.button3)?.let { it.textAlignment = CENTER }
+ return this
+}
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/java/mozilla/components/ui/widgets/SnackbarDelegate.kt b/mobile/android/android-components/components/ui/widgets/src/main/java/mozilla/components/ui/widgets/SnackbarDelegate.kt
new file mode 100644
index 0000000000..8e2dd2fad7
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/java/mozilla/components/ui/widgets/SnackbarDelegate.kt
@@ -0,0 +1,55 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package mozilla.components.ui.widgets
+
+import android.view.View
+import com.google.android.material.snackbar.Snackbar
+
+/**
+ * Delegate to display a snackbar.
+ */
+interface SnackbarDelegate {
+ /**
+ * Displays a snackbar.
+ *
+ * @param snackBarParentView The view to find a parent from for displaying the Snackbar.
+ * @param text The text to show. Can be formatted text.
+ * @param duration How long to display the message.
+ * @param action String resource to display for the action.
+ * @param listener callback to be invoked when the action is clicked.
+ */
+ fun show(
+ snackBarParentView: View,
+ text: Int,
+ duration: Int,
+ action: Int = 0,
+ listener: ((v: View) -> Unit)? = null,
+ )
+}
+
+/**
+ * Default implementation for [SnackbarDelegate]. Will display a standard default Snackbar.
+ */
+class DefaultSnackbarDelegate : SnackbarDelegate {
+ override fun show(
+ snackBarParentView: View,
+ text: Int,
+ duration: Int,
+ action: Int,
+ listener: ((v: View) -> Unit)?,
+ ) {
+ val snackbar = Snackbar.make(
+ snackBarParentView,
+ text,
+ duration,
+ )
+
+ if (action != 0 && listener != null) {
+ snackbar.setAction(action, listener)
+ }
+
+ snackbar.show()
+ }
+}
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/java/mozilla/components/ui/widgets/VerticalSwipeRefreshLayout.kt b/mobile/android/android-components/components/ui/widgets/src/main/java/mozilla/components/ui/widgets/VerticalSwipeRefreshLayout.kt
new file mode 100644
index 0000000000..aa8bc10b67
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/java/mozilla/components/ui/widgets/VerticalSwipeRefreshLayout.kt
@@ -0,0 +1,216 @@
+/* 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 mozilla.components.ui.widgets
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.util.AttributeSet
+import android.view.MotionEvent
+import android.view.View
+import android.view.ViewConfiguration
+import androidx.annotation.VisibleForTesting
+import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
+import kotlin.math.abs
+
+/**
+ * [SwipeRefreshLayout] that filters only vertical scrolls for triggering pull to refresh.
+ *
+ * Following situations will not trigger pull to refresh:
+ * - a scroll happening more on the horizontal axis
+ * - a scale in/out gesture
+ * - a quick scale gesture
+ *
+ * To control responding to scrolls and showing the pull to refresh throbber or not
+ * use the [View.isEnabled] property.
+ */
+class VerticalSwipeRefreshLayout @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+) : SwipeRefreshLayout(context, attrs) {
+ @VisibleForTesting
+ internal var isQuickScaleInProgress = false
+
+ @VisibleForTesting
+ internal var quickScaleEvents = QuickScaleEvents()
+ private var previousX = 0f
+ private var previousY = 0f
+ private val doubleTapTimeout = ViewConfiguration.getDoubleTapTimeout()
+ private val doubleTapSlop = ViewConfiguration.get(context).scaledDoubleTapSlop
+ private val doubleTapSlopSquare = doubleTapSlop * doubleTapSlop
+
+ @VisibleForTesting
+ internal var hadMultiTouch: Boolean = false
+
+ @VisibleForTesting
+ internal var disallowInterceptTouchEvent = false
+
+ @Suppress("ComplexMethod", "ReturnCount")
+ override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
+ // Setting "isEnabled = false" is recommended for users of this ViewGroup
+ // who who are not interested in the pull to refresh functionality
+ // Setting this easily avoids executing code unneededsly before the check for "canChildScrollUp".
+ if (!isEnabled || disallowInterceptTouchEvent) {
+ return false
+ }
+
+ if (MotionEvent.ACTION_DOWN == event.action) {
+ hadMultiTouch = false
+ }
+
+ // Layman's scale gesture (with two fingers) detector.
+ // Allows for quick, serial inference as opposed to using ScaleGestureDetector
+ // which uses callbacks and would be hard to synchronize in the little time we have.
+ if (event.pointerCount > 1 || hadMultiTouch) {
+ hadMultiTouch = true
+ return false
+ }
+
+ val eventAction = event.action
+
+ // Cleanup if the gesture has been aborted or quick scale just ended/
+ if (MotionEvent.ACTION_CANCEL == eventAction ||
+ (MotionEvent.ACTION_UP == eventAction && isQuickScaleInProgress)
+ ) {
+ forgetQuickScaleEvents()
+ return callSuperOnInterceptTouchEvent(event)
+ }
+
+ // Disable pull to refresh if quick scale is in progress.
+ maybeAddDoubleTapEvent(event)
+ if (isQuickScaleInProgress(quickScaleEvents)) {
+ isQuickScaleInProgress = true
+ return false
+ }
+
+ // Disable pull to refresh if the move was more on the X axis.
+ if (MotionEvent.ACTION_DOWN == eventAction) {
+ previousX = event.x
+ previousY = event.y
+ } else if (MotionEvent.ACTION_MOVE == eventAction) {
+ val xDistance = abs(event.x - previousX)
+ val yDistance = abs(event.y - previousY)
+ previousX = event.x
+ previousY = event.y
+ if (xDistance > yDistance) {
+ return false
+ }
+ }
+
+ return callSuperOnInterceptTouchEvent(event)
+ }
+
+ override fun onStartNestedScroll(child: View, target: View, nestedScrollAxes: Int): Boolean {
+ // Ignoring nested scrolls from descendants.
+ // Allowing descendants to trigger nested scrolls would defeat the purpose of this class
+ // and result in pull to refresh to happen for all movements on the Y axis
+ // (even as part of scale/quick scale gestures) while also doubling the throbber with the overscroll shadow.
+ return if (isEnabled) {
+ return false
+ } else {
+ callSuperOnStartNestedScroll(child, target, nestedScrollAxes)
+ }
+ }
+
+ @SuppressLint("Recycle") // we do recycle the events in forgetQuickScaleEvents()
+ @VisibleForTesting
+ internal fun maybeAddDoubleTapEvent(event: MotionEvent) {
+ val currentEventAction = event.action
+
+ // A double tap event must follow the order:
+ // ACTION_DOWN - ACTION_UP - ACTION_DOWN
+ // all these events happening in an interval defined by a system constant - DOUBLE_TAP_TIMEOUT
+
+ if (MotionEvent.ACTION_DOWN == currentEventAction) {
+ if (quickScaleEvents.upEvent != null) {
+ if (event.eventTime - quickScaleEvents.upEvent!!.eventTime > doubleTapTimeout) {
+ // Too much time passed for the MotionEvents sequence to be considered
+ // a quick scale gesture. Restart counting.
+ forgetQuickScaleEvents()
+ quickScaleEvents.firstDownEvent = MotionEvent.obtain(event)
+ } else {
+ quickScaleEvents.secondDownEvent = MotionEvent.obtain(event)
+ }
+ } else {
+ // This may be the first time the user touches the screen or
+ // the gesture was not finished with ACTION_UP.
+ forgetQuickScaleEvents()
+ quickScaleEvents.firstDownEvent = MotionEvent.obtain(event)
+ }
+ }
+ // For the double tap events series we need ACTION_DOWN first
+ // and then ACTION_UP second.
+ else if (MotionEvent.ACTION_UP == currentEventAction && quickScaleEvents.firstDownEvent != null) {
+ quickScaleEvents.upEvent = MotionEvent.obtain(event)
+ }
+ }
+
+ override fun requestDisallowInterceptTouchEvent(b: Boolean) {
+ // We need to disable Pull to Refresh on this layout be we don't want to propagate the
+ // request to the parent, because they may use the gesture for other purpose, like
+ // propagating it to ToolbarBehavior
+ this.disallowInterceptTouchEvent = b
+ }
+
+ @VisibleForTesting
+ internal fun forgetQuickScaleEvents() {
+ quickScaleEvents.firstDownEvent?.recycle()
+ quickScaleEvents.upEvent?.recycle()
+ quickScaleEvents.secondDownEvent?.recycle()
+ quickScaleEvents.firstDownEvent = null
+ quickScaleEvents.upEvent = null
+ quickScaleEvents.secondDownEvent = null
+
+ isQuickScaleInProgress = false
+ }
+
+ @VisibleForTesting
+ internal fun isQuickScaleInProgress(events: QuickScaleEvents): Boolean {
+ return if (events.isNotNull()) {
+ isQuickScaleInProgress(events.firstDownEvent!!, events.upEvent!!, events.secondDownEvent!!)
+ } else {
+ false
+ }
+ }
+
+ // Method closely following GestureDetectorCompat#isConsideredDoubleTap.
+ // Allows for serial inference of double taps as opposed to using callbacks.
+ @VisibleForTesting
+ internal fun isQuickScaleInProgress(
+ firstDown: MotionEvent,
+ firstUp: MotionEvent,
+ secondDown: MotionEvent,
+ ): Boolean {
+ if (secondDown.eventTime - firstUp.eventTime > doubleTapTimeout) {
+ return false
+ }
+
+ val deltaX = firstDown.x.toInt() - secondDown.x.toInt()
+ val deltaY = firstDown.y.toInt() - secondDown.y.toInt()
+
+ return deltaX * deltaX + deltaY * deltaY < doubleTapSlopSquare
+ }
+
+ @VisibleForTesting
+ internal fun callSuperOnInterceptTouchEvent(event: MotionEvent) =
+ super.onInterceptTouchEvent(event)
+
+ @VisibleForTesting
+ internal fun callSuperOnStartNestedScroll(child: View, target: View, nestedScrollAxes: Int) =
+ super.onStartNestedScroll(child, target, nestedScrollAxes)
+
+ private fun QuickScaleEvents.isNotNull(): Boolean {
+ return firstDownEvent != null && upEvent != null && secondDownEvent != null
+ }
+
+ /**
+ * Wrapper over the MotionEvents that compose a quickScale gesture.
+ */
+ @VisibleForTesting
+ internal data class QuickScaleEvents(
+ var firstDownEvent: MotionEvent? = null,
+ var upEvent: MotionEvent? = null,
+ var secondDownEvent: MotionEvent? = null,
+ )
+}
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/java/mozilla/components/ui/widgets/WidgetSiteItemView.kt b/mobile/android/android-components/components/ui/widgets/src/main/java/mozilla/components/ui/widgets/WidgetSiteItemView.kt
new file mode 100644
index 0000000000..0eca198957
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/java/mozilla/components/ui/widgets/WidgetSiteItemView.kt
@@ -0,0 +1,106 @@
+/* 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 mozilla.components.ui.widgets
+
+import android.content.Context
+import android.graphics.drawable.Drawable
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import android.view.View
+import android.widget.FrameLayout
+import android.widget.ImageButton
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.annotation.DrawableRes
+import androidx.annotation.StringRes
+import androidx.appcompat.content.res.AppCompatResources.getDrawable
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.core.view.isVisible
+
+/**
+ * Shared UI widget for showing a website in a list of websites,
+ * such as in bookmarks, history, site exceptions, or collections.
+ */
+class WidgetSiteItemView @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0,
+) : ConstraintLayout(context, attrs, defStyleAttr) {
+
+ private val labelView: TextView by lazy { findViewById<TextView>(R.id.label) }
+ private val captionView: TextView by lazy { findViewById<TextView>(R.id.caption) }
+ private val iconWrapper: FrameLayout by lazy { findViewById<FrameLayout>(R.id.favicon_wrapper) }
+ private val secondaryButton: ImageButton by lazy { findViewById<ImageButton>(R.id.secondary_button) }
+
+ /**
+ * ImageView that should display favicons.
+ */
+ val iconView: ImageView by lazy { findViewById<ImageView>(R.id.favicon) }
+
+ init {
+ LayoutInflater.from(context).inflate(R.layout.mozac_widget_site_item, this, true)
+ }
+
+ /**
+ * Sets the text displayed inside of the site item view.
+ *
+ * @param label Main label text, such as a site title.
+ * @param caption Sub caption text, such as a URL. If null, the caption is hidden.
+ */
+ fun setText(label: CharSequence, caption: CharSequence?) {
+ labelView.text = label
+ captionView.text = caption
+ captionView.isVisible = caption != null
+ }
+
+ /**
+ * Add a view that will overlay the favicon, such as a checkmark.
+ */
+ fun addIconOverlay(overlay: View) {
+ iconWrapper.addView(overlay)
+ }
+
+ /**
+ * Add a secondary button, such as an overflow menu.
+ *
+ * @param icon Drawable to display in the button.
+ * @param contentDescription Accessible description of the button's purpose.
+ * @param onClickListener Listener called when the button is clicked.
+ */
+ fun setSecondaryButton(
+ icon: Drawable?,
+ contentDescription: CharSequence,
+ onClickListener: (View) -> Unit,
+ ) {
+ secondaryButton.isVisible = true
+ secondaryButton.setImageDrawable(icon)
+ secondaryButton.contentDescription = contentDescription
+ secondaryButton.setOnClickListener(onClickListener)
+ }
+
+ /**
+ * Add a secondary button, such as an overflow menu.
+ *
+ * @param icon Drawable to display in the button.
+ * @param contentDescription Accessible description of the button's purpose.
+ * @param onClickListener Listener called when the button is clicked.
+ */
+ fun setSecondaryButton(
+ @DrawableRes icon: Int,
+ @StringRes contentDescription: Int,
+ onClickListener: (View) -> Unit,
+ ) = setSecondaryButton(
+ icon = getDrawable(context, icon),
+ contentDescription = context.getString(contentDescription),
+ onClickListener = onClickListener,
+ )
+
+ /**
+ * Removes the secondary button if it was previously set in [setSecondaryButton].
+ */
+ fun removeSecondaryButton() {
+ secondaryButton.isVisible = false
+ }
+}
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/java/mozilla/components/ui/widgets/behavior/BrowserGestureDetector.kt b/mobile/android/android-components/components/ui/widgets/src/main/java/mozilla/components/ui/widgets/behavior/BrowserGestureDetector.kt
new file mode 100644
index 0000000000..b15df65078
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/java/mozilla/components/ui/widgets/behavior/BrowserGestureDetector.kt
@@ -0,0 +1,192 @@
+/* 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 mozilla.components.ui.widgets.behavior
+
+import android.content.Context
+import android.view.GestureDetector
+import android.view.MotionEvent
+import android.view.ScaleGestureDetector
+import androidx.annotation.VisibleForTesting
+import mozilla.components.concept.base.crash.CrashReporting
+import kotlin.math.abs
+
+/**
+ * Wraps exceptions that are caught by [BrowserGestureDetector].
+ * Instances of this class are submitted via [CrashReporting]. This wrapping helps easily identify
+ * exceptions related to [BrowserGestureDetector].
+ */
+internal class BrowserGestureDetectorException(e: Throwable) : Throwable(e)
+
+/**
+ * Custom [MotionEvent] gestures detector with scroll / zoom callbacks.
+ *
+ * Favors zoom gestures in detriment of the scroll gestures with:
+ * - higher sensitivity for multi-finger zoom gestures
+ * - ignoring scrolls if zoom is in progress
+ *
+ * @param applicationContext context used for registering internal gesture listeners.
+ * @param listener client interested in zoom / scroll events.
+ */
+internal class BrowserGestureDetector(
+ applicationContext: Context,
+ listener: GesturesListener,
+ private val crashReporting: CrashReporting? = null,
+) {
+ @VisibleForTesting
+ internal var gestureDetector = GestureDetector(
+ applicationContext,
+ CustomScrollDetectorListener { previousEvent: MotionEvent?, currentEvent: MotionEvent, distanceX, distanceY ->
+ run {
+ listener.onScroll?.invoke(distanceX, distanceY)
+
+ // We got many crashes because of the initial event - ACTION_DOWN being null.
+ // Investigations to be continued in android-components/issues/8552.
+ // In the meantime we'll protect against this with a simple null check.
+ if (previousEvent != null) {
+ if (abs(currentEvent.y - previousEvent.y) >= abs(currentEvent.x - previousEvent.x)) {
+ listener.onVerticalScroll?.invoke(distanceY)
+ } else {
+ listener.onHorizontalScroll?.invoke(distanceX)
+ }
+ }
+ }
+ },
+ )
+
+ @VisibleForTesting
+ internal var scaleGestureDetector = ScaleGestureDetector(
+ applicationContext,
+ CustomScaleDetectorListener(
+ listener.onScaleBegin ?: {},
+ listener.onScale ?: {},
+ listener.onScaleEnd ?: {},
+ ),
+ )
+
+ /**
+ * Accepts MotionEvents and dispatches zoom / scroll events to the registered listener when appropriate.
+ *
+ * Applications should pass a complete and consistent event stream to this method.
+ * A complete and consistent event stream involves all MotionEvents from the initial ACTION_DOWN
+ * to the final ACTION_UP or ACTION_CANCEL.
+ *
+ * @return if the event was handled by any of the registered detectors
+ */
+ @Suppress("ComplexCondition")
+ internal fun handleTouchEvent(event: MotionEvent): Boolean {
+ val eventAction = event.actionMasked
+
+ // A double tap for a quick scale gesture (quick double tap followed by a drag)
+ // would trigger a ACTION_CANCEL event before the MOVE_EVENT.
+ // This would prevent the scale detector from properly inferring the movement.
+ // We'll want to ignore ACTION_CANCEL but process the next stream of events.
+ if (eventAction != MotionEvent.ACTION_CANCEL) {
+ scaleGestureDetector.onTouchEvent(event)
+ }
+
+ // Ignore scrolling if zooming is already in progress.
+ // Always pass motion begin / end events just to have the detector ready
+ // to infer scrolls when the scale gesture ended.
+ return if (!scaleGestureDetector.isInProgress ||
+ eventAction == MotionEvent.ACTION_DOWN ||
+ eventAction == MotionEvent.ACTION_UP ||
+ eventAction == MotionEvent.ACTION_CANCEL
+ ) {
+ @Suppress("TooGenericExceptionCaught")
+ try {
+ gestureDetector.onTouchEvent(event)
+ } catch (e: Exception) {
+ crashReporting?.submitCaughtException(BrowserGestureDetectorException(e))
+ false
+ }
+ } else {
+ false
+ }
+ }
+
+ /**
+ * A convenience containing listeners for zoom / scroll events
+ *
+ * Provide implementation for the events you are interested in.
+ * The others will be no-op.
+ */
+ internal class GesturesListener(
+ /**
+ * Responds to scroll events for a gesture in progress.
+ * The distance in x and y is also supplied for convenience.
+ */
+ val onScroll: ((distanceX: Float, distanceY: Float) -> Unit)? = { _, _ -> run {} },
+
+ /**
+ * Responds to an in progress scroll occuring more on the vertical axis.
+ * The scroll distance is also supplied for convenience.
+ */
+ val onVerticalScroll: ((distance: Float) -> Unit)? = {},
+
+ /**
+ * Responds to an in progress scroll occurring more on the horizontal axis.
+ * The scroll distance is also supplied for convenience.
+ */
+ val onHorizontalScroll: ((distance: Float) -> Unit)? = {},
+
+ /**
+ * Responds to the the beginning of a new scale gesture.
+ * Reported by new pointers going down.
+ */
+ val onScaleBegin: ((scaleFactor: Float) -> Unit)? = {},
+
+ /**
+ * Responds to scaling events for a gesture in progress.
+ * The scaling factor is also supplied for convenience.
+ * This value is represents the difference from the previous scale event to the current event.
+ */
+ val onScale: ((scaleFactor: Float) -> Unit)? = {},
+
+ /**
+ * Responds to the end of a scale gesture.
+ * Reported by existing pointers going up.
+ */
+ val onScaleEnd: ((scaleFactor: Float) -> Unit)? = {},
+ )
+
+ private class CustomScrollDetectorListener(
+ val onScrolling: (
+ previousEvent: MotionEvent?,
+ currentEvent: MotionEvent,
+ distanceX: Float,
+ distanceY: Float,
+ ) -> Unit,
+ ) : GestureDetector.SimpleOnGestureListener() {
+ override fun onScroll(
+ e1: MotionEvent?,
+ e2: MotionEvent,
+ distanceX: Float,
+ distanceY: Float,
+ ): Boolean {
+ onScrolling(e1, e2, distanceX, distanceY)
+ return true
+ }
+ }
+
+ private class CustomScaleDetectorListener(
+ val onScaleBegin: (scaleFactor: Float) -> Unit = {},
+ val onScale: (scaleFactor: Float) -> Unit = {},
+ val onScaleEnd: (scaleFactor: Float) -> Unit = {},
+ ) : ScaleGestureDetector.SimpleOnScaleGestureListener() {
+ override fun onScaleBegin(detector: ScaleGestureDetector): Boolean {
+ onScaleBegin(detector.scaleFactor)
+ return true
+ }
+
+ override fun onScale(detector: ScaleGestureDetector): Boolean {
+ onScale(detector.scaleFactor)
+ return true
+ }
+
+ override fun onScaleEnd(detector: ScaleGestureDetector) {
+ onScaleEnd(detector.scaleFactor)
+ }
+ }
+}
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/java/mozilla/components/ui/widgets/behavior/EngineViewClippingBehavior.kt b/mobile/android/android-components/components/ui/widgets/src/main/java/mozilla/components/ui/widgets/behavior/EngineViewClippingBehavior.kt
new file mode 100644
index 0000000000..d0f532bfbc
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/java/mozilla/components/ui/widgets/behavior/EngineViewClippingBehavior.kt
@@ -0,0 +1,90 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package mozilla.components.ui.widgets.behavior
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import androidx.annotation.VisibleForTesting
+import androidx.coordinatorlayout.widget.CoordinatorLayout
+import mozilla.components.concept.engine.EngineView
+import mozilla.components.concept.toolbar.ScrollableToolbar
+import mozilla.components.support.ktx.android.view.findViewInHierarchy
+import kotlin.math.roundToInt
+
+/**
+ * A [CoordinatorLayout.Behavior] implementation that allows the [EngineView] to automatically
+ * size itself in relation to the Y translation of the [ScrollableToolbar].
+ *
+ * This is useful for dynamic [ScrollableToolbar]s ensuring the web content is displayed immediately
+ * below / above the toolbar even when that is animated.
+ *
+ * @param context [Context] used for various Android interactions
+ * @param attrs XML set attributes configuring this
+ * @param engineViewParent NestedScrollingChild parent of the [EngineView]
+ * @param toolbarHeight size of [ScrollableToolbar] when it is placed above the [EngineView]
+ * @param toolbarPosition whether the [ScrollableToolbar] is placed above or below the [EngineView]
+ */
+class EngineViewClippingBehavior(
+ context: Context?,
+ attrs: AttributeSet?,
+ engineViewParent: View,
+ toolbarHeight: Int,
+ toolbarPosition: ToolbarPosition,
+) : CoordinatorLayout.Behavior<View>(context, attrs) {
+
+ @VisibleForTesting
+ internal val engineView = engineViewParent.findViewInHierarchy { it is EngineView } as EngineView?
+
+ @VisibleForTesting
+ internal var toolbarChangedAction: (Float) -> Unit?
+ private val bottomToolbarChangedAction = { newToolbarTranslationY: Float ->
+ if (!newToolbarTranslationY.isNaN()) {
+ engineView?.setVerticalClipping(-newToolbarTranslationY.roundToInt())
+ }
+ }
+ private val topToolbarChangedAction = { newToolbarTranslationY: Float ->
+ // the top toolbar is translated upwards when collapsing-> all values received are 0 or negative
+ engineView?.let {
+ it.setVerticalClipping(newToolbarTranslationY.roundToInt())
+ // Need to add the toolbarHeight to effectively place the engineView below the toolbar.
+ engineViewParent.translationY = newToolbarTranslationY + toolbarHeight
+ }
+ }
+
+ init {
+ toolbarChangedAction = if (toolbarPosition == ToolbarPosition.TOP) {
+ topToolbarChangedAction
+ } else {
+ bottomToolbarChangedAction
+ }
+ }
+
+ override fun layoutDependsOn(parent: CoordinatorLayout, child: View, dependency: View): Boolean {
+ if (dependency is ScrollableToolbar) {
+ return true
+ }
+
+ return super.layoutDependsOn(parent, child, dependency)
+ }
+
+ /**
+ * Apply vertical clipping to [EngineView]. This requires [EngineViewClippingBehavior] to be set
+ * in/on the [EngineView] or its parent. Must be a direct descending child of [CoordinatorLayout].
+ */
+ override fun onDependentViewChanged(parent: CoordinatorLayout, child: View, dependency: View): Boolean {
+ toolbarChangedAction.invoke(dependency.translationY)
+
+ return true
+ }
+}
+
+/**
+ * Where the toolbar is placed on the screen.
+ */
+enum class ToolbarPosition {
+ TOP,
+ BOTTOM,
+}
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/java/mozilla/components/ui/widgets/behavior/EngineViewScrollingBehavior.kt b/mobile/android/android-components/components/ui/widgets/src/main/java/mozilla/components/ui/widgets/behavior/EngineViewScrollingBehavior.kt
new file mode 100644
index 0000000000..08da7e5064
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/java/mozilla/components/ui/widgets/behavior/EngineViewScrollingBehavior.kt
@@ -0,0 +1,237 @@
+/* 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 mozilla.components.ui.widgets.behavior
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.MotionEvent
+import android.view.View
+import androidx.annotation.VisibleForTesting
+import androidx.coordinatorlayout.widget.CoordinatorLayout
+import androidx.core.view.ViewCompat
+import mozilla.components.concept.base.crash.CrashReporting
+import mozilla.components.concept.engine.EngineView
+import mozilla.components.support.ktx.android.view.findViewInHierarchy
+
+/**
+ * Where the view is placed on the screen.
+ */
+enum class ViewPosition {
+ TOP,
+ BOTTOM,
+}
+
+/**
+ * A [CoordinatorLayout.Behavior] implementation to be used when placing [View] at the bottom of the screen.
+ *
+ * This is safe to use even if the [View] may be added / removed from a parent layout later
+ * or if it could have Visibility.GONE set.
+ *
+ * This implementation will:
+ * - Show/Hide the [View] automatically when scrolling vertically.
+ * - Snap the [View] to be hidden or visible when the user stops scrolling.
+ */
+class EngineViewScrollingBehavior(
+ val context: Context?,
+ attrs: AttributeSet?,
+ private val viewPosition: ViewPosition,
+ private val crashReporting: CrashReporting? = null,
+) : CoordinatorLayout.Behavior<View>(context, attrs) {
+ // This implementation is heavily based on this blog article:
+ // https://android.jlelse.eu/scroll-your-bottom-navigation-view-away-with-10-lines-of-code-346f1ed40e9e
+
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ internal var shouldSnapAfterScroll: Boolean = false
+
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ internal var startedScroll = false
+
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ internal var isScrollEnabled = false
+
+ /**
+ * Reference to [EngineView] used to check user's [android.view.MotionEvent]s.
+ */
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ internal var engineView: EngineView? = null
+
+ /**
+ * Reference to the actual [View] that we'll animate.
+ */
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ internal var dynamicScrollView: View? = null
+
+ /**
+ * Depending on how user's touch was consumed by EngineView / current website,
+ *
+ * we will animate the dynamic navigation bar if:
+ * - touches were used for zooming / panning operations in the website.
+ *
+ * We will do nothing if:
+ * - the website is not scrollable
+ * - the website handles the touch events itself through it's own touch event listeners.
+ */
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ internal val shouldScroll: Boolean
+ get() = engineView?.getInputResultDetail()?.let {
+ (it.canScrollToBottom() || it.canScrollToTop()) && isScrollEnabled
+ } ?: false
+
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ internal var gesturesDetector: BrowserGestureDetector = createGestureDetector()
+
+ @VisibleForTesting
+ internal var yTranslator: ViewYTranslator = createYTranslationStrategy()
+
+ private fun createYTranslationStrategy() = ViewYTranslator(viewPosition)
+
+ override fun onStartNestedScroll(
+ coordinatorLayout: CoordinatorLayout,
+ child: View,
+ directTargetChild: View,
+ target: View,
+ axes: Int,
+ type: Int,
+ ): Boolean {
+ return if (dynamicScrollView != null) {
+ startNestedScroll(axes, type, child)
+ } else {
+ return false // not interested in subsequent scroll events
+ }
+ }
+
+ override fun onStopNestedScroll(
+ coordinatorLayout: CoordinatorLayout,
+ child: View,
+ target: View,
+ type: Int,
+ ) {
+ if (dynamicScrollView != null) {
+ stopNestedScroll(type, child)
+ }
+ }
+
+ override fun onInterceptTouchEvent(
+ parent: CoordinatorLayout,
+ child: View,
+ ev: MotionEvent,
+ ): Boolean {
+ if (dynamicScrollView != null) {
+ gesturesDetector.handleTouchEvent(ev)
+ }
+ return false // allow events to be passed to below listeners
+ }
+
+ override fun onLayoutChild(
+ parent: CoordinatorLayout,
+ child: View,
+ layoutDirection: Int,
+ ): Boolean {
+ dynamicScrollView = child
+ engineView = parent.findViewInHierarchy { it is EngineView } as? EngineView
+
+ return super.onLayoutChild(parent, child, layoutDirection)
+ }
+
+ /**
+ * Used to expand the [View]
+ */
+ fun forceExpand(view: View) {
+ yTranslator.expandWithAnimation(view)
+ }
+
+ /**
+ * Used to collapse the [View]
+ */
+ fun forceCollapse(view: View) {
+ yTranslator.collapseWithAnimation(view)
+ }
+
+ /**
+ * Allow this view to be animated.
+ *
+ * @see disableScrolling
+ */
+ fun enableScrolling() {
+ isScrollEnabled = true
+ }
+
+ /**
+ * Disable scrolling of the view irrespective of the intrinsic checks.
+ *
+ * @see enableScrolling
+ */
+ fun disableScrolling() {
+ isScrollEnabled = false
+ }
+
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ internal fun tryToScrollVertically(distance: Float) {
+ dynamicScrollView?.let { view ->
+ if (shouldScroll && startedScroll) {
+ yTranslator.translate(view, distance)
+ } else if (engineView?.getInputResultDetail()?.isTouchHandlingUnknown() == false) {
+ // Force expand the view if the user scrolled up, it is not already expanded and
+ // an animation to expand it is not already in progress,
+ // otherwise the user could get stuck in a state where they cannot show the view
+ // See https://github.com/mozilla-mobile/android-components/issues/7101
+ yTranslator.forceExpandIfNotAlready(view, distance)
+ }
+ }
+ }
+
+ /**
+ * Helper function to ease testing.
+ * (Re)Initializes the [BrowserGestureDetector] in a new context.
+ *
+ * Useful in spied behaviors, to ensure callbacks are of the spy and not of the initially created object
+ * if the passed in argument is the result of [createGestureDetector].
+ */
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ internal fun initGesturesDetector(detector: BrowserGestureDetector) {
+ gesturesDetector = detector
+ }
+
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ internal fun createGestureDetector() =
+ BrowserGestureDetector(
+ context!!,
+ BrowserGestureDetector.GesturesListener(
+ onVerticalScroll = ::tryToScrollVertically,
+ onScaleBegin = {
+ // Scale shouldn't animate the view but a small y translation is still possible
+ // because of a previous scroll. Try to be swift about such an in progress animation.
+ yTranslator.snapImmediately(dynamicScrollView)
+ },
+ ),
+ crashReporting = crashReporting,
+ )
+
+ @VisibleForTesting
+ internal fun startNestedScroll(axes: Int, type: Int, view: View): Boolean {
+ return if (shouldScroll && axes == ViewCompat.SCROLL_AXIS_VERTICAL) {
+ startedScroll = true
+ shouldSnapAfterScroll = type == ViewCompat.TYPE_TOUCH
+ yTranslator.cancelInProgressTranslation()
+ true
+ } else if (engineView?.getInputResultDetail()?.isTouchUnhandled() == true) {
+ // Force expand the view if event is unhandled, otherwise user could get stuck in a
+ // state where they cannot show the view
+ yTranslator.cancelInProgressTranslation()
+ yTranslator.expandWithAnimation(view)
+ false
+ } else {
+ false
+ }
+ }
+
+ @VisibleForTesting
+ internal fun stopNestedScroll(type: Int, view: View) {
+ startedScroll = false
+ if (shouldSnapAfterScroll || type == ViewCompat.TYPE_NON_TOUCH) {
+ yTranslator.snapWithAnimation(view)
+ }
+ }
+}
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/java/mozilla/components/ui/widgets/behavior/ViewYTranslationStrategy.kt b/mobile/android/android-components/components/ui/widgets/src/main/java/mozilla/components/ui/widgets/behavior/ViewYTranslationStrategy.kt
new file mode 100644
index 0000000000..8311f2d21a
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/java/mozilla/components/ui/widgets/behavior/ViewYTranslationStrategy.kt
@@ -0,0 +1,189 @@
+/* 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 mozilla.components.ui.widgets.behavior
+
+import android.animation.ValueAnimator
+import android.view.View
+import android.view.animation.DecelerateInterpolator
+import androidx.annotation.VisibleForTesting
+import kotlin.math.max
+import kotlin.math.min
+
+@VisibleForTesting
+internal const val SNAP_ANIMATION_DURATION = 150L
+
+/**
+ * Helper class with methods for different behaviors for when translating a [View] on the Y axis.
+ */
+internal abstract class ViewYTranslationStrategy {
+ @VisibleForTesting
+ var animator = ValueAnimator().apply {
+ interpolator = DecelerateInterpolator()
+ duration = SNAP_ANIMATION_DURATION
+ }
+
+ /**
+ * Snap the [View] to be collapsed or expanded, depending on whatever state is closer
+ * over a short amount of time.
+ */
+ abstract fun snapWithAnimation(view: View)
+
+ /**
+ * Snap the [View] to be collapsed or expanded, depending on whatever state is closer immediately.
+ */
+ abstract fun snapImmediately(view: View?)
+
+ /**
+ * Translate the [View] to it's full visible height.
+ */
+ abstract fun expandWithAnimation(view: View)
+
+ /**
+ * Force expanding the [View] depending on the [distance] value that should be translated
+ * cancelling any other translation already in progress.
+ */
+ abstract fun forceExpandWithAnimation(view: View, distance: Float)
+
+ /**
+ * Translate the [View] to it's full 0 visible height.
+ */
+ abstract fun collapseWithAnimation(view: View)
+
+ /**
+ * Translate [view] immediately to the specified [distance] amount (positive or negative).
+ */
+ abstract fun translate(view: View, distance: Float)
+
+ /**
+ * Translate [view] to the indicated [targetTranslationY] vaue over a short amount of time.
+ */
+ open fun animateToTranslationY(view: View, targetTranslationY: Float) = with(animator) {
+ addUpdateListener { view.translationY = it.animatedValue as Float }
+ setFloatValues(view.translationY, targetTranslationY)
+ start()
+ }
+
+ /**
+ * Cancel any translation animations currently in progress.
+ */
+ fun cancelInProgressTranslation() = animator.cancel()
+}
+
+/**
+ * Helper class containing methods for translating a [View] on the Y axis
+ * between 0 and [View.getHeight]
+ */
+internal class BottomViewBehaviorStrategy : ViewYTranslationStrategy() {
+ @VisibleForTesting
+ internal var wasLastExpanding = false
+
+ override fun snapWithAnimation(view: View) {
+ if (view.translationY >= (view.height / 2f)) {
+ collapseWithAnimation(view)
+ } else {
+ expandWithAnimation(view)
+ }
+ }
+
+ override fun snapImmediately(view: View?) {
+ if (animator.isStarted) {
+ animator.end()
+ } else {
+ view?.apply {
+ translationY = if (translationY >= height / 2) {
+ height.toFloat()
+ } else {
+ 0f
+ }
+ }
+ }
+ }
+
+ override fun expandWithAnimation(view: View) {
+ animateToTranslationY(view, 0f)
+ }
+
+ override fun forceExpandWithAnimation(view: View, distance: Float) {
+ val shouldExpandToolbar = distance < 0
+ val isToolbarExpanded = view.translationY == 0f
+ if (shouldExpandToolbar && !isToolbarExpanded && !wasLastExpanding) {
+ animator.cancel()
+ expandWithAnimation(view)
+ }
+ }
+
+ override fun collapseWithAnimation(view: View) {
+ animateToTranslationY(view, view.height.toFloat())
+ }
+
+ override fun translate(view: View, distance: Float) {
+ view.translationY =
+ max(0f, min(view.height.toFloat(), view.translationY + distance))
+ }
+
+ override fun animateToTranslationY(view: View, targetTranslationY: Float) {
+ wasLastExpanding = targetTranslationY <= view.translationY
+ super.animateToTranslationY(view, targetTranslationY)
+ }
+}
+
+/**
+ * Helper class containing methods for translating a [View] on the Y axis
+ * between -[View.getHeight] and 0.
+ */
+internal class TopViewBehaviorStrategy : ViewYTranslationStrategy() {
+ @VisibleForTesting
+ internal var wasLastExpanding = false
+
+ override fun snapWithAnimation(view: View) {
+ if (view.translationY >= -(view.height / 2f)) {
+ expandWithAnimation(view)
+ } else {
+ collapseWithAnimation(view)
+ }
+ }
+
+ override fun snapImmediately(view: View?) {
+ if (animator.isStarted) {
+ animator.end()
+ } else {
+ view?.apply {
+ translationY = if (translationY >= -height / 2) {
+ 0f
+ } else {
+ -height.toFloat()
+ }
+ }
+ }
+ }
+
+ override fun expandWithAnimation(view: View) {
+ animateToTranslationY(view, 0f)
+ }
+
+ override fun forceExpandWithAnimation(view: View, distance: Float) {
+ val isExpandingInProgress = animator.isStarted && wasLastExpanding
+ val shouldExpandToolbar = distance < 0
+ val isToolbarExpanded = view.translationY == 0f
+ if (shouldExpandToolbar && !isToolbarExpanded && !isExpandingInProgress) {
+ animator.cancel()
+ expandWithAnimation(view)
+ }
+ }
+
+ override fun collapseWithAnimation(view: View) {
+ animateToTranslationY(view, -view.height.toFloat())
+ }
+
+ override fun translate(view: View, distance: Float) {
+ view.translationY =
+ min(0f, max(-view.height.toFloat(), view.translationY - distance))
+ }
+
+ override fun animateToTranslationY(view: View, targetTranslationY: Float) {
+ wasLastExpanding = targetTranslationY >= view.translationY
+ super.animateToTranslationY(view, targetTranslationY)
+ }
+}
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/java/mozilla/components/ui/widgets/behavior/ViewYTranslator.kt b/mobile/android/android-components/components/ui/widgets/src/main/java/mozilla/components/ui/widgets/behavior/ViewYTranslator.kt
new file mode 100644
index 0000000000..042b810b40
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/java/mozilla/components/ui/widgets/behavior/ViewYTranslator.kt
@@ -0,0 +1,81 @@
+/* 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 mozilla.components.ui.widgets.behavior
+
+import android.view.View
+import androidx.annotation.VisibleForTesting
+
+/**
+ * Helper class with methods for translating on the Y axis a top / bottom [View].
+ *
+ * @param viewPosition whether the view is displayed immediately at the top of the screen or
+ * immediately at the bottom. This affects how it will be translated:
+ * - if place at the bottom it will be Y translated between 0 and [View.getHeight]
+ * - if place at the top it will be Y translated between -[View.getHeight] and 0
+ */
+class ViewYTranslator(viewPosition: ViewPosition) {
+ @VisibleForTesting
+ internal var strategy = getTranslationStrategy(viewPosition)
+
+ /**
+ * Snap the [View] to be collapsed or expanded, depending on whatever state is closer
+ * over a short amount of time.
+ */
+ internal fun snapWithAnimation(view: View) {
+ strategy.snapWithAnimation(view)
+ }
+
+ /**
+ * Snap the [View] to be collapsed or expanded, depending on whatever state is closer immediately.
+ */
+ fun snapImmediately(view: View?) {
+ strategy.snapImmediately(view)
+ }
+
+ /**
+ * Translate the [View] to it's full visible height over a short amount of time.
+ */
+ internal fun expandWithAnimation(view: View) {
+ strategy.expandWithAnimation(view)
+ }
+
+ /**
+ * Translate the [View] to be hidden from view over a short amount of time.
+ */
+ internal fun collapseWithAnimation(view: View) {
+ strategy.collapseWithAnimation(view)
+ }
+
+ /**
+ * Force expanding the [View] depending on the [distance] value that should be translated
+ * cancelling any other translation already in progress.
+ */
+ fun forceExpandIfNotAlready(view: View, distance: Float) {
+ strategy.forceExpandWithAnimation(view, distance)
+ }
+
+ /**
+ * Translate [view] immediately to the specified [distance] amount (positive or negative).
+ */
+ fun translate(view: View, distance: Float) {
+ strategy.translate(view, distance)
+ }
+
+ /**
+ * Cancel any translation animations currently in progress.
+ */
+ fun cancelInProgressTranslation() {
+ strategy.cancelInProgressTranslation()
+ }
+
+ @VisibleForTesting
+ internal fun getTranslationStrategy(viewPosition: ViewPosition): ViewYTranslationStrategy {
+ return if (viewPosition == ViewPosition.TOP) {
+ TopViewBehaviorStrategy()
+ } else {
+ BottomViewBehaviorStrategy()
+ }
+ }
+}
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/drawable/mozac_widget_favicon_background.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/drawable/mozac_widget_favicon_background.xml
new file mode 100644
index 0000000000..93e6f01141
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/drawable/mozac_widget_favicon_background.xml
@@ -0,0 +1,17 @@
+<?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/. -->
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:shape="rectangle">
+ <corners android:radius="4dp" />
+ <solid
+ android:color="?mozac_widget_favicon_background_color"
+ tools:color="@color/photonWhite" />
+ <stroke
+ android:width="1dp"
+ android:color="?mozac_widget_favicon_border_color"
+ tools:color="@color/photonLightGrey30" />
+</shape>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/drawable/rounded_button_background.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/drawable/rounded_button_background.xml
new file mode 100644
index 0000000000..260a02530d
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/drawable/rounded_button_background.xml
@@ -0,0 +1,18 @@
+<?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/. -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="?android:attr/colorControlHighlight">
+ <item android:id="@android:id/mask">
+ <shape>
+ <solid android:color="#000000" />
+ <corners android:radius="4dp" />
+ </shape>
+ </item>
+ <item>
+ <shape android:shape="rectangle">
+ <corners android:radius="4dp" />
+ </shape>
+ </item>
+</ripple>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/layout/mozac_widget_site_item.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/layout/mozac_widget_site_item.xml
new file mode 100644
index 0000000000..3ddfd7a2c3
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/layout/mozac_widget_site_item.xml
@@ -0,0 +1,77 @@
+<?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/. -->
+<merge
+ 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"
+ tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/mozac_widget_site_item_height"
+ android:background="?android:attr/selectableItemBackground">
+
+ <FrameLayout
+ android:id="@+id/favicon_wrapper"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="16dp"
+ android:layout_marginEnd="16dp"
+ app:layout_constraintHorizontal_chainStyle="spread_inside"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/label">
+ <ImageView
+ android:id="@+id/favicon"
+ style="@style/Mozac.Widgets.Favicon"
+ android:importantForAccessibility="no"
+ tools:src="@android:drawable/ic_secure" />
+ </FrameLayout>
+
+ <TextView
+ android:id="@+id/label"
+ style="@style/Mozac.Widgets.SiteItem.Label"
+ android:layout_width="0dp"
+ tools:textColor="#20123A"
+ tools:text="Example site"
+ app:layout_goneMarginEnd="16dp"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toTopOf="@id/caption"
+ app:layout_constraintStart_toEndOf="@id/favicon_wrapper"
+ app:layout_constraintEnd_toStartOf="@id/secondary_button"
+ app:layout_constraintVertical_chainStyle="packed" />
+
+ <TextView
+ android:id="@+id/caption"
+ style="@style/Mozac.Widgets.SiteItem.Caption"
+ android:layout_width="0dp"
+ android:layout_marginTop="2dp"
+ tools:text="https://example.com/"
+ tools:textColor="@color/photonLightGrey90"
+ app:layout_constraintEnd_toEndOf="@id/label"
+ app:layout_constraintStart_toStartOf="@id/label"
+ app:layout_constraintTop_toBottomOf="@id/label"
+ app:layout_constraintBottom_toBottomOf="parent" />
+
+ <ImageButton
+ android:id="@+id/secondary_button"
+ android:layout_width="@dimen/mozac_widget_site_item_secondary_button_size"
+ android:layout_height="@dimen/mozac_widget_site_item_secondary_button_size"
+ android:padding="@dimen/mozac_widget_site_item_secondary_button_padding"
+ android:layout_marginStart="12dp"
+ android:layout_marginEnd="12dp"
+ android:background="?android:attr/selectableItemBackgroundBorderless"
+ android:visibility="gone"
+ tools:visibility="visible"
+ tools:src="@drawable/mozac_ic_ellipsis_vertical_24"
+ tools:ignore="ContentDescription"
+ tools:tint="#20123A"
+ app:tint="?attr/mozac_primary_text_color"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toEndOf="@id/label"
+ app:layout_constraintEnd_toEndOf="parent" />
+
+</merge>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-am/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-am/strings.xml
new file mode 100644
index 0000000000..cdde258cef
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-am/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">ምስል ወደ ቅንጥብ ሰሌዳ ተቀድቷል</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-ar/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-ar/strings.xml
new file mode 100644
index 0000000000..eb2045ed67
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-ar/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">نُسخت الصورة إلى الحافظة</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-ast/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-ast/strings.xml
new file mode 100644
index 0000000000..013fbec0b8
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-ast/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">La imaxe copióse al cartafueyu</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-azb/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-azb/strings.xml
new file mode 100644
index 0000000000..706d647459
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-azb/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">عکس کلیپ‌بوردا کوپی اولدو</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-be/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-be/strings.xml
new file mode 100644
index 0000000000..7096e05e05
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-be/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Відарыс скапіяваны ў буфер абмену</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-bg/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-bg/strings.xml
new file mode 100644
index 0000000000..9c2defef43
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-bg/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Изображението е копирано</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-br/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-br/strings.xml
new file mode 100644
index 0000000000..3ae6d8340a
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-br/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Skeudenn eilet er golver</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-bs/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-bs/strings.xml
new file mode 100644
index 0000000000..b0e64d0437
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-bs/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Slika je kopirana u privremenu memoriju</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-ca/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-ca/strings.xml
new file mode 100644
index 0000000000..75a4596b48
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-ca/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">S’ha copiat la imatge al porta-retalls</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-cak/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-cak/strings.xml
new file mode 100644
index 0000000000..dee19cb2a1
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-cak/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Xwachib\'ëx ri wachib\'äl pa molwuj</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-co/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-co/strings.xml
new file mode 100644
index 0000000000..dc5ae642c1
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-co/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Fiura cupiata in u preme’papei</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-cs/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-cs/strings.xml
new file mode 100644
index 0000000000..039f1848dc
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-cs/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Obrázek zkopírován do schránky</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-cy/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-cy/strings.xml
new file mode 100644
index 0000000000..274e4c8a92
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-cy/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Copïwyd delwedd i’r clipfwrdd</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-da/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-da/strings.xml
new file mode 100644
index 0000000000..1e8162dcf1
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-da/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Billede kopieret til udklipsholder</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-de/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-de/strings.xml
new file mode 100644
index 0000000000..a6f23ccb4a
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-de/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Grafik in Zwischenablage kopiert</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-dsb/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-dsb/strings.xml
new file mode 100644
index 0000000000..5119090d13
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-dsb/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Wobraz jo se kopěrował do mjazywótkłada</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-el/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-el/strings.xml
new file mode 100644
index 0000000000..57da3ec96d
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-el/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Η εικόνα αντιγράφτηκε στο πρόχειρο</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-en-rCA/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-en-rCA/strings.xml
new file mode 100644
index 0000000000..638ee8543e
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-en-rCA/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Image copied to clipboard</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-en-rGB/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-en-rGB/strings.xml
new file mode 100644
index 0000000000..638ee8543e
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-en-rGB/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Image copied to clipboard</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-eo/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-eo/strings.xml
new file mode 100644
index 0000000000..9cc052f510
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-eo/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Bildo kopiita al la tondujo</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-es-rAR/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-es-rAR/strings.xml
new file mode 100644
index 0000000000..06f644e903
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-es-rAR/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Imagen copiada al portapapeles</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-es-rCL/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-es-rCL/strings.xml
new file mode 100644
index 0000000000..06f644e903
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-es-rCL/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Imagen copiada al portapapeles</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-es-rES/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-es-rES/strings.xml
new file mode 100644
index 0000000000..06f644e903
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-es-rES/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Imagen copiada al portapapeles</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-es-rMX/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-es-rMX/strings.xml
new file mode 100644
index 0000000000..06f644e903
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-es-rMX/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Imagen copiada al portapapeles</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-es/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-es/strings.xml
new file mode 100644
index 0000000000..06f644e903
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-es/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Imagen copiada al portapapeles</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-et/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-et/strings.xml
new file mode 100644
index 0000000000..b77f8fe2fc
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-et/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Pilt kopeeriti vahemällu</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-eu/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-eu/strings.xml
new file mode 100644
index 0000000000..18039c3b03
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-eu/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Irudia arbelean kopiatu da</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-fa/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-fa/strings.xml
new file mode 100644
index 0000000000..4d9c243f3c
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-fa/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">تصویر به تخته‌گیره رونوشت شد</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-fi/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-fi/strings.xml
new file mode 100644
index 0000000000..426de77fe1
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-fi/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Kuva kopioitu leikepöydälle</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-fr/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-fr/strings.xml
new file mode 100644
index 0000000000..c41cec7a23
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-fr/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Image copiée dans le presse-papiers</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-fur/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-fur/strings.xml
new file mode 100644
index 0000000000..5fb71c5b51
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-fur/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Imagjin copiade intes notis</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-fy-rNL/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-fy-rNL/strings.xml
new file mode 100644
index 0000000000..db7c6fa54d
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-fy-rNL/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Ofbylding nei klamboerd kopiearre</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-gd/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-gd/strings.xml
new file mode 100644
index 0000000000..a2c04bd312
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-gd/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Chaidh lethbhreac dhen dealbh a chur air an stòr-bhòrd</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-gl/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-gl/strings.xml
new file mode 100644
index 0000000000..3e7252778d
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-gl/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Copiouse a imaxe ao portapapeis</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-gn/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-gn/strings.xml
new file mode 100644
index 0000000000..af5e3ba7cb
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-gn/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Embohasa ta’ãnga kuatiajokohápe</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-hr/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-hr/strings.xml
new file mode 100644
index 0000000000..dbfcb2d809
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-hr/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Slika je kopirana u međuspremnik</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-hsb/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-hsb/strings.xml
new file mode 100644
index 0000000000..473527a6cd
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-hsb/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Wobraz je so do mjezyskłada kopěrował</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-hu/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-hu/strings.xml
new file mode 100644
index 0000000000..0ca081817d
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-hu/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Kép vágólapra másolva</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-hy-rAM/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-hy-rAM/strings.xml
new file mode 100644
index 0000000000..4567efaed4
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-hy-rAM/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Պատկերը պատճենվել է սեղմատախտակին</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-ia/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-ia/strings.xml
new file mode 100644
index 0000000000..6b8f02500b
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-ia/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Imagine copiate al area de transferentia</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-in/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-in/strings.xml
new file mode 100644
index 0000000000..4b6807e51e
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-in/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Gambar disalin ke papan klip</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-is/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-is/strings.xml
new file mode 100644
index 0000000000..c2e2bb5714
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-is/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Mynd afrituð á klippispjald</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-it/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-it/strings.xml
new file mode 100644
index 0000000000..79c58ec52e
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-it/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Immagine copiata negli appunti</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-iw/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-iw/strings.xml
new file mode 100644
index 0000000000..85bf7a942a
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-iw/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">התמונה הועתקה ללוח</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-ja/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-ja/strings.xml
new file mode 100644
index 0000000000..805eacfc87
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-ja/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">画像をクリップボードにコピーしました</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-ka/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-ka/strings.xml
new file mode 100644
index 0000000000..8de04f6263
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-ka/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">სურათის ასლი აღებულია</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-kaa/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-kaa/strings.xml
new file mode 100644
index 0000000000..cb2c90ba5b
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-kaa/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Súwret almasıw buferine kóshirip alındı</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-kab/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-kab/strings.xml
new file mode 100644
index 0000000000..093f1d2616
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-kab/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Tugna tettwanɣel ɣef wafus</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-kk/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-kk/strings.xml
new file mode 100644
index 0000000000..80b99ef483
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-kk/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Сурет алмасу буферіне көшірілді</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-kmr/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-kmr/strings.xml
new file mode 100644
index 0000000000..b299c8f6b7
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-kmr/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Wêne li panoyê hate kopîkirin</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-ko/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-ko/strings.xml
new file mode 100644
index 0000000000..5d6ebf83e7
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-ko/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">클립보드에 이미지 복사됨</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-lo/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-lo/strings.xml
new file mode 100644
index 0000000000..ab066dba6f
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-lo/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">ສຳເນົາຮູບໃສ່ຄລິບບອດແລ້ວ</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-nb-rNO/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-nb-rNO/strings.xml
new file mode 100644
index 0000000000..bef36125f1
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-nb-rNO/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Bilde kopiert til utklippstavlen</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-nl/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-nl/strings.xml
new file mode 100644
index 0000000000..06070807bb
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-nl/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Afbeelding naar klembord gekopieerd</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-nn-rNO/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-nn-rNO/strings.xml
new file mode 100644
index 0000000000..16c41f8d38
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-nn-rNO/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Bilde kopiert til utklippstavla</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-oc/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-oc/strings.xml
new file mode 100644
index 0000000000..131c779121
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-oc/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Imatge copiat al quichapapièrs</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-pa-rIN/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-pa-rIN/strings.xml
new file mode 100644
index 0000000000..03300db934
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-pa-rIN/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">ਚਿੱਤਰ ਨੂੰ ਕਲਿੱਪਬੋਰਡ ਲਈ ਕਾਪੀ ਕੀਤਾ</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-pa-rPK/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-pa-rPK/strings.xml
new file mode 100644
index 0000000000..cb4e8c879c
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-pa-rPK/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">کاپی کیتی گئی</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-pl/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-pl/strings.xml
new file mode 100644
index 0000000000..8a79fb5c5e
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-pl/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Skopiowano obraz do schowka</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-pt-rBR/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-pt-rBR/strings.xml
new file mode 100644
index 0000000000..223e60bc6a
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-pt-rBR/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Imagem copiada para área de transferência</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-pt-rPT/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-pt-rPT/strings.xml
new file mode 100644
index 0000000000..48dc017c65
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-pt-rPT/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Imagem copiada para a área de transferência</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-rm/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-rm/strings.xml
new file mode 100644
index 0000000000..8b428f02b1
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-rm/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Copià il maletg en l\'archiv provisoric</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-ro/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-ro/strings.xml
new file mode 100644
index 0000000000..be7fc12e65
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-ro/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Imaginea a fost copiată în clipboard</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-ru/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-ru/strings.xml
new file mode 100644
index 0000000000..35ac3be80a
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-ru/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Изображение скопировано в буфер обмена</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-sat/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-sat/strings.xml
new file mode 100644
index 0000000000..d945ea02c5
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-sat/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">ᱨᱮᱴᱚᱯᱵᱚᱰ ᱨᱮ ᱪᱤᱛᱟᱹᱨ ᱱᱚᱠᱚᱞ ᱮᱱᱟ</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-sc/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-sc/strings.xml
new file mode 100644
index 0000000000..6d122f7e8b
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-sc/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Immàgine copiada in punta de billete</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-si/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-si/strings.xml
new file mode 100644
index 0000000000..95d46d5ddd
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-si/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">රූපය පසුරුපුවරුවට පිටපත් විය</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-sk/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-sk/strings.xml
new file mode 100644
index 0000000000..02dc6628cb
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-sk/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Obrázok bol skopírovaný do schránky</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-skr/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-skr/strings.xml
new file mode 100644
index 0000000000..da370ffcb7
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-skr/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">تصویر کلپ بورڈ تے نقل تھی ڳئی</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-sl/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-sl/strings.xml
new file mode 100644
index 0000000000..b3a3418289
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-sl/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Slika kopirana v odložišče</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-sq/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-sq/strings.xml
new file mode 100644
index 0000000000..b76b67a352
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-sq/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Figura u kopjua në të papastër</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-sr/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-sr/strings.xml
new file mode 100644
index 0000000000..f0fbe5c485
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-sr/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Слика је копирана у привремену меморију</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-su/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-su/strings.xml
new file mode 100644
index 0000000000..91a37e77b6
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-su/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Gambar ditiron kana papan klip</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-sv-rSE/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-sv-rSE/strings.xml
new file mode 100644
index 0000000000..51210de628
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-sv-rSE/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Bilden har kopierats till urklipp</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-tg/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-tg/strings.xml
new file mode 100644
index 0000000000..b304da28c1
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-tg/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Тасвир ба ҳофизаи муваққатӣ нусха бардошта шуд</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-th/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-th/strings.xml
new file mode 100644
index 0000000000..52a77b64dc
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-th/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">คัดลอกภาพไปยังคลิปบอร์ดแล้ว</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-tr/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-tr/strings.xml
new file mode 100644
index 0000000000..b0ef721f12
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-tr/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Resim panoya kopyalandı</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-trs/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-trs/strings.xml
new file mode 100644
index 0000000000..d905bed3df
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-trs/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Ñadū’hua ngà nanun riña portapapel</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-tt/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-tt/strings.xml
new file mode 100644
index 0000000000..ad4402d83c
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-tt/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Рәсем алмашу буферына копияләнде</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-ug/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-ug/strings.xml
new file mode 100644
index 0000000000..2719590251
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-ug/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">سۈرەت چاپلاش تاختىسىغا كۆچۈرۈلدى</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-uk/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-uk/strings.xml
new file mode 100644
index 0000000000..49cffe6ba2
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-uk/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Зображення скопійовано в буфер обміну</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-vi/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-vi/strings.xml
new file mode 100644
index 0000000000..8127c7855b
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-vi/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Đã sao chép ảnh vào khay nhớ tạm</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-zh-rCN/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000000..98d08c5c6a
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-zh-rCN/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">图像已复制到剪贴板</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values-zh-rTW/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000000..39326e4ac0
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values-zh-rTW/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">已將圖片複製至剪貼簿</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values/attrs.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values/attrs.xml
new file mode 100644
index 0000000000..60a3b3a35f
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values/attrs.xml
@@ -0,0 +1,19 @@
+<?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>
+ <attr name="mozac_font_semibold" format="reference" />
+ <attr name="mozac_accent" format="reference" />
+ <attr name="mozac_contrast_text" format="reference" />
+
+ <!-- Background color for favicon widget box -->
+ <attr name="mozac_widget_favicon_background_color" format="reference" />
+ <!-- Border color for favicon widget box -->
+ <attr name="mozac_widget_favicon_border_color" format="reference" />
+
+ <!-- Label color and icon button tint for site item widget -->
+ <attr name="mozac_primary_text_color" format="reference" />
+ <!-- Caption color for site item widget -->
+ <attr name="mozac_caption_text_color" format="reference" />
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values/colors.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values/colors.xml
new file mode 100644
index 0000000000..105264e605
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values/colors.xml
@@ -0,0 +1,10 @@
+<?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>
+ <!-- Button Colors -->
+ <color name="mozac_grey_button_color">#E0E0E6</color>
+ <color name="mozac_destructive_button_text_color">#C50042</color>
+ <color name="mozac_grey_button_text_color">#312A65</color>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values/dimens.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values/dimens.xml
new file mode 100644
index 0000000000..9f6d4587fd
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values/dimens.xml
@@ -0,0 +1,14 @@
+<?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>
+ <dimen name="mozac_widget_favicon_size">40dp</dimen>
+ <dimen name="mozac_widget_favicon_padding">8dp</dimen>
+
+ <dimen name="mozac_widget_site_item_height">56dp</dimen>
+ <dimen name="mozac_widget_site_item_label_text_size">16sp</dimen>
+ <dimen name="mozac_widget_site_item_caption_text_size">12sp</dimen>
+ <dimen name="mozac_widget_site_item_secondary_button_size">32dp</dimen>
+ <dimen name="mozac_widget_site_item_secondary_button_padding">4dp</dimen>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values/strings.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values/strings.xml
new file mode 100644
index 0000000000..6a82d7eaef
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values/strings.xml
@@ -0,0 +1,8 @@
+<?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>
+ <!-- Text for confirmation "snackbar" shown after copying an image to the clipboard. -->
+ <string name="snackbar_copy_image_to_clipboard_confirmation">Image copied to clipboard</string>
+</resources>
diff --git a/mobile/android/android-components/components/ui/widgets/src/main/res/values/styles.xml b/mobile/android/android-components/components/ui/widgets/src/main/res/values/styles.xml
new file mode 100644
index 0000000000..515b98e12e
--- /dev/null
+++ b/mobile/android/android-components/components/ui/widgets/src/main/res/values/styles.xml
@@ -0,0 +1,63 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android">
+ <style name="Mozac.Widgets.TestTheme" parent="Theme.MaterialComponents.Light.NoActionBar">
+ <item name="mozac_widget_favicon_background_color">@color/photonWhite</item>
+ <item name="mozac_widget_favicon_border_color">@color/photonLightGrey30</item>
+ <item name="mozac_primary_text_color">@color/photonInk90</item>
+ <item name="mozac_caption_text_color">@color/photonInk50</item>
+ </style>
+
+ <!-- Button styling -->
+ <style name="Mozac.Widgets.NeutralButton" parent="Widget.MaterialComponents.Button.TextButton">
+ <item name="iconTint">@color/mozac_grey_button_text_color</item>
+ <item name="iconPadding">8dp</item>
+ <item name="iconGravity">textStart</item>
+ <item name="android:textAlignment">center</item>
+ <item name="android:background">@drawable/rounded_button_background</item>
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">48dp</item>
+ <item name="android:textStyle">bold</item>
+ <item name="android:textAllCaps">false</item>
+ <item name="backgroundTint">@color/mozac_grey_button_color</item>
+ <item name="android:textColor">@color/mozac_grey_button_text_color</item>
+ <item name="android:letterSpacing">0</item>
+ <item name="fontFamily">?mozac_font_semibold</item>
+ </style>
+
+ <style name="Mozac.Widgets.DestructiveButton" parent="Mozac.Widgets.NeutralButton">
+ <item name="iconTint">@color/mozac_destructive_button_text_color</item>
+ <item name="android:textColor">@color/mozac_destructive_button_text_color</item>
+ </style>
+
+ <style name="Mozac.Widgets.PositiveButton" parent="Mozac.Widgets.NeutralButton">
+ <item name="backgroundTint">?mozac_accent</item>
+ <item name="iconTint">?mozac_contrast_text</item>
+ <item name="android:textColor">?mozac_contrast_text</item>
+ </style>
+
+ <!-- Favicon styling -->
+ <style name="Mozac.Widgets.Favicon" parent="">
+ <item name="android:layout_width">@dimen/mozac_widget_favicon_size</item>
+ <item name="android:layout_height">@dimen/mozac_widget_favicon_size</item>
+ <item name="android:scaleType">fitCenter</item>
+ <item name="android:padding">@dimen/mozac_widget_favicon_padding</item>
+ <item name="android:background">@drawable/mozac_widget_favicon_background</item>
+ </style>
+
+ <style name="Mozac.Widgets.SiteItem.Label" parent="">
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:ellipsize">end</item>
+ <item name="android:singleLine">true</item>
+ <item name="android:textAlignment">viewStart</item>
+ <item name="android:textSize">@dimen/mozac_widget_site_item_label_text_size</item>
+ <item name="android:textColor">?attr/mozac_primary_text_color</item>
+ </style>
+ <style name="Mozac.Widgets.SiteItem.Caption" parent="Mozac.Widgets.SiteItem.Label">
+ <item name="android:textSize">@dimen/mozac_widget_site_item_caption_text_size</item>
+ <item name="android:textColor">?attr/mozac_caption_text_color</item>
+ </style>
+</resources>