Skip to content

Commit e25e9e1

Browse files
Fix(#227): Crash when launching MoonPay Widget (#126)
Refactored the MoonPay widget integration to a dedicated Composable (`MoonPayWidgetLauncher`) to ensure proper state handling and loading behavior. - Added `MoonPayWidgetLauncher.kt` with Composable and ViewModel for managing signed URL fetch and Custom Tab launch. - Ensures loading state covers the whole screen, preventing UI crashes. - Removed legacy static method `LegacyNavigation.showMoonPayWidget`. - Updated `ReceiveDialog.kt` and `BuyLitecoinScreen.kt` to use the new Composable. - Minor cleanup in `LegacyNavigation.kt`.
1 parent ef353e3 commit e25e9e1

File tree

4 files changed

+123
-81
lines changed

4 files changed

+123
-81
lines changed
Lines changed: 1 addition & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,14 @@
11
package com.brainwallet.navigation
22

33
import android.app.Activity
4-
import android.app.ProgressDialog
54
import android.content.Context
65
import android.content.Intent
7-
import android.widget.Toast
8-
import androidx.browser.customtabs.CustomTabsIntent
9-
import androidx.core.net.toUri
10-
import com.brainwallet.BuildConfig
116
import com.brainwallet.R
12-
import com.brainwallet.data.repository.LtcRepository
13-
import com.brainwallet.di.AppModule.getKoinInstance
147
import com.brainwallet.presenter.activities.BreadActivity
158
import com.brainwallet.ui.BrainwalletActivity
16-
import kotlinx.coroutines.CoroutineScope
17-
import kotlinx.coroutines.Dispatchers
18-
import kotlinx.coroutines.launch
19-
import kotlinx.coroutines.withContext
209
import timber.log.Timber
2110
import com.google.firebase.analytics.FirebaseAnalytics
2211

23-
2412
//provide old navigation using intent activity
2513
object LegacyNavigation {
2614

@@ -67,52 +55,4 @@ object LegacyNavigation {
6755
) = BrainwalletActivity.createIntent(context, destination).also {
6856
context.startActivity(it)
6957
}
70-
71-
@JvmOverloads
72-
@JvmStatic
73-
fun showMoonPayWidget(
74-
context: Context,
75-
params: Map<String, String> = mapOf(),
76-
isDarkMode: Boolean = true,
77-
) {
78-
val ltcRepository: LtcRepository = getKoinInstance()
79-
val progressDialog = ProgressDialog(context).apply {
80-
setMessage(context.getString(R.string.loading))
81-
setCancelable(false)
82-
show()
83-
}
84-
85-
CoroutineScope(Dispatchers.Main).launch {
86-
try {
87-
val result = withContext(Dispatchers.IO) {
88-
ltcRepository.fetchMoonpaySignedUrl(
89-
params = params.toMutableMap().apply {
90-
put("theme", if (isDarkMode) "dark" else "light")
91-
}
92-
)
93-
}
94-
95-
val widgetUri = result.toUri().buildUpon()
96-
.apply {
97-
if (BuildConfig.DEBUG) {
98-
authority("buy-sandbox.moonpay.com")//replace base url from buy.moonpay.com
99-
}
100-
}
101-
.build()
102-
val intent = CustomTabsIntent.Builder()
103-
.setColorScheme(if (isDarkMode) CustomTabsIntent.COLOR_SCHEME_DARK else CustomTabsIntent.COLOR_SCHEME_LIGHT)
104-
.build()
105-
intent.launchUrl(context, widgetUri)
106-
} catch (e: Exception) {
107-
Toast.makeText(
108-
context,
109-
"Failed to load: ${e.message}, please try again later",
110-
Toast.LENGTH_LONG
111-
).show()
112-
} finally {
113-
progressDialog.dismiss()
114-
}
115-
}
116-
}
117-
118-
}
58+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package com.brainwallet.navigation
2+
3+
import android.net.Uri
4+
import android.widget.Toast
5+
import androidx.browser.customtabs.CustomTabsIntent
6+
import androidx.compose.animation.AnimatedVisibility
7+
import androidx.compose.runtime.Composable
8+
import androidx.compose.runtime.LaunchedEffect
9+
import androidx.compose.runtime.collectAsState
10+
import androidx.compose.runtime.getValue
11+
import androidx.compose.ui.Modifier
12+
import androidx.compose.ui.platform.LocalContext
13+
import androidx.core.net.toUri
14+
import androidx.lifecycle.ViewModel
15+
import androidx.lifecycle.viewModelScope
16+
import com.brainwallet.BuildConfig
17+
import com.brainwallet.data.repository.LtcRepository
18+
import com.brainwallet.data.repository.SettingRepository
19+
import com.brainwallet.ui.composable.LoadingDialog
20+
import kotlinx.coroutines.CoroutineDispatcher
21+
import kotlinx.coroutines.Dispatchers
22+
import kotlinx.coroutines.channels.Channel
23+
import kotlinx.coroutines.flow.MutableStateFlow
24+
import kotlinx.coroutines.flow.asStateFlow
25+
import kotlinx.coroutines.flow.receiveAsFlow
26+
import kotlinx.coroutines.flow.update
27+
import kotlinx.coroutines.launch
28+
import org.koin.android.annotation.KoinViewModel
29+
import org.koin.compose.viewmodel.koinViewModel
30+
31+
@KoinViewModel
32+
class MoonPayWidgetLauncherViewModel(
33+
private val settingRepository: SettingRepository,
34+
private val ltcRepository: LtcRepository,
35+
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
36+
) : ViewModel() {
37+
38+
private val _isLoading = MutableStateFlow(false)
39+
val isLoading = _isLoading.asStateFlow()
40+
41+
private val _result = Channel<Result<Pair<Boolean, Uri>>>()
42+
val result = _result.receiveAsFlow()
43+
44+
fun launch(params: Map<String, String>) {
45+
_isLoading.update { true }
46+
viewModelScope.launch(ioDispatcher) {
47+
val isDarkMode = settingRepository.isDarkMode()
48+
_result.send(ltcRepository.runCatching {
49+
val result = ltcRepository.fetchMoonpaySignedUrl(
50+
params = params.toMutableMap().apply {
51+
put("theme", if (isDarkMode) "dark" else "light")
52+
}
53+
)
54+
isDarkMode to result.toUri().buildUpon()
55+
.apply {
56+
if (BuildConfig.DEBUG) {
57+
authority("buy-sandbox.moonpay.com")//replace base url from buy.moonpay.com
58+
}
59+
}
60+
.build()
61+
})
62+
_isLoading.update { false }
63+
}
64+
}
65+
}
66+
67+
@Composable
68+
fun MoonPayWidgetLauncher(
69+
modifier: Modifier = Modifier,
70+
viewModel: MoonPayWidgetLauncherViewModel = koinViewModel(),
71+
onResult: () -> Unit = {}
72+
) {
73+
val context = LocalContext.current
74+
val isLoading by viewModel.isLoading.collectAsState()
75+
76+
AnimatedVisibility(isLoading, modifier = modifier) {
77+
LoadingDialog()
78+
}
79+
80+
LaunchedEffect(Unit) {
81+
viewModel.result.collect { result ->
82+
result.fold(
83+
onSuccess = { (isDarkMode, uri) ->
84+
val intent = CustomTabsIntent.Builder()
85+
.setColorScheme(
86+
if (isDarkMode) CustomTabsIntent.COLOR_SCHEME_DARK
87+
else CustomTabsIntent.COLOR_SCHEME_LIGHT
88+
)
89+
.build()
90+
intent.launchUrl(context, uri)
91+
},
92+
onFailure = { e ->
93+
Toast.makeText(
94+
context,
95+
"Failed to load: ${e.message}, please try again later",
96+
Toast.LENGTH_LONG
97+
).show()
98+
}
99+
)
100+
onResult.invoke()
101+
}
102+
}
103+
}

app/src/main/java/com/brainwallet/ui/screens/buylitecoin/BuyLitecoinScreen.kt

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import androidx.compose.foundation.layout.padding
99
import androidx.compose.foundation.text.KeyboardOptions
1010
import androidx.compose.material.icons.Icons
1111
import androidx.compose.material.icons.automirrored.filled.ArrowBack
12-
import androidx.compose.material.icons.filled.Done
1312
import androidx.compose.material3.Icon
1413
import androidx.compose.material3.IconButton
1514
import androidx.compose.material3.MaterialTheme
@@ -26,22 +25,22 @@ import androidx.compose.ui.res.stringResource
2625
import androidx.compose.ui.text.input.KeyboardType
2726
import androidx.compose.ui.unit.dp
2827
import com.brainwallet.R
29-
import com.brainwallet.navigation.LegacyNavigation
28+
import com.brainwallet.navigation.MoonPayWidgetLauncher
29+
import com.brainwallet.navigation.MoonPayWidgetLauncherViewModel
3030
import com.brainwallet.navigation.OnNavigate
3131
import com.brainwallet.navigation.UiEffect
3232
import com.brainwallet.ui.composable.BrainwalletScaffold
3333
import com.brainwallet.ui.composable.BrainwalletTopAppBar
3434
import com.brainwallet.ui.composable.LargeButton
35-
import com.brainwallet.ui.composable.LoadingDialog
36-
import com.brainwallet.ui.screens.home.receive.ReceiveDialogEvent
3735
import com.brainwallet.ui.theme.BrainwalletTheme
38-
import org.koin.compose.koinInject
36+
import org.koin.compose.viewmodel.koinViewModel
3937

4038
//TODO: wip
4139
@Composable
4240
fun BuyLitecoinScreen(
4341
onNavigate: OnNavigate,
44-
viewModel: BuyLitecoinViewModel = koinInject()
42+
viewModel: BuyLitecoinViewModel = koinViewModel(),
43+
moonPayWidgetLauncherViewModel: MoonPayWidgetLauncherViewModel = koinViewModel()
4544
) {
4645
val state by viewModel.state.collectAsState()
4746
val loadingState by viewModel.loadingState.collectAsState()
@@ -148,21 +147,20 @@ fun BuyLitecoinScreen(
148147
modifier = Modifier.align(Alignment.BottomCenter),
149148
enabled = loadingState.visible.not(),
150149
onClick = {
151-
//open bread activity first then open moonpay widget
152-
LegacyNavigation.restartBreadActivity(context)
153-
LegacyNavigation.showMoonPayWidget(
154-
context = context,
150+
moonPayWidgetLauncherViewModel.launch(
155151
params = mapOf(
156152
"baseCurrencyCode" to appSetting.currency.code,
157153
"baseCurrencyAmount" to state.fiatAmount.toString(),
158154
"language" to appSetting.languageCode,
159155
"walletAddress" to state.address
160-
)
156+
),
157+
161158
)
162159
}
163160
) {
164161
Text(stringResource(R.string.buy_litecoin_button_moonpay))
165162
}
166163
}
164+
MoonPayWidgetLauncher(viewModel = moonPayWidgetLauncherViewModel)
167165
}
168166
}

app/src/main/java/com/brainwallet/ui/screens/home/receive/ReceiveDialog.kt

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@ import androidx.fragment.app.FragmentManager
6363
import com.brainwallet.R
6464
import com.brainwallet.data.model.getFormattedText
6565
import com.brainwallet.data.model.isCustom
66-
import com.brainwallet.navigation.LegacyNavigation
66+
import com.brainwallet.navigation.MoonPayWidgetLauncher
67+
import com.brainwallet.navigation.MoonPayWidgetLauncherViewModel
6768
import com.brainwallet.navigation.UiEffect
6869
import com.brainwallet.ui.composable.MoonpayBuyButton
6970
import com.brainwallet.ui.composable.VerticalWheelPicker
@@ -77,13 +78,14 @@ import kotlinx.coroutines.flow.debounce
7778
import kotlinx.coroutines.flow.distinctUntilChanged
7879
import kotlinx.coroutines.flow.filter
7980
import org.koin.android.ext.android.inject
80-
import org.koin.compose.koinInject
81+
import org.koin.compose.viewmodel.koinViewModel
8182
import timber.log.Timber
8283

8384
@Composable
8485
fun ReceiveDialog(
8586
onDismissRequest: () -> Unit,
86-
viewModel: ReceiveDialogViewModel = koinInject()
87+
viewModel: ReceiveDialogViewModel = koinViewModel(),
88+
moonPayWidgetLauncherViewModel: MoonPayWidgetLauncherViewModel = koinViewModel()
8789
) {
8890
val state by viewModel.state.collectAsState()
8991
val loadingState by viewModel.loadingState.collectAsState()
@@ -369,24 +371,23 @@ fun ReceiveDialog(
369371
//todo: revisit this later
370372
//viewModel.onEvent(ReceiveDialogEvent.OnMoonpayButtonClick)
371373

372-
LegacyNavigation.showMoonPayWidget(
373-
context = context,
374+
moonPayWidgetLauncherViewModel.launch(
374375
params = mapOf(
375376
"baseCurrencyCode" to state.selectedFiatCurrency.code,
376377
"baseCurrencyAmount" to state.fiatAmount.toString(),
377378
"language" to appSetting.languageCode,
378379
"walletAddress" to state.address,
379380
),
380-
isDarkMode = appSetting.isDarkMode
381381
)
382-
onDismissRequest.invoke()
383382
},
384383
)
385384

386385
// }
387386
}
388-
389-
387+
MoonPayWidgetLauncher(
388+
viewModel = moonPayWidgetLauncherViewModel,
389+
onResult = onDismissRequest
390+
)
390391
}
391392
}
392393

0 commit comments

Comments
 (0)