Skip to content
Snippets Groups Projects
Verified Commit af11e480 authored by t-p-white's avatar t-p-white Committed by ma1
Browse files

Bug 1823316 - Use 'Snackbar' themed Dialog to notify on making app full-screen

parent 06e63c65
No related branches found
No related tags found
No related merge requests found
Showing
with 196 additions and 26 deletions
/* 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()
}
}
}
}
......@@ -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
......
......@@ -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
......
<?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>
......@@ -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
@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 {
......
......@@ -272,6 +272,7 @@ class BrowserFragment :
binding.browserToolbar,
binding.statusBarBackground,
binding.engineView,
parentFragmentManager,
),
this,
view,
......
<?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>
......@@ -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(),
),
)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment