From 650feeb6574645180d731256f63060ba03554ac2 Mon Sep 17 00:00:00 2001 From: kcw-grunt Date: Fri, 8 May 2026 15:27:54 -0700 Subject: [PATCH 1/3] Added remote config to shop --- .../repository/ShopProxyRepositoryImpl.kt | 47 +++++++++++++++++++ .../com/brainwallet/di/RemoteConfigModule.kt | 8 +++- .../bentosections/shopbento/ShopBentoState.kt | 3 +- .../shopbento/ShopBentoViewModel.kt | 11 +++++ .../main/res/xml/remote_config_defaults.xml | 2 +- 5 files changed, 68 insertions(+), 3 deletions(-) create mode 100644 app/src/main/java/com/brainwallet/data/repository/ShopProxyRepositoryImpl.kt diff --git a/app/src/main/java/com/brainwallet/data/repository/ShopProxyRepositoryImpl.kt b/app/src/main/java/com/brainwallet/data/repository/ShopProxyRepositoryImpl.kt new file mode 100644 index 00000000..ee3505db --- /dev/null +++ b/app/src/main/java/com/brainwallet/data/repository/ShopProxyRepositoryImpl.kt @@ -0,0 +1,47 @@ +package com.brainwallet.data.repository +import com.brainwallet.data.source.RemoteConfigSource +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json +import timber.log.Timber + +@Serializable +data class ShopProxy( + @SerialName("widget_url") val widget: String = "" +) + +interface ShopProxyRepository { + val shopProxy: StateFlow> + suspend fun refresh() +} + +class ShopProxyRepositoryImpl( + private val remoteConfigSource: RemoteConfigSource, + private val json: Json, +) : ShopProxyRepository { + + private val _shopProxy = MutableStateFlow>(emptyList()) + override val shopProxy: StateFlow> = _shopProxy.asStateFlow() + + override suspend fun refresh() { + runCatching { remoteConfigSource.fetchAndActivate() } + .onFailure { Timber.e(it, "RemoteConfig fetch failed") } + + val rawJson = remoteConfigSource.getString(RemoteConfigSource.PATH_SHOP_CONTENT) + Timber.d("ShopProxy rawJson: '$rawJson'") + + runCatching { + val wrapper = json.decodeFromString(rawJson) + Timber.d("ShopProxy parsed: ${wrapper.shopProxy}") + _shopProxy.value = wrapper.shopProxy + }.onFailure { Timber.e(it, "RemoteConfig parse failed") } + } + + @Serializable + private data class ShopProxyWrapper( + @SerialName("shop_data") val shopProxy: List = emptyList() + ) +} diff --git a/app/src/main/java/com/brainwallet/di/RemoteConfigModule.kt b/app/src/main/java/com/brainwallet/di/RemoteConfigModule.kt index be82670d..26a8a1ca 100644 --- a/app/src/main/java/com/brainwallet/di/RemoteConfigModule.kt +++ b/app/src/main/java/com/brainwallet/di/RemoteConfigModule.kt @@ -3,6 +3,8 @@ package com.brainwallet.di import com.brainwallet.data.repository.FirebaseRemoteConfigRepositoryImpl import com.brainwallet.data.repository.HubContentRepository import com.brainwallet.data.repository.HubContentRepositoryImpl +import com.brainwallet.data.repository.ShopProxyRepository +import com.brainwallet.data.repository.ShopProxyRepositoryImpl import com.brainwallet.data.source.RemoteConfigSource import com.google.firebase.remoteconfig.FirebaseRemoteConfig import org.koin.dsl.module @@ -11,10 +13,14 @@ val remoteConfigModule = module { single { FirebaseRemoteConfig.getInstance().apply { setDefaultsAsync( - mapOf(RemoteConfigSource.KEY_FEATURE_GAMEHUB_CONTENT to "[]") + mapOf( + RemoteConfigSource.KEY_FEATURE_GAMEHUB_CONTENT to "[]", + RemoteConfigSource.PATH_SHOP_CONTENT to "{\"shop_data\":{}}" + ) ) } } single { FirebaseRemoteConfigRepositoryImpl(get()) } single { HubContentRepositoryImpl(get(), get()) } + single { ShopProxyRepositoryImpl(get(), get()) } } diff --git a/app/src/main/java/com/brainwallet/ui/bentosections/shopbento/ShopBentoState.kt b/app/src/main/java/com/brainwallet/ui/bentosections/shopbento/ShopBentoState.kt index cd924d48..eeda54f7 100644 --- a/app/src/main/java/com/brainwallet/ui/bentosections/shopbento/ShopBentoState.kt +++ b/app/src/main/java/com/brainwallet/ui/bentosections/shopbento/ShopBentoState.kt @@ -5,5 +5,6 @@ data class ShopBentoState( val regionData: String = "", val countryIso: String = "US", val darkMode: Boolean = true, - val shouldSlide: Boolean = false + val shouldSlide: Boolean = false, + val shopBaseUrl: String = "" ) diff --git a/app/src/main/java/com/brainwallet/ui/bentosections/shopbento/ShopBentoViewModel.kt b/app/src/main/java/com/brainwallet/ui/bentosections/shopbento/ShopBentoViewModel.kt index b3d6ce44..9fbef227 100644 --- a/app/src/main/java/com/brainwallet/ui/bentosections/shopbento/ShopBentoViewModel.kt +++ b/app/src/main/java/com/brainwallet/ui/bentosections/shopbento/ShopBentoViewModel.kt @@ -13,11 +13,13 @@ import org.koin.android.annotation.KoinViewModel import java.util.Locale import android.telephony.TelephonyManager import com.brainwallet.data.repository.SettingRepository +import com.brainwallet.data.repository.ShopProxyRepository @KoinViewModel class ShopBentoViewModel( private val app: Application, private val settingRepository: SettingRepository, + private val shopProxyRepository: ShopProxyRepository ) : BrainwalletViewModel() { private val _state = MutableStateFlow(ShopBentoState()) @@ -34,6 +36,15 @@ class ShopBentoViewModel( } } } + viewModelScope.launch { + shopProxyRepository.refresh() + shopProxyRepository.shopProxy.collect { shopList -> + val widget = shopList.firstOrNull()?.widget.orEmpty() + _state.update { + it.copy(shopBaseUrl = widget) + } + } + } } private fun getCountryIso(): String { val telephonyManager = app.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager diff --git a/app/src/main/res/xml/remote_config_defaults.xml b/app/src/main/res/xml/remote_config_defaults.xml index 632af3b8..d41161a3 100644 --- a/app/src/main/res/xml/remote_config_defaults.xml +++ b/app/src/main/res/xml/remote_config_defaults.xml @@ -22,7 +22,7 @@ path_shop_content - https://www.bitrefill.com/?ref=bAshL935 + {"shop_data":[{ "widget_url":"https://www.bitrefill.com/?ref=2h186s7q"}]} key_api_baseurl_prod_new_enabled From f1c170b777998df83c997fa9b52e47445ac8f62d Mon Sep 17 00:00:00 2001 From: kcw-grunt Date: Fri, 8 May 2026 21:55:58 -0700 Subject: [PATCH 2/3] Match event name --- .../main/java/com/brainwallet/ui/screens/main/WebModalScreen.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/brainwallet/ui/screens/main/WebModalScreen.kt b/app/src/main/java/com/brainwallet/ui/screens/main/WebModalScreen.kt index e9ebfd00..be66cadf 100644 --- a/app/src/main/java/com/brainwallet/ui/screens/main/WebModalScreen.kt +++ b/app/src/main/java/com/brainwallet/ui/screens/main/WebModalScreen.kt @@ -26,7 +26,7 @@ fun WebModalScreen( modifier: Modifier = Modifier, ) { val eventString = if (url.contains("bitrefill")) { - "user_did_tap_bitrefill" + "user_did_tap_shop_bento" } else { "user_did_tap_linktree" } From 9a64d9260def1ff208f7dc91f4397ae0671159d9 Mon Sep 17 00:00:00 2001 From: kcw-grunt Date: Sat, 9 May 2026 06:44:33 -0700 Subject: [PATCH 3/3] Added activity indicator --- .../composable/BrainwalletLoadingIndicator.kt | 34 +++++++++ .../ui/screens/main/WebModalScreen.kt | 76 +++++++++++-------- 2 files changed, 80 insertions(+), 30 deletions(-) create mode 100644 app/src/main/java/com/brainwallet/ui/composable/BrainwalletLoadingIndicator.kt diff --git a/app/src/main/java/com/brainwallet/ui/composable/BrainwalletLoadingIndicator.kt b/app/src/main/java/com/brainwallet/ui/composable/BrainwalletLoadingIndicator.kt new file mode 100644 index 00000000..4708c49f --- /dev/null +++ b/app/src/main/java/com/brainwallet/ui/composable/BrainwalletLoadingIndicator.kt @@ -0,0 +1,34 @@ +package com.brainwallet.ui.composable + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.size +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.grunt.brainwallet.core.presentation.theme.BrainwalletTheme +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.tooling.preview.PreviewLightDark +import com.brainwallet.ui.theme.lavender +import com.brainwallet.ui.theme.midnight + +@Composable +fun BrainwalletLoadingIndicator( + modifier: Modifier = Modifier, + color: Color = BrainwalletTheme.colors.content +) { + CircularProgressIndicator( + color = lavender, + trackColor = midnight, + strokeWidth = 5.dp, + modifier = Modifier.size(48.dp).then(modifier) + ) +} + +@PreviewLightDark +@Composable +private fun BrainwalletLoadingIndicatorPreview() { + Box(modifier = Modifier) { + BrainwalletLoadingIndicator() + } +} diff --git a/app/src/main/java/com/brainwallet/ui/screens/main/WebModalScreen.kt b/app/src/main/java/com/brainwallet/ui/screens/main/WebModalScreen.kt index be66cadf..f5e5489a 100644 --- a/app/src/main/java/com/brainwallet/ui/screens/main/WebModalScreen.kt +++ b/app/src/main/java/com/brainwallet/ui/screens/main/WebModalScreen.kt @@ -5,20 +5,26 @@ import android.view.ViewGroup import android.webkit.WebView import android.webkit.WebViewClient import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView import com.brainwallet.navigation.OnNavigate import com.brainwallet.tools.manager.AnalyticsManager +import com.brainwallet.ui.composable.BrainwalletLoadingIndicator import com.brainwallet.ui.theme.bentoDarkSurfaceGradient - @Composable fun WebModalScreen( onNavigate: OnNavigate, @@ -34,42 +40,52 @@ fun WebModalScreen( AnalyticsManager.logCustomEvent(eventString) } + var isLoading by remember { mutableStateOf(true) } + Card( - modifier = modifier - .fillMaxSize() - .background(brush = bentoDarkSurfaceGradient), + modifier = modifier.fillMaxSize(), shape = RoundedCornerShape(18.dp), + colors = CardDefaults.cardColors(containerColor = Color.Transparent), ) { - AndroidView( - modifier = Modifier.fillMaxSize(), - factory = { context -> - WebView(context).apply { - layoutParams = ViewGroup.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT - ) - - setBackgroundColor(0) - - settings.javaScriptEnabled = true - settings.domStorageEnabled = true - settings.useWideViewPort = true + Box( + modifier = Modifier + .fillMaxSize() + .background(brush = bentoDarkSurfaceGradient), + ) { + AndroidView( + modifier = Modifier.fillMaxSize(), + factory = { context -> + WebView(context).apply { + layoutParams = ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ) + setBackgroundColor(0) + settings.javaScriptEnabled = true + settings.domStorageEnabled = true + settings.useWideViewPort = true - webViewClient = object : WebViewClient() { + webViewClient = object : WebViewClient() { + override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) { + super.onPageStarted(view, url, favicon) + isLoading = true + } - override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) { - super.onPageStarted(view, url, favicon) - } - - override fun onPageFinished(view: WebView?, url: String?) { - super.onPageFinished(view, url) + override fun onPageFinished(view: WebView?, url: String?) { + super.onPageFinished(view, url) + isLoading = false + } } } - } - }, - update = { - it.loadUrl(url) + }, + update = { it.loadUrl(url) } + ) + + if (isLoading) { + BrainwalletLoadingIndicator( + modifier = Modifier.align(Alignment.Center) + ) } - ) + } } }