@@ -6,6 +6,7 @@ package mozilla.components.feature.addons.ui
6
6
7
7
import android.annotation.SuppressLint
8
8
import android.app.Dialog
9
+ import android.graphics.Bitmap
9
10
import android.graphics.Color
10
11
import android.graphics.drawable.BitmapDrawable
11
12
import android.graphics.drawable.ColorDrawable
@@ -25,6 +26,7 @@ import androidx.annotation.VisibleForTesting
25
26
import androidx.appcompat.app.AppCompatDialogFragment
26
27
import androidx.appcompat.widget.AppCompatCheckBox
27
28
import androidx.core.content.ContextCompat
29
+ import androidx.fragment.app.FragmentManager
28
30
import kotlinx.android.synthetic.main.mozac_feature_addons_fragment_dialog_addon_installed.view.*
29
31
import kotlinx.coroutines.CoroutineScope
30
32
import kotlinx.coroutines.Dispatchers
@@ -38,26 +40,33 @@ import mozilla.components.support.ktx.android.content.appName
38
40
import mozilla.components.support.ktx.android.content.res.resolveAttribute
39
41
import java.io.IOException
40
42
43
+ @VisibleForTesting internal const val KEY_INSTALLED_ADDON = " KEY_ADDON"
41
44
private const val KEY_DIALOG_GRAVITY = " KEY_DIALOG_GRAVITY"
42
45
private const val KEY_DIALOG_WIDTH_MATCH_PARENT = " KEY_DIALOG_WIDTH_MATCH_PARENT"
43
46
private const val KEY_CONFIRM_BUTTON_BACKGROUND_COLOR = " KEY_CONFIRM_BUTTON_BACKGROUND_COLOR"
44
47
private const val KEY_CONFIRM_BUTTON_TEXT_COLOR = " KEY_CONFIRM_BUTTON_TEXT_COLOR"
45
48
private const val KEY_CONFIRM_BUTTON_RADIUS = " KEY_CONFIRM_BUTTON_RADIUS"
49
+ @VisibleForTesting internal const val KEY_ICON = " KEY_ICON"
50
+
46
51
private const val DEFAULT_VALUE = Int .MAX_VALUE
47
52
48
53
/* *
49
54
* A dialog that shows [Addon] installation confirmation.
50
55
*/
51
- class AddonInstallationDialogFragment (
52
- private val addonCollectionProvider : AddonCollectionProvider
53
- ) : AppCompatDialogFragment() {
56
+ class AddonInstallationDialogFragment : AppCompatDialogFragment () {
54
57
private val scope = CoroutineScope (Dispatchers .IO )
58
+ @VisibleForTesting internal var iconJob: Job ? = null
55
59
private val logger = Logger (" AddonInstallationDialogFragment" )
56
60
/* *
57
61
* A lambda called when the confirm button is clicked.
58
62
*/
59
63
var onConfirmButtonClicked: ((Addon , Boolean ) -> Unit )? = null
60
64
65
+ /* *
66
+ * Reference to the application's [AddonCollectionProvider] to fetch add-on icons.
67
+ */
68
+ var addonCollectionProvider: AddonCollectionProvider ? = null
69
+
61
70
private val safeArguments get() = requireNotNull(arguments)
62
71
63
72
internal val addon get() = requireNotNull(safeArguments.getParcelable<Addon >(KEY_ADDON ))
@@ -91,6 +100,11 @@ class AddonInstallationDialogFragment(
91
100
DEFAULT_VALUE
92
101
)
93
102
103
+ override fun onStop () {
104
+ super .onStop()
105
+ iconJob?.cancel()
106
+ }
107
+
94
108
override fun onCreateDialog (savedInstanceState : Bundle ? ): Dialog {
95
109
val sheetDialog = Dialog (requireContext())
96
110
sheetDialog.requestWindowFeature(Window .FEATURE_NO_TITLE )
@@ -144,7 +158,12 @@ class AddonInstallationDialogFragment(
144
158
requireContext().appName
145
159
)
146
160
147
- fetchIcon(addon, rootView.icon)
161
+ val icon = safeArguments.getParcelable<Bitmap >(KEY_ICON )
162
+ if (icon != null ) {
163
+ rootView.icon.setImageDrawable(BitmapDrawable (resources, icon))
164
+ } else {
165
+ iconJob = fetchIcon(addon, rootView.icon)
166
+ }
148
167
149
168
val allowedInPrivateBrowsing = rootView.findViewById<AppCompatCheckBox >(R .id.allow_in_private_browsing)
150
169
allowedInPrivateBrowsing.setOnCheckedChangeListener { _, isChecked ->
@@ -188,9 +207,10 @@ class AddonInstallationDialogFragment(
188
207
internal fun fetchIcon (addon : Addon , iconView : ImageView , scope : CoroutineScope = this.scope): Job {
189
208
return scope.launch {
190
209
try {
191
- val iconBitmap = addonCollectionProvider.getAddonIconBitmap(addon)
210
+ val iconBitmap = addonCollectionProvider? .getAddonIconBitmap(addon)
192
211
iconBitmap?.let {
193
212
scope.launch(Dispatchers .Main ) {
213
+ safeArguments.putParcelable(KEY_ICON , it)
194
214
iconView.setImageDrawable(BitmapDrawable (iconView.resources, it))
195
215
}
196
216
}
@@ -206,6 +226,19 @@ class AddonInstallationDialogFragment(
206
226
}
207
227
}
208
228
229
+ override fun show (manager : FragmentManager , tag : String? ) {
230
+ // This dialog is shown as a result of an async operation (installing
231
+ // an add-on). Once installation succeeds, the activity may already be
232
+ // in the process of being destroyed. Since the dialog doesn't have any
233
+ // state we need to keep, and since it's also fine to not display the
234
+ // dialog at all in case the user navigates away, we can simply use
235
+ // commitAllowingStateLoss here to prevent crashing on commit:
236
+ // https://github.com/mozilla-mobile/android-components/issues/7782
237
+ val ft = manager.beginTransaction()
238
+ ft.add(this , tag)
239
+ ft.commitAllowingStateLoss()
240
+ }
241
+
209
242
@Suppress(" LongParameterList" )
210
243
companion object {
211
244
/* *
@@ -224,11 +257,11 @@ class AddonInstallationDialogFragment(
224
257
onConfirmButtonClicked : ((Addon , Boolean ) -> Unit )? = null
225
258
): AddonInstallationDialogFragment {
226
259
227
- val fragment = AddonInstallationDialogFragment (addonCollectionProvider )
260
+ val fragment = AddonInstallationDialogFragment ()
228
261
val arguments = fragment.arguments ? : Bundle ()
229
262
230
263
arguments.apply {
231
- putParcelable(KEY_ADDON , addon)
264
+ putParcelable(KEY_INSTALLED_ADDON , addon)
232
265
233
266
promptsStyling?.gravity?.apply {
234
267
putInt(KEY_DIALOG_GRAVITY , this )
@@ -246,6 +279,7 @@ class AddonInstallationDialogFragment(
246
279
}
247
280
fragment.onConfirmButtonClicked = onConfirmButtonClicked
248
281
fragment.arguments = arguments
282
+ fragment.addonCollectionProvider = addonCollectionProvider
249
283
return fragment
250
284
}
251
285
}
0 commit comments