[tor-commits] [Git][tpo/applications/firefox-android][firefox-android-115.2.1-13.0-1] 2 commits: Bug 1823316 - Use 'Snackbar' themed Dialog to notify on making app full-screen

richard (@richard) git at gitlab.torproject.org
Thu Dec 14 18:54:50 UTC 2023



richard pushed to branch firefox-android-115.2.1-13.0-1 at The Tor Project / Applications / firefox-android


Commits:
a0805d02 by t-p-white at 2023-12-14T15:36:13+01:00
Bug 1823316 - Use 'Snackbar' themed Dialog to notify on making app full-screen

- - - - -
8ff2dd1b by Tarik Eshaq at 2023-12-14T15:36:22+01:00
Bug 1865488: Adds server parameter to push subscription

(cherry picked from commit f66bc9d4981d9bba7091389d9f0a6864291d38fe)

- - - - -


9 changed files:

- + android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/dialog/FullScreenNotificationDialog.kt
- android-components/components/feature/sitepermissions/src/main/java/mozilla/components/feature/sitepermissions/SitePermissionsFeature.kt
- fenix/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt
- fenix/app/src/main/java/org/mozilla/fenix/push/WebPushEngineIntegration.kt
- + fenix/app/src/main/res/layout/full_screen_notification_dialog.xml
- focus-android/app/src/main/java/org/mozilla/focus/browser/integration/FullScreenIntegration.kt
- focus-android/app/src/main/java/org/mozilla/focus/fragment/BrowserFragment.kt
- + focus-android/app/src/main/res/layout/dialog_full_screen_notification.xml
- focus-android/app/src/test/java/org/mozilla/focus/browser/integration/FullScreenIntegrationTest.kt


Changes:

=====================================
android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/dialog/FullScreenNotificationDialog.kt
=====================================
@@ -0,0 +1,69 @@
+/* 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.feature.prompts.dialog
+
+import android.app.Dialog
+import android.os.Bundle
+import android.view.Gravity
+import android.view.WindowManager
+import androidx.annotation.LayoutRes
+import androidx.appcompat.app.AlertDialog
+import androidx.fragment.app.DialogFragment
+import androidx.fragment.app.FragmentManager
+import androidx.lifecycle.lifecycleScope
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+
+private const val TAG = "mozac_feature_prompts_full_screen_notification_dialog"
+private const val SNACKBAR_DURATION_LONG_MS = 3000L
+
+/**
+ * UI to show a 'full screen mode' notification.
+ */
+interface FullScreenNotification {
+    /**
+     * Show the notification.
+     *
+     * @param fragmentManager the [FragmentManager] to add this notification to.
+     */
+    fun show(fragmentManager: FragmentManager)
+}
+
+/**
+ * [DialogFragment] that is configured to match the style and behaviour of a Snackbar.
+ *
+ * @property layout the layout to use for the dialog.
+ */
+class FullScreenNotificationDialog(@LayoutRes val layout: Int) :
+    DialogFragment(), FullScreenNotification {
+    override fun show(fragmentManager: FragmentManager) = super.show(fragmentManager, TAG)
+
+    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog = requireActivity().let {
+        val view = layoutInflater.inflate(layout, null)
+        AlertDialog.Builder(it).setView(view).create()
+    }
+
+    override fun onStart() {
+        super.onStart()
+
+        dialog?.let { dialog ->
+            dialog.window?.let { window ->
+                // Prevent any user input from key or other button events to it.
+                window.setFlags(
+                    WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+                    WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+                )
+
+                window.setGravity(Gravity.BOTTOM)
+                window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
+            }
+
+            lifecycleScope.launch {
+                delay(SNACKBAR_DURATION_LONG_MS)
+                dismiss()
+            }
+        }
+    }
+}


=====================================
android-components/components/feature/sitepermissions/src/main/java/mozilla/components/feature/sitepermissions/SitePermissionsFeature.kt
=====================================
@@ -70,7 +70,9 @@ import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged
 import java.security.InvalidParameterException
 import mozilla.components.ui.icons.R as iconsR
 
-internal const val FRAGMENT_TAG = "mozac_feature_sitepermissions_prompt_dialog"
+internal const val PROMPT_FRAGMENT_TAG = "mozac_feature_sitepermissions_prompt_dialog"
+
+private const val FULL_SCREEN_NOTIFICATION_TAG = "mozac_feature_prompts_full_screen_notification_dialog"
 
 @VisibleForTesting
 internal const val STORAGE_ACCESS_DOCUMENTATION_URL =
@@ -124,7 +126,7 @@ class SitePermissionsFeature(
     private var loadingScope: CoroutineScope? = null
 
     override fun start() {
-        fragmentManager.findFragmentByTag(FRAGMENT_TAG)?.let { fragment ->
+        fragmentManager.findFragmentByTag(PROMPT_FRAGMENT_TAG)?.let { fragment ->
             // There's still a [SitePermissionsDialogFragment] visible from the last time. Re-attach
             // this feature so that the fragment can invoke the callback on this feature once the user
             // makes a selection. This can happen when the app was in the background and on resume
@@ -439,8 +441,16 @@ class SitePermissionsFeature(
         } else {
             handleNoRuledFlow(permissionFromStorage, permissionRequest, origin)
         }
-        prompt?.show(fragmentManager, FRAGMENT_TAG)
-        return prompt
+
+        val fullScreenNotificationDisplayed =
+            fragmentManager.fragments.any { fragment -> fragment.tag == FULL_SCREEN_NOTIFICATION_TAG }
+
+        return if (fullScreenNotificationDisplayed || prompt == null) {
+            null
+        } else {
+            prompt.show(fragmentManager, PROMPT_FRAGMENT_TAG)
+            prompt
+        }
     }
 
     @VisibleForTesting


=====================================
fenix/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt
=====================================
@@ -17,7 +17,6 @@ import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
 import android.view.accessibility.AccessibilityManager
-import android.widget.Toast
 import androidx.activity.result.ActivityResultLauncher
 import androidx.annotation.CallSuper
 import androidx.annotation.VisibleForTesting
@@ -74,6 +73,7 @@ import mozilla.components.feature.prompts.PromptFeature
 import mozilla.components.feature.prompts.PromptFeature.Companion.PIN_REQUEST
 import mozilla.components.feature.prompts.address.AddressDelegate
 import mozilla.components.feature.prompts.creditcard.CreditCardDelegate
+import mozilla.components.feature.prompts.dialog.FullScreenNotificationDialog
 import mozilla.components.feature.prompts.login.LoginDelegate
 import mozilla.components.feature.prompts.share.ShareDelegate
 import mozilla.components.feature.readerview.ReaderViewFeature
@@ -1465,10 +1465,11 @@ abstract class BaseBrowserFragment :
         if (inFullScreen) {
             // Close find in page bar if opened
             findInPageIntegration.onBackPressed()
-            Toast
-                .makeText(requireContext(), R.string.full_screen_notification, Toast.LENGTH_SHORT)
-                .show()
-            activity?.enterToImmersiveMode()
+
+            FullScreenNotificationDialog(R.layout.full_screen_notification_dialog).show(
+                parentFragmentManager,
+            )
+
             (view as? SwipeGestureLayout)?.isSwipeEnabled = false
             browserToolbarView.collapse()
             browserToolbarView.view.isVisible = false


=====================================
fenix/app/src/main/java/org/mozilla/fenix/push/WebPushEngineIntegration.kt
=====================================
@@ -58,8 +58,6 @@ internal class WebPushEngineDelegate(
     private val logger = Logger("WebPushEngineDelegate")
 
     override fun onGetSubscription(scope: String, onSubscription: (WebPushSubscription?) -> Unit) {
-        // We don't have the appServerKey unless an app is creating a new subscription so we
-        // allow the key to be null since it won't be overridden from a previous subscription.
         pushFeature.getSubscription(scope) {
             onSubscription(it?.toEnginePushSubscription())
         }
@@ -72,9 +70,7 @@ internal class WebPushEngineDelegate(
     ) {
         pushFeature.subscribe(
             scope = scope,
-            // See the full note at the implementation of `toEnginePushSubscription`.
-            // Issue: https://github.com/mozilla/application-services/issues/2698
-            appServerKey = null,
+            appServerKey = serverKey?.toEncodedBase64String(),
             onSubscribeError = {
                 logger.error("Error on push onSubscribe.")
                 onSubscribe(null)
@@ -104,13 +100,12 @@ internal fun AutoPushSubscription.toEnginePushSubscription() = WebPushSubscripti
     publicKey = this.publicKey.toDecodedByteArray(),
     endpoint = this.endpoint,
     authSecret = this.authKey.toDecodedByteArray(),
-    // We don't send the `serverKey` because the code path from that will query
-    // the push database for this key, which leads to an exception thrown.
-    // Our workaround for now is to not put the server key in to begin with (which
-    // will probably break a lot of sites).
-    // See: https://github.com/mozilla/application-services/issues/2698
+    // We don't have the appServerKey unless an app is creating a new subscription so we
+    // allow the key to be null since it won't be overridden from a previous subscription.
     appServerKey = null,
 )
 
 private fun String.toDecodedByteArray() =
     Base64.decode(this.toByteArray(), Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP)
+private fun ByteArray.toEncodedBase64String() =
+    Base64.encodeToString(this, Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP)


=====================================
fenix/app/src/main/res/layout/full_screen_notification_dialog.xml
=====================================
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/full_screen_notification"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_margin="8dp"
+    android:background="@drawable/fenix_snackbar_background"
+    android:elevation="4dp"
+    android:minHeight="48dp"
+    android:orientation="horizontal"
+    android:paddingStart="16dp"
+    android:paddingEnd="16dp">
+
+    <TextView
+        android:id="@+id/full_screen_notification_text"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:ellipsize="end"
+        android:letterSpacing="0.05"
+        android:maxLines="2"
+        android:minHeight="46dp"
+        android:paddingTop="8dp"
+        android:paddingBottom="8dp"
+        android:text="@string/full_screen_notification"
+        android:textAlignment="textStart"
+        android:textColor="@color/photonWhite"
+        android:textSize="18sp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        tools:text="@string/full_screen_notification" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>


=====================================
focus-android/app/src/main/java/org/mozilla/focus/browser/integration/FullScreenIntegration.kt
=====================================
@@ -7,12 +7,14 @@ package org.mozilla.focus.browser.integration
 import android.app.Activity
 import android.os.Build
 import android.view.View
-import android.widget.Toast
 import androidx.annotation.VisibleForTesting
 import androidx.core.view.isVisible
+import androidx.fragment.app.FragmentManager
 import mozilla.components.browser.state.store.BrowserStore
 import mozilla.components.browser.toolbar.BrowserToolbar
 import mozilla.components.concept.engine.EngineView
+import mozilla.components.feature.prompts.dialog.FullScreenNotification
+import mozilla.components.feature.prompts.dialog.FullScreenNotificationDialog
 import mozilla.components.feature.session.FullScreenFeature
 import mozilla.components.feature.session.SessionUseCases
 import mozilla.components.support.base.feature.LifecycleAwareFeature
@@ -26,6 +28,7 @@ import org.mozilla.focus.ext.hide
 import org.mozilla.focus.ext.showAsFixed
 import org.mozilla.focus.utils.Settings
 
+ at Suppress("LongParameterList")
 class FullScreenIntegration(
     val activity: Activity,
     val store: BrowserStore,
@@ -35,6 +38,7 @@ class FullScreenIntegration(
     private val toolbarView: BrowserToolbar,
     private val statusBar: View,
     private val engineView: EngineView,
+    private val parentFragmentManager: FragmentManager,
 ) : LifecycleAwareFeature, UserInteractionHandler {
     @VisibleForTesting
     internal var feature = FullScreenFeature(
@@ -54,14 +58,16 @@ class FullScreenIntegration(
     }
 
     @VisibleForTesting
-    internal fun fullScreenChanged(enabled: Boolean) {
+    internal fun fullScreenChanged(
+        enabled: Boolean,
+        fullScreenNotification: FullScreenNotification =
+            FullScreenNotificationDialog(R.layout.dialog_full_screen_notification),
+    ) {
         if (enabled) {
             enterBrowserFullscreen()
             statusBar.isVisible = false
 
-            Toast
-                .makeText(activity, R.string.full_screen_notification, Toast.LENGTH_SHORT)
-                .show()
+            fullScreenNotification.show(parentFragmentManager)
 
             switchToImmersiveMode()
         } else {


=====================================
focus-android/app/src/main/java/org/mozilla/focus/fragment/BrowserFragment.kt
=====================================
@@ -272,6 +272,7 @@ class BrowserFragment :
                 binding.browserToolbar,
                 binding.statusBarBackground,
                 binding.engineView,
+                parentFragmentManager,
             ),
             this,
             view,


=====================================
focus-android/app/src/main/res/layout/dialog_full_screen_notification.xml
=====================================
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/full_screen_notification_layout"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_margin="8dp"
+    android:background="@drawable/focus_snackbar_background"
+    android:elevation="4dp"
+    android:minHeight="48dp"
+    android:orientation="horizontal"
+    android:paddingStart="16dp"
+    android:paddingEnd="16dp">
+
+    <TextView
+        android:id="@+id/full_screen_notification_text"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:ellipsize="end"
+        android:letterSpacing="0.05"
+        android:maxLines="2"
+        android:paddingTop="8dp"
+        android:paddingBottom="8dp"
+        android:text="@string/full_screen_notification"
+        android:textAlignment="textStart"
+        android:textColor="@color/snackbarTextColor"
+        android:textSize="14sp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        tools:text="@string/full_screen_notification" />
+</androidx.constraintlayout.widget.ConstraintLayout>


=====================================
focus-android/app/src/test/java/org/mozilla/focus/browser/integration/FullScreenIntegrationTest.kt
=====================================
@@ -12,11 +12,11 @@ import android.view.WindowManager
 import androidx.core.view.isVisible
 import mozilla.components.browser.engine.gecko.GeckoEngineView
 import mozilla.components.browser.toolbar.BrowserToolbar
+import mozilla.components.feature.prompts.dialog.FullScreenNotification
 import mozilla.components.feature.session.FullScreenFeature
 import mozilla.components.support.test.any
 import mozilla.components.support.test.mock
 import mozilla.components.support.test.robolectric.testContext
-import org.junit.Assert.assertNotNull
 import org.junit.Test
 import org.junit.jupiter.api.Assertions.assertEquals
 import org.junit.runner.RunWith
@@ -26,7 +26,6 @@ import org.mockito.Mockito.never
 import org.mockito.Mockito.spy
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
-import org.mozilla.focus.R
 import org.mozilla.focus.ext.disableDynamicBehavior
 import org.mozilla.focus.ext.enableDynamicBehavior
 import org.mozilla.focus.ext.hide
@@ -34,7 +33,6 @@ import org.mozilla.focus.ext.showAsFixed
 import org.mozilla.focus.utils.Settings
 import org.robolectric.Robolectric
 import org.robolectric.RobolectricTestRunner
-import org.robolectric.shadows.ShadowToast
 
 @RunWith(RobolectricTestRunner::class)
 internal class FullScreenIntegrationTest {
@@ -50,6 +48,7 @@ internal class FullScreenIntegrationTest {
             mock(),
             mock(),
             mock(),
+            mock(),
         ).apply {
             this.feature = feature
         }
@@ -71,6 +70,7 @@ internal class FullScreenIntegrationTest {
             mock(),
             mock(),
             mock(),
+            mock(),
         ).apply {
             this.feature = feature
         }
@@ -92,6 +92,7 @@ internal class FullScreenIntegrationTest {
             mock(),
             mock(),
             mock(),
+            mock(),
         ).apply {
             this.feature = feature
         }
@@ -117,6 +118,7 @@ internal class FullScreenIntegrationTest {
             mock(),
             mock(),
             mock(),
+            mock(),
         )
 
         integration.viewportFitChanged(33)
@@ -141,6 +143,7 @@ internal class FullScreenIntegrationTest {
             mock(),
             mock(),
             mock(),
+            mock(),
         )
 
         integration.switchToImmersiveMode()
@@ -169,6 +172,7 @@ internal class FullScreenIntegrationTest {
             mock(),
             mock(),
             mock(),
+            mock(),
         )
 
         integration.exitImmersiveMode()
@@ -195,6 +199,7 @@ internal class FullScreenIntegrationTest {
             toolbar,
             mock(),
             engineView,
+            mock(),
         )
 
         integration.enterBrowserFullscreen()
@@ -220,6 +225,7 @@ internal class FullScreenIntegrationTest {
             toolbar,
             mock(),
             engineView,
+            mock(),
         )
 
         integration.enterBrowserFullscreen()
@@ -250,6 +256,7 @@ internal class FullScreenIntegrationTest {
             toolbar,
             mock(),
             engineView,
+            mock(),
         )
 
         integration.exitBrowserFullscreen()
@@ -278,6 +285,7 @@ internal class FullScreenIntegrationTest {
             toolbar,
             mock(),
             engineView,
+            mock(),
         )
 
         integration.exitBrowserFullscreen()
@@ -308,21 +316,17 @@ internal class FullScreenIntegrationTest {
                 toolbar,
                 statusBar,
                 engineView,
+                mock(),
             ),
         )
 
-        integration.fullScreenChanged(true)
+        val fullScreenNotification = mock<FullScreenNotification>()
+        integration.fullScreenChanged(true, fullScreenNotification)
 
         verify(integration).enterBrowserFullscreen()
-        verify(integration).switchToImmersiveMode()
         verify(statusBar).isVisible = false
-
-        val toast = ShadowToast.getTextOfLatestToast()
-        assertNotNull(toast)
-        assertEquals(
-            testContext.getString(R.string.full_screen_notification),
-            toast,
-        )
+        verify(fullScreenNotification).show(any())
+        verify(integration).switchToImmersiveMode()
     }
 
     @Test
@@ -352,6 +356,7 @@ internal class FullScreenIntegrationTest {
                 toolbar,
                 statusBar,
                 engineView,
+                mock(),
             ),
         )
 



View it on GitLab: https://gitlab.torproject.org/tpo/applications/firefox-android/-/compare/332071bd74f249a5da4969091ddc2b268f1715ec...8ff2dd1b1d2063105d5fe7129d32064bda87f422

-- 
View it on GitLab: https://gitlab.torproject.org/tpo/applications/firefox-android/-/compare/332071bd74f249a5da4969091ddc2b268f1715ec...8ff2dd1b1d2063105d5fe7129d32064bda87f422
You're receiving this email because of your account on gitlab.torproject.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.torproject.org/pipermail/tor-commits/attachments/20231214/5d07a050/attachment-0001.htm>


More information about the tor-commits mailing list