Commits:
-
8c3c4617
by Mugurell at 2023-03-15T18:01:20+00:00
Bug 1783561 - Stop BrowserToolbarBehavior positioning the snackbar
The snackbar is placed depending on the toolbar so the relation should be
reversed. The toolbar should not know about and control the snackbar.
There could be other siblings that the snackbar wants to position itself by
and so removing this responsability from BrowserToolbarBehavior will help
better support current and future flows.
Backport of https://github.com/mozilla-mobile/firefox-android/commit/04a464e66a95892d9ac7929974da51116cd1585d
-
e135dc89
by Mugurell at 2023-03-15T18:01:54+00:00
Bug 1812518 - Allow a custom View for first party downloads
This will allow clients easily implement their own UI.
Backport of https://github.com/mozilla-mobile/firefox-android/commit/e678610bc10df96b8543c26fb5eb7c1ba26e3e20
-
c5c27b90
by Mugurell at 2023-03-15T18:01:54+00:00
Bug 1783561 - Allow a custom View for 3rd party downloads
This will allow clients easily implement their own UI
Backport of https://github.com/mozilla-mobile/firefox-android/commit/c53dd65718579006e174803acf683ffd44075049
-
952ef13c
by Mugurell at 2023-03-15T18:01:54+00:00
Improve prompts UX
Backport of https://github.com/mozilla-mobile/firefox-android/commit/1dc21a3786506200be124733e654dff8f39b5395
8 changed files:
Changes:
components/browser/toolbar/src/main/java/mozilla/components/browser/toolbar/behavior/BrowserToolbarBehavior.kt
... |
... |
@@ -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 ->
|
components/browser/toolbar/src/test/java/mozilla/components/browser/toolbar/behavior/BrowserToolbarBehaviorTest.kt
... |
... |
@@ -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))
|
components/feature/downloads/src/main/java/mozilla/components/feature/downloads/DownloadDialogFragment.kt
... |
... |
@@ -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
|
components/feature/downloads/src/main/java/mozilla/components/feature/downloads/DownloadsFeature.kt
... |
... |
@@ -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,
|
components/feature/downloads/src/main/java/mozilla/components/feature/downloads/ext/DownloadState.kt
... |
... |
@@ -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) |
components/feature/downloads/src/main/java/mozilla/components/feature/downloads/ui/DownloaderAppAdapter.kt
... |
... |
@@ -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 {
|
components/feature/prompts/build.gradle
... |
... |
@@ -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
|
|
components/feature/prompts/src/main/java/mozilla/components/feature/prompts/PromptFeature.kt
... |
... |
@@ -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)
|
|