Richard Pospesel pushed to branch android-components-102.0.14-12.5-1 at The Tor Project / Applications / android-components
Commits:
-
52f79946
by Mugurell at 2023-03-16T12:04:07+00:00
-
4ec68dfa
by Mugurell at 2023-03-16T12:04:07+00:00
-
9589ff50
by Mugurell at 2023-03-16T12:04:07+00:00
-
ccafd6d1
by Mugurell at 2023-03-16T12:04:07+00:00
8 changed files:
- components/browser/toolbar/src/main/java/mozilla/components/browser/toolbar/behavior/BrowserToolbarBehavior.kt
- components/browser/toolbar/src/test/java/mozilla/components/browser/toolbar/behavior/BrowserToolbarBehaviorTest.kt
- components/feature/downloads/src/main/java/mozilla/components/feature/downloads/DownloadDialogFragment.kt
- components/feature/downloads/src/main/java/mozilla/components/feature/downloads/DownloadsFeature.kt
- components/feature/downloads/src/main/java/mozilla/components/feature/downloads/ext/DownloadState.kt
- components/feature/downloads/src/main/java/mozilla/components/feature/downloads/ui/DownloaderAppAdapter.kt
- components/feature/prompts/build.gradle
- components/feature/prompts/src/main/java/mozilla/components/feature/prompts/PromptFeature.kt
Changes:
| ... | ... | @@ -6,19 +6,15 @@ package mozilla.components.browser.toolbar.behavior |
| 6 | 6 | |
| 7 | 7 | import android.content.Context
|
| 8 | 8 | import android.util.AttributeSet
|
| 9 | -import android.view.Gravity
|
|
| 10 | 9 | import android.view.MotionEvent
|
| 11 | 10 | import android.view.View
|
| 12 | 11 | import androidx.annotation.VisibleForTesting
|
| 13 | 12 | import androidx.coordinatorlayout.widget.CoordinatorLayout
|
| 14 | 13 | import androidx.core.view.ViewCompat
|
| 15 | -import com.google.android.material.snackbar.Snackbar
|
|
| 16 | 14 | import mozilla.components.browser.toolbar.BrowserToolbar
|
| 17 | 15 | import mozilla.components.concept.engine.EngineView
|
| 18 | 16 | import mozilla.components.support.ktx.android.view.findViewInHierarchy
|
| 19 | 17 | |
| 20 | -private const val SMALL_ELEVATION_CHANGE = 0.01f
|
|
| 21 | - |
|
| 22 | 18 | /**
|
| 23 | 19 | * Where the toolbar is placed on the screen.
|
| 24 | 20 | */
|
| ... | ... | @@ -35,7 +31,6 @@ enum class ToolbarPosition { |
| 35 | 31 | *
|
| 36 | 32 | * This implementation will:
|
| 37 | 33 | * - Show/Hide the [BrowserToolbar] automatically when scrolling vertically.
|
| 38 | - * - On showing a [Snackbar] position it above the [BrowserToolbar].
|
|
| 39 | 34 | * - Snap the [BrowserToolbar] to be hidden or visible when the user stops scrolling.
|
| 40 | 35 | */
|
| 41 | 36 | class BrowserToolbarBehavior(
|
| ... | ... | @@ -128,14 +123,6 @@ class BrowserToolbarBehavior( |
| 128 | 123 | return false // allow events to be passed to below listeners
|
| 129 | 124 | }
|
| 130 | 125 | |
| 131 | - override fun layoutDependsOn(parent: CoordinatorLayout, child: BrowserToolbar, dependency: View): Boolean {
|
|
| 132 | - if (toolbarPosition == ToolbarPosition.BOTTOM && dependency is Snackbar.SnackbarLayout) {
|
|
| 133 | - positionSnackbar(child, dependency)
|
|
| 134 | - }
|
|
| 135 | - |
|
| 136 | - return super.layoutDependsOn(parent, child, dependency)
|
|
| 137 | - }
|
|
| 138 | - |
|
| 139 | 126 | override fun onLayoutChild(
|
| 140 | 127 | parent: CoordinatorLayout,
|
| 141 | 128 | child: BrowserToolbar,
|
| ... | ... | @@ -179,23 +166,6 @@ class BrowserToolbarBehavior( |
| 179 | 166 | isScrollEnabled = false
|
| 180 | 167 | }
|
| 181 | 168 | |
| 182 | - @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
|
| 183 | - internal fun positionSnackbar(child: View, snackbarLayout: Snackbar.SnackbarLayout) {
|
|
| 184 | - val params = snackbarLayout.layoutParams as CoordinatorLayout.LayoutParams
|
|
| 185 | - |
|
| 186 | - // Position the snackbar above the toolbar so that it doesn't overlay the toolbar.
|
|
| 187 | - params.anchorId = child.id
|
|
| 188 | - params.anchorGravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL
|
|
| 189 | - params.gravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL
|
|
| 190 | - |
|
| 191 | - snackbarLayout.layoutParams = params
|
|
| 192 | - |
|
| 193 | - // In order to avoid the snackbar casting a shadow on the toolbar we adjust the elevation of the snackbar here.
|
|
| 194 | - // We still place it slightly behind the toolbar so that it will not animate over the toolbar but instead pop
|
|
| 195 | - // out from under the toolbar.
|
|
| 196 | - snackbarLayout.elevation = child.elevation - SMALL_ELEVATION_CHANGE
|
|
| 197 | - }
|
|
| 198 | - |
|
| 199 | 169 | @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
| 200 | 170 | internal fun tryToScrollVertically(distance: Float) {
|
| 201 | 171 | browserToolbar?.let { toolbar ->
|
| ... | ... | @@ -6,14 +6,12 @@ package mozilla.components.browser.toolbar.behavior |
| 6 | 6 | |
| 7 | 7 | import android.content.Context
|
| 8 | 8 | import android.graphics.Bitmap
|
| 9 | -import android.view.Gravity
|
|
| 10 | 9 | import android.view.MotionEvent.ACTION_DOWN
|
| 11 | 10 | import android.view.MotionEvent.ACTION_MOVE
|
| 12 | 11 | import android.widget.FrameLayout
|
| 13 | 12 | import androidx.coordinatorlayout.widget.CoordinatorLayout
|
| 14 | 13 | import androidx.core.view.ViewCompat
|
| 15 | 14 | import androidx.test.ext.junit.runners.AndroidJUnit4
|
| 16 | -import com.google.android.material.snackbar.Snackbar
|
|
| 17 | 15 | import mozilla.components.browser.toolbar.BrowserToolbar
|
| 18 | 16 | import mozilla.components.concept.engine.EngineSession
|
| 19 | 17 | import mozilla.components.concept.engine.EngineView
|
| ... | ... | @@ -474,29 +472,6 @@ class BrowserToolbarBehaviorTest { |
| 474 | 472 | verify(yTranslator).collapseWithAnimation(toolbar)
|
| 475 | 473 | }
|
| 476 | 474 | |
| 477 | - @Test
|
|
| 478 | - fun `Behavior will position snackbar above toolbar`() {
|
|
| 479 | - val behavior = BrowserToolbarBehavior(testContext, null, ToolbarPosition.BOTTOM)
|
|
| 480 | - |
|
| 481 | - val toolbar: BrowserToolbar = mock()
|
|
| 482 | - doReturn(4223).`when`(toolbar).id
|
|
| 483 | - |
|
| 484 | - val layoutParams: CoordinatorLayout.LayoutParams = CoordinatorLayout.LayoutParams(0, 0)
|
|
| 485 | - |
|
| 486 | - val snackbarLayout: Snackbar.SnackbarLayout = mock()
|
|
| 487 | - doReturn(layoutParams).`when`(snackbarLayout).layoutParams
|
|
| 488 | - |
|
| 489 | - behavior.layoutDependsOn(
|
|
| 490 | - parent = mock(),
|
|
| 491 | - child = toolbar,
|
|
| 492 | - dependency = snackbarLayout
|
|
| 493 | - )
|
|
| 494 | - |
|
| 495 | - assertEquals(4223, layoutParams.anchorId)
|
|
| 496 | - assertEquals(Gravity.TOP or Gravity.CENTER_HORIZONTAL, layoutParams.anchorGravity)
|
|
| 497 | - assertEquals(Gravity.TOP or Gravity.CENTER_HORIZONTAL, layoutParams.gravity)
|
|
| 498 | - }
|
|
| 499 | - |
|
| 500 | 475 | @Test
|
| 501 | 476 | fun `Behavior will forceExpand when scrolling up and !shouldScroll if the touch was handled in the browser`() {
|
| 502 | 477 | val behavior = spy(BrowserToolbarBehavior(testContext, null, ToolbarPosition.BOTTOM))
|
| ... | ... | @@ -10,7 +10,7 @@ import mozilla.components.browser.state.state.content.DownloadState |
| 10 | 10 | import mozilla.components.feature.downloads.DownloadDialogFragment.Companion.BYTES_TO_MB_LIMIT
|
| 11 | 11 | import mozilla.components.feature.downloads.DownloadDialogFragment.Companion.KILOBYTE
|
| 12 | 12 | import mozilla.components.feature.downloads.DownloadDialogFragment.Companion.MEGABYTE
|
| 13 | -import mozilla.components.support.utils.DownloadUtils
|
|
| 13 | +import mozilla.components.feature.downloads.ext.realFilenameOrGuessed
|
|
| 14 | 14 | |
| 15 | 15 | /**
|
| 16 | 16 | * This is a general representation of a dialog meant to be used in collaboration with [DownloadsFeature]
|
| ... | ... | @@ -34,11 +34,7 @@ abstract class DownloadDialogFragment : AppCompatDialogFragment() { |
| 34 | 34 | */
|
| 35 | 35 | fun setDownload(download: DownloadState) {
|
| 36 | 36 | val args = arguments ?: Bundle()
|
| 37 | - args.putString(
|
|
| 38 | - KEY_FILE_NAME,
|
|
| 39 | - download.fileName
|
|
| 40 | - ?: DownloadUtils.guessFileName(null, download.destinationDirectory, download.url, download.contentType)
|
|
| 41 | - )
|
|
| 37 | + args.putString(KEY_FILE_NAME, download.realFilenameOrGuessed)
|
|
| 42 | 38 | args.putString(KEY_URL, download.url)
|
| 43 | 39 | args.putLong(KEY_CONTENT_LENGTH, download.contentLength ?: 0)
|
| 44 | 40 | arguments = args
|
| ... | ... | @@ -25,6 +25,7 @@ import mozilla.components.browser.state.state.SessionState |
| 25 | 25 | import mozilla.components.browser.state.state.content.DownloadState
|
| 26 | 26 | import mozilla.components.browser.state.store.BrowserStore
|
| 27 | 27 | import mozilla.components.feature.downloads.DownloadDialogFragment.Companion.FRAGMENT_TAG
|
| 28 | +import mozilla.components.feature.downloads.ext.realFilenameOrGuessed
|
|
| 28 | 29 | import mozilla.components.feature.downloads.manager.DownloadManager
|
| 29 | 30 | import mozilla.components.feature.downloads.manager.noop
|
| 30 | 31 | import mozilla.components.feature.downloads.manager.onDownloadStopped
|
| ... | ... | @@ -41,6 +42,43 @@ import mozilla.components.support.ktx.kotlin.isSameOriginAs |
| 41 | 42 | import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged
|
| 42 | 43 | import mozilla.components.support.utils.Browsers
|
| 43 | 44 | |
| 45 | +/**
|
|
| 46 | + * The name of the file to be downloaded.
|
|
| 47 | + */
|
|
| 48 | +@JvmInline
|
|
| 49 | +value class Filename(val value: String)
|
|
| 50 | + |
|
| 51 | +/**
|
|
| 52 | + * The size of the file to be downloaded expressed as the number of `bytes`.
|
|
| 53 | + * The value will be `0` if the size is unknown.
|
|
| 54 | + */
|
|
| 55 | +@JvmInline
|
|
| 56 | +value class ContentSize(val value: Long)
|
|
| 57 | + |
|
| 58 | +/**
|
|
| 59 | + * The list of all applications that can perform a download, including this application.
|
|
| 60 | + */
|
|
| 61 | +@JvmInline
|
|
| 62 | +value class ThirdPartyDownloaderApps(val value: List<DownloaderApp>)
|
|
| 63 | + |
|
| 64 | +/**
|
|
| 65 | + * Callback for when the user picked a certain application with which to download the current file.
|
|
| 66 | + */
|
|
| 67 | +@JvmInline
|
|
| 68 | +value class ThirdPartyDownloaderAppChosenCallback(val value: (DownloaderApp) -> Unit)
|
|
| 69 | + |
|
| 70 | +/**
|
|
| 71 | + * Callback for when the positive button of a download dialog was tapped.
|
|
| 72 | + */
|
|
| 73 | +@JvmInline
|
|
| 74 | +value class PositiveActionCallback(val value: () -> Unit)
|
|
| 75 | + |
|
| 76 | +/**
|
|
| 77 | + * Callback for when the negative button of a download dialog was tapped.
|
|
| 78 | + */
|
|
| 79 | +@JvmInline
|
|
| 80 | +value class NegativeActionCallback(val value: () -> Unit)
|
|
| 81 | + |
|
| 44 | 82 | /**
|
| 45 | 83 | * Feature implementation to provide download functionality for the selected
|
| 46 | 84 | * session. The feature will subscribe to the selected session and listen
|
| ... | ... | @@ -60,6 +98,10 @@ import mozilla.components.support.utils.Browsers |
| 60 | 98 | * @property promptsStyling styling properties for the dialog.
|
| 61 | 99 | * @property shouldForwardToThirdParties Indicates if downloads should be forward to third party apps,
|
| 62 | 100 | * if there are multiple apps a chooser dialog will shown.
|
| 101 | + * @property customFirstPartyDownloadDialog An optional delegate for showing a dialog for a download
|
|
| 102 | + * that will be processed by the current application.
|
|
| 103 | + * @property customThirdPartyDownloadDialog An optional delegate for showing a dialog for a download
|
|
| 104 | + * that can be processed by multiple installed applications including the current one.
|
|
| 63 | 105 | */
|
| 64 | 106 | @Suppress("LongParameterList", "LargeClass")
|
| 65 | 107 | class DownloadsFeature(
|
| ... | ... | @@ -73,7 +115,11 @@ class DownloadsFeature( |
| 73 | 115 | private val tabId: String? = null,
|
| 74 | 116 | private val fragmentManager: FragmentManager? = null,
|
| 75 | 117 | private val promptsStyling: PromptsStyling? = null,
|
| 76 | - private val shouldForwardToThirdParties: () -> Boolean = { false }
|
|
| 118 | + private val shouldForwardToThirdParties: () -> Boolean = { false },
|
|
| 119 | + private val customFirstPartyDownloadDialog:
|
|
| 120 | + ((Filename, ContentSize, PositiveActionCallback, NegativeActionCallback) -> Unit)? = null,
|
|
| 121 | + private val customThirdPartyDownloadDialog:
|
|
| 122 | + ((ThirdPartyDownloaderApps, ThirdPartyDownloaderAppChosenCallback, NegativeActionCallback) -> Unit)? = null,
|
|
| 77 | 123 | ) : LifecycleAwareFeature, PermissionsFeature {
|
| 78 | 124 | |
| 79 | 125 | var onDownloadStopped: onDownloadStopped
|
| ... | ... | @@ -159,16 +205,45 @@ class DownloadsFeature( |
| 159 | 205 | val shouldShowAppDownloaderDialog = shouldForwardToThirdParties() && apps.size > 1
|
| 160 | 206 | |
| 161 | 207 | return if (shouldShowAppDownloaderDialog) {
|
| 162 | - showAppDownloaderDialog(tab, download, apps)
|
|
| 208 | + when (customThirdPartyDownloadDialog) {
|
|
| 209 | + null -> showAppDownloaderDialog(tab, download, apps)
|
|
| 210 | + else -> customThirdPartyDownloadDialog.invoke(
|
|
| 211 | + ThirdPartyDownloaderApps(apps),
|
|
| 212 | + ThirdPartyDownloaderAppChosenCallback {
|
|
| 213 | + onDownloaderAppSelected(it, tab, download)
|
|
| 214 | + },
|
|
| 215 | + NegativeActionCallback {
|
|
| 216 | + useCases.cancelDownloadRequest.invoke(tab.id, download.id)
|
|
| 217 | + },
|
|
| 218 | + )
|
|
| 219 | + }
|
|
| 220 | + |
|
| 163 | 221 | false
|
| 164 | 222 | } else {
|
| 165 | 223 | if (applicationContext.isPermissionGranted(downloadManager.permissions.asIterable())) {
|
| 166 | - if (fragmentManager != null && !download.skipConfirmation) {
|
|
| 167 | - showDownloadDialog(tab, download)
|
|
| 168 | - false
|
|
| 169 | - } else {
|
|
| 170 | - useCases.consumeDownload(tab.id, download.id)
|
|
| 171 | - startDownload(download)
|
|
| 224 | + when {
|
|
| 225 | + customFirstPartyDownloadDialog != null && !download.skipConfirmation -> {
|
|
| 226 | + customFirstPartyDownloadDialog.invoke(
|
|
| 227 | + Filename(download.realFilenameOrGuessed),
|
|
| 228 | + ContentSize(download.contentLength ?: 0),
|
|
| 229 | + PositiveActionCallback {
|
|
| 230 | + startDownload(download)
|
|
| 231 | + useCases.consumeDownload.invoke(tab.id, download.id)
|
|
| 232 | + },
|
|
| 233 | + NegativeActionCallback {
|
|
| 234 | + useCases.cancelDownloadRequest.invoke(tab.id, download.id)
|
|
| 235 | + },
|
|
| 236 | + )
|
|
| 237 | + false
|
|
| 238 | + }
|
|
| 239 | + fragmentManager != null && !download.skipConfirmation -> {
|
|
| 240 | + showDownloadDialog(tab, download)
|
|
| 241 | + false
|
|
| 242 | + }
|
|
| 243 | + else -> {
|
|
| 244 | + useCases.consumeDownload(tab.id, download.id)
|
|
| 245 | + startDownload(download)
|
|
| 246 | + }
|
|
| 172 | 247 | }
|
| 173 | 248 | } else {
|
| 174 | 249 | onNeedToRequestPermissions(downloadManager.permissions)
|
| ... | ... | @@ -264,25 +339,7 @@ class DownloadsFeature( |
| 264 | 339 | ) {
|
| 265 | 340 | appChooserDialog.setApps(apps)
|
| 266 | 341 | appChooserDialog.onAppSelected = { app ->
|
| 267 | - if (app.packageName == applicationContext.packageName) {
|
|
| 268 | - if (applicationContext.isPermissionGranted(downloadManager.permissions.asIterable())) {
|
|
| 269 | - startDownload(download)
|
|
| 270 | - useCases.consumeDownload(tab.id, download.id)
|
|
| 271 | - } else {
|
|
| 272 | - onNeedToRequestPermissions(downloadManager.permissions)
|
|
| 273 | - }
|
|
| 274 | - } else {
|
|
| 275 | - try {
|
|
| 276 | - applicationContext.startActivity(app.toIntent())
|
|
| 277 | - } catch (error: ActivityNotFoundException) {
|
|
| 278 | - val errorMessage = applicationContext.getString(
|
|
| 279 | - R.string.mozac_feature_downloads_unable_to_open_third_party_app,
|
|
| 280 | - app.name
|
|
| 281 | - )
|
|
| 282 | - Toast.makeText(applicationContext, errorMessage, Toast.LENGTH_SHORT).show()
|
|
| 283 | - }
|
|
| 284 | - useCases.consumeDownload(tab.id, download.id)
|
|
| 285 | - }
|
|
| 342 | + onDownloaderAppSelected(app, tab, download)
|
|
| 286 | 343 | }
|
| 287 | 344 | |
| 288 | 345 | appChooserDialog.onDismiss = {
|
| ... | ... | @@ -294,6 +351,29 @@ class DownloadsFeature( |
| 294 | 351 | }
|
| 295 | 352 | }
|
| 296 | 353 | |
| 354 | + @VisibleForTesting
|
|
| 355 | + internal fun onDownloaderAppSelected(app: DownloaderApp, tab: SessionState, download: DownloadState) {
|
|
| 356 | + if (app.packageName == applicationContext.packageName) {
|
|
| 357 | + if (applicationContext.isPermissionGranted(downloadManager.permissions.asIterable())) {
|
|
| 358 | + startDownload(download)
|
|
| 359 | + useCases.consumeDownload(tab.id, download.id)
|
|
| 360 | + } else {
|
|
| 361 | + onNeedToRequestPermissions(downloadManager.permissions)
|
|
| 362 | + }
|
|
| 363 | + } else {
|
|
| 364 | + try {
|
|
| 365 | + applicationContext.startActivity(app.toIntent())
|
|
| 366 | + } catch (error: ActivityNotFoundException) {
|
|
| 367 | + val errorMessage = applicationContext.getString(
|
|
| 368 | + R.string.mozac_feature_downloads_unable_to_open_third_party_app,
|
|
| 369 | + app.name,
|
|
| 370 | + )
|
|
| 371 | + Toast.makeText(applicationContext, errorMessage, Toast.LENGTH_SHORT).show()
|
|
| 372 | + }
|
|
| 373 | + useCases.consumeDownload(tab.id, download.id)
|
|
| 374 | + }
|
|
| 375 | + }
|
|
| 376 | + |
|
| 297 | 377 | private fun getAppDownloaderDialog() = findPreviousAppDownloaderDialogFragment()
|
| 298 | 378 | ?: DownloadAppChooserDialog.newInstance(
|
| 299 | 379 | promptsStyling?.gravity,
|
| ... | ... | @@ -47,3 +47,6 @@ internal fun DownloadState.withResponse(headers: Headers, stream: InputStream?): |
| 47 | 47 | contentLength = contentLength ?: headers[CONTENT_LENGTH]?.toLongOrNull()
|
| 48 | 48 | )
|
| 49 | 49 | }
|
| 50 | + |
|
| 51 | +internal val DownloadState.realFilenameOrGuessed
|
|
| 52 | + get() = fileName ?: DownloadUtils.guessFileName(null, destinationDirectory, url, contentType) |
| ... | ... | @@ -16,10 +16,10 @@ import mozilla.components.feature.downloads.R |
| 16 | 16 | /**
|
| 17 | 17 | * An adapter for displaying the applications that can perform downloads.
|
| 18 | 18 | */
|
| 19 | -internal class DownloaderAppAdapter(
|
|
| 19 | +class DownloaderAppAdapter(
|
|
| 20 | 20 | context: Context,
|
| 21 | 21 | private val apps: List<DownloaderApp>,
|
| 22 | - val onAppSelected: ((DownloaderApp) -> Unit)
|
|
| 22 | + val onAppSelected: ((DownloaderApp) -> Unit),
|
|
| 23 | 23 | ) : RecyclerView.Adapter<DownloaderAppViewHolder>() {
|
| 24 | 24 | |
| 25 | 25 | private val inflater = LayoutInflater.from(context)
|
| ... | ... | @@ -49,11 +49,14 @@ internal class DownloaderAppAdapter( |
| 49 | 49 | /**
|
| 50 | 50 | * View holder for a [DownloaderApp] item.
|
| 51 | 51 | */
|
| 52 | -internal class DownloaderAppViewHolder(
|
|
| 52 | +class DownloaderAppViewHolder(
|
|
| 53 | 53 | itemView: View,
|
| 54 | 54 | val nameLabel: TextView,
|
| 55 | - val iconImage: ImageView
|
|
| 55 | + val iconImage: ImageView,
|
|
| 56 | 56 | ) : RecyclerView.ViewHolder(itemView) {
|
| 57 | + /**
|
|
| 58 | + * Show a certain downloader application in the current View.
|
|
| 59 | + */
|
|
| 57 | 60 | fun bind(app: DownloaderApp, onAppSelected: ((DownloaderApp) -> Unit)) {
|
| 58 | 61 | itemView.app = app
|
| 59 | 62 | itemView.setOnClickListener {
|
| ... | ... | @@ -31,6 +31,7 @@ tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach { |
| 31 | 31 | dependencies {
|
| 32 | 32 | implementation project(':browser-state')
|
| 33 | 33 | implementation project(':concept-engine')
|
| 34 | + implementation project(':feature-session')
|
|
| 34 | 35 | implementation project(':lib-state')
|
| 35 | 36 | implementation project(':support-ktx')
|
| 36 | 37 | implementation project(':support-utils')
|
| ... | ... | @@ -46,6 +47,7 @@ dependencies { |
| 46 | 47 | testImplementation Dependencies.testing_coroutines
|
| 47 | 48 | testImplementation Dependencies.testing_robolectric
|
| 48 | 49 | testImplementation Dependencies.testing_mockito
|
| 50 | + testImplementation project(':feature-session')
|
|
| 49 | 51 | testImplementation project(':support-test')
|
| 50 | 52 | testImplementation project(':support-test-libstate')
|
| 51 | 53 |
| ... | ... | @@ -71,6 +71,8 @@ import mozilla.components.feature.prompts.login.LoginExceptions |
| 71 | 71 | import mozilla.components.feature.prompts.login.LoginPicker
|
| 72 | 72 | import mozilla.components.feature.prompts.share.DefaultShareDelegate
|
| 73 | 73 | import mozilla.components.feature.prompts.share.ShareDelegate
|
| 74 | +import mozilla.components.feature.session.SessionUseCases
|
|
| 75 | +import mozilla.components.feature.session.SessionUseCases.ExitFullScreenUseCase
|
|
| 74 | 76 | import mozilla.components.lib.state.ext.flowScoped
|
| 75 | 77 | import mozilla.components.support.base.feature.ActivityResultHandler
|
| 76 | 78 | import mozilla.components.support.base.feature.LifecycleAwareFeature
|
| ... | ... | @@ -111,6 +113,7 @@ internal const val FRAGMENT_TAG = "mozac_feature_prompt_dialog" |
| 111 | 113 | * @property fragmentManager The [FragmentManager] to be used when displaying
|
| 112 | 114 | * a dialog (fragment).
|
| 113 | 115 | * @property shareDelegate Delegate used to display share sheet.
|
| 116 | + * @property exitFullscreenUsecase Usecase allowing to exit browser tabs' fullscreen mode.
|
|
| 114 | 117 | * @property loginStorageDelegate Delegate used to access login storage. If null,
|
| 115 | 118 | * 'save login'prompts will not be shown.
|
| 116 | 119 | * @property isSaveLoginEnabled A callback invoked when a login prompt is triggered. If false,
|
| ... | ... | @@ -144,6 +147,7 @@ class PromptFeature private constructor( |
| 144 | 147 | private var customTabId: String?,
|
| 145 | 148 | private val fragmentManager: FragmentManager,
|
| 146 | 149 | private val shareDelegate: ShareDelegate,
|
| 150 | + private val exitFullscreenUsecase: ExitFullScreenUseCase = SessionUseCases(store).exitFullscreen,
|
|
| 147 | 151 | override val creditCardValidationDelegate: CreditCardValidationDelegate? = null,
|
| 148 | 152 | override val loginValidationDelegate: LoginValidationDelegate? = null,
|
| 149 | 153 | private val isSaveLoginEnabled: () -> Boolean = { false },
|
| ... | ... | @@ -184,6 +188,7 @@ class PromptFeature private constructor( |
| 184 | 188 | customTabId: String? = null,
|
| 185 | 189 | fragmentManager: FragmentManager,
|
| 186 | 190 | shareDelegate: ShareDelegate = DefaultShareDelegate(),
|
| 191 | + exitFullscreenUsecase: ExitFullScreenUseCase = SessionUseCases(store).exitFullscreen,
|
|
| 187 | 192 | creditCardValidationDelegate: CreditCardValidationDelegate? = null,
|
| 188 | 193 | loginValidationDelegate: LoginValidationDelegate? = null,
|
| 189 | 194 | isSaveLoginEnabled: () -> Boolean = { false },
|
| ... | ... | @@ -202,6 +207,7 @@ class PromptFeature private constructor( |
| 202 | 207 | customTabId = customTabId,
|
| 203 | 208 | fragmentManager = fragmentManager,
|
| 204 | 209 | shareDelegate = shareDelegate,
|
| 210 | + exitFullscreenUsecase = exitFullscreenUsecase,
|
|
| 205 | 211 | creditCardValidationDelegate = creditCardValidationDelegate,
|
| 206 | 212 | loginValidationDelegate = loginValidationDelegate,
|
| 207 | 213 | isSaveLoginEnabled = isSaveLoginEnabled,
|
| ... | ... | @@ -222,6 +228,7 @@ class PromptFeature private constructor( |
| 222 | 228 | customTabId: String? = null,
|
| 223 | 229 | fragmentManager: FragmentManager,
|
| 224 | 230 | shareDelegate: ShareDelegate = DefaultShareDelegate(),
|
| 231 | + exitFullscreenUsecase: ExitFullScreenUseCase = SessionUseCases(store).exitFullscreen,
|
|
| 225 | 232 | creditCardValidationDelegate: CreditCardValidationDelegate? = null,
|
| 226 | 233 | loginValidationDelegate: LoginValidationDelegate? = null,
|
| 227 | 234 | isSaveLoginEnabled: () -> Boolean = { false },
|
| ... | ... | @@ -240,6 +247,7 @@ class PromptFeature private constructor( |
| 240 | 247 | customTabId = customTabId,
|
| 241 | 248 | fragmentManager = fragmentManager,
|
| 242 | 249 | shareDelegate = shareDelegate,
|
| 250 | + exitFullscreenUsecase = exitFullscreenUsecase,
|
|
| 243 | 251 | creditCardValidationDelegate = creditCardValidationDelegate,
|
| 244 | 252 | loginValidationDelegate = loginValidationDelegate,
|
| 245 | 253 | isSaveLoginEnabled = isSaveLoginEnabled,
|
| ... | ... | @@ -420,6 +428,10 @@ class PromptFeature private constructor( |
| 420 | 428 | internal fun onPromptRequested(session: SessionState) {
|
| 421 | 429 | // Some requests are handle with intents
|
| 422 | 430 | session.content.promptRequests.lastOrNull()?.let { promptRequest ->
|
| 431 | + store.state.findTabOrCustomTabOrSelectedTab(customTabId)?.let {
|
|
| 432 | + exitFullscreenUsecase(it.id)
|
|
| 433 | + }
|
|
| 434 | + |
|
| 423 | 435 | when (promptRequest) {
|
| 424 | 436 | is File -> filePicker.handleFileRequest(promptRequest)
|
| 425 | 437 | is Share -> handleShareRequest(promptRequest, session)
|