[#1528] Coinbase on-ramp integration

* [#1528] Coinbase integration

Closes #1528

* [#1528] CI hotfix

Closes #1528

* Remove duplicate lines

* Improve CI scripts + variable renaming

* Remove coinbase button in testnet build

* Update changelogs

---------

Co-authored-by: Honza <rychnovsky.honza@gmail.com>
This commit is contained in:
Milan 2024-09-12 18:10:54 +02:00 committed by GitHub
parent fa9ea0c03a
commit 35c01df313
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 217 additions and 39 deletions

View File

@ -63,9 +63,11 @@ jobs:
# GOOGLE_PLAY_WORKLOAD_IDENTITY_PROVIDER: ${{ secrets.GOOGLE_PLAY_WORKLOAD_IDENTITY_PROVIDER }}
GOOGLE_PLAY_SERVICE_ACCOUNT_KEY: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_KEY }}
GOOGLE_PLAY_PUBLISHER_API_KEY: ${{ secrets.GOOGLE_PLAY_PUBLISHER_API_KEY }}
COINBASE_APP_ID: ${{ secrets.COINBASE_APP_ID }}
if: "${{ env.GOOGLE_PLAY_CLOUD_PROJECT != '' &&
env.GOOGLE_PLAY_SERVICE_ACCOUNT_KEY != '' &&
env.GOOGLE_PLAY_PUBLISHER_API_KEY != ''
env.GOOGLE_PLAY_PUBLISHER_API_KEY != '' &&
env.COINBASE_APP_ID != ''
}}"
run: echo "defined=true" >> $GITHUB_OUTPUT
@ -154,6 +156,7 @@ jobs:
ORG_GRADLE_PROJECT_ZCASH_RELEASE_KEYSTORE_PASSWORD: ${{ secrets.UPLOAD_KEYSTORE_PASSWORD }}
ORG_GRADLE_PROJECT_ZCASH_RELEASE_KEY_ALIAS: ${{ secrets.UPLOAD_KEY_ALIAS }}
ORG_GRADLE_PROJECT_ZCASH_RELEASE_KEY_ALIAS_PASSWORD: ${{ secrets.UPLOAD_KEY_ALIAS_PASSWORD }}
ORG_GRADLE_PROJECT_ZCASH_COINBASE_APP_ID: ${{ secrets.COINBASE_APP_ID }}
run: |
./gradlew :app:publishToGooglePlay
- name: Collect Artifacts

View File

@ -63,6 +63,17 @@ jobs:
if: "${{ env.EMULATOR_WTF_API_KEY != '' }}"
run: echo "defined=true" >> $GITHUB_OUTPUT
check_coinbase_secrets:
runs-on: ubuntu-latest
outputs:
has-secrets: ${{ steps.check_coinbase_secrets.outputs.defined }}
steps:
- id: check_coinbase_secrets
env:
COINBASE_APP_ID: ${{ secrets.COINBASE_APP_ID }}
if: "${{ env.COINBASE_APP_ID != '' }}"
run: echo "defined=true" >> $GITHUB_OUTPUT
check_properties:
needs: validate_gradle_wrapper
runs-on: ubuntu-latest
@ -252,8 +263,8 @@ jobs:
# Emulator.wtf is preferred if it has an API key.
test_android_modules_ftl:
if: needs.check_firebase_secrets.outputs.has-secrets == 'true' && needs.check_emulator_wtf_secrets.outputs.has-secrets == 'false'
needs: [validate_gradle_wrapper, check_firebase_secrets, check_emulator_wtf_secrets]
if: needs.check_firebase_secrets.outputs.has-secrets == 'true' && needs.check_emulator_wtf_secrets.outputs.has-secrets == 'false' && needs.check_coinbase_secrets.outputs.has-secrets == 'true'
needs: [validate_gradle_wrapper, check_firebase_secrets, check_emulator_wtf_secrets, check_coinbase_secrets]
runs-on: ubuntu-latest
permissions:
contents: read
@ -295,6 +306,7 @@ jobs:
ORG_GRADLE_PROJECT_ZCASH_FIREBASE_TEST_LAB_API_KEY_PATH: ${{ steps.auth_test_lab.outputs.credentials_file_path }}
# Because Fulladle doesn't allow Test Orchestrator to be enabled/disabled for a specific submodule, it must be enabled for all modules
ORG_GRADLE_PROJECT_IS_USE_TEST_ORCHESTRATOR: true
ORG_GRADLE_PROJECT_ZCASH_COINBASE_APP_ID: ${{ secrets.COINBASE_APP_ID }}
run: |
./gradlew runFlank
- name: Collect Artifacts
@ -316,8 +328,8 @@ jobs:
path: ~/artifacts
test_android_modules_wtf_coverage:
if: needs.check_emulator_wtf_secrets.outputs.has-secrets == 'true'
needs: [ validate_gradle_wrapper, check_emulator_wtf_secrets ]
if: needs.check_emulator_wtf_secrets.outputs.has-secrets == 'true' && needs.check_coinbase_secrets.outputs.has-secrets == 'true'
needs: [ validate_gradle_wrapper, check_emulator_wtf_secrets, check_coinbase_secrets ]
runs-on: ubuntu-latest
permissions:
contents: read
@ -342,6 +354,7 @@ jobs:
ORG_GRADLE_PROJECT_ZCASH_DEBUG_APP_NAME_SUFFIX: ""
ORG_GRADLE_PROJECT_ZCASH_EMULATOR_WTF_API_KEY: ${{ secrets.EMULATOR_WTF_API_KEY }}
ORG_GRADLE_PROJECT_IS_ANDROID_INSTRUMENTATION_TEST_COVERAGE_ENABLED: true
ORG_GRADLE_PROJECT_ZCASH_COINBASE_APP_ID: ${{ secrets.COINBASE_APP_ID }}
run: |
./gradlew testDebugWithEmulatorWtf :ui-integration-test:testZcashmainnetDebugWithEmulatorWtf
- name: Collect Artifacts
@ -363,8 +376,8 @@ jobs:
path: ~/artifacts
test_android_modules_wtf_no_coverage:
if: needs.check_emulator_wtf_secrets.outputs.has-secrets == 'true'
needs: [ validate_gradle_wrapper, check_emulator_wtf_secrets ]
if: needs.check_emulator_wtf_secrets.outputs.has-secrets == 'true' && needs.check_coinbase_secrets.outputs.has-secrets == 'true'
needs: [ validate_gradle_wrapper, check_emulator_wtf_secrets, check_coinbase_secrets ]
runs-on: ubuntu-latest
permissions:
contents: read
@ -389,6 +402,7 @@ jobs:
ORG_GRADLE_PROJECT_ZCASH_DEBUG_APP_NAME_SUFFIX: ""
ORG_GRADLE_PROJECT_ZCASH_EMULATOR_WTF_API_KEY: ${{ secrets.EMULATOR_WTF_API_KEY }}
ORG_GRADLE_PROJECT_IS_ANDROID_INSTRUMENTATION_TEST_COVERAGE_ENABLED: false
ORG_GRADLE_PROJECT_ZCASH_COINBASE_APP_ID: ${{ secrets.COINBASE_APP_ID }}
run: |
./gradlew :app:testZcashmainnetDebugWithEmulatorWtf :ui-screenshot-test:testZcashmainnetDebugWithEmulatorWtf
- name: Collect Artifacts
@ -411,8 +425,8 @@ jobs:
# Performs a button mash test on the debug build of the app with strict mode enabled
test_robo_debug:
if: needs.check_firebase_secrets.outputs.has-secrets == 'true'
needs: [check_firebase_secrets]
if: needs.check_firebase_secrets.outputs.has-secrets == 'true' && needs.check_coinbase_secrets.outputs.has-secrets == 'true'
needs: [check_firebase_secrets, check_coinbase_secrets]
runs-on: ubuntu-latest
permissions:
packages: read
@ -447,6 +461,7 @@ jobs:
env:
ORG_GRADLE_PROJECT_ZCASH_SUPPORT_EMAIL_ADDRESS: ${{ vars.SUPPORT_EMAIL_ADDRESS }}
ORG_GRADLE_PROJECT_IS_CRASH_ON_STRICT_MODE_VIOLATION: true
ORG_GRADLE_PROJECT_ZCASH_COINBASE_APP_ID: ${{ secrets.COINBASE_APP_ID }}
run: |
./gradlew :app:assembleDebug
- name: Authenticate to Google Cloud for Firebase Test Lab
@ -464,11 +479,13 @@ jobs:
# This first environment variable is used by Flank, since the temporary token is missing the project name
GOOGLE_CLOUD_PROJECT: ${{ vars.FIREBASE_TEST_LAB_PROJECT }}
ORG_GRADLE_PROJECT_ZCASH_FIREBASE_TEST_LAB_API_KEY_PATH: ${{ steps.auth_test_lab.outputs.credentials_file_path }}
ORG_GRADLE_PROJECT_COINBASE_APP_ID: ${{ secrets.COINBASE_APP_ID }}
run: |
./gradlew :app:runFlankSanityConfigDebug
build:
needs: validate_gradle_wrapper
if: needs.check_coinbase_secrets.outputs.has-secrets == 'true'
needs: [validate_gradle_wrapper, check_coinbase_secrets]
runs-on: ubuntu-latest
permissions:
contents: read
@ -515,6 +532,7 @@ jobs:
ORG_GRADLE_PROJECT_ZCASH_RELEASE_KEYSTORE_PASSWORD: android
ORG_GRADLE_PROJECT_ZCASH_RELEASE_KEY_ALIAS: androiddebugkey
ORG_GRADLE_PROJECT_ZCASH_RELEASE_KEY_ALIAS_PASSWORD: android
ORG_GRADLE_PROJECT_ZCASH_COINBASE_APP_ID: ${{ secrets.COINBASE_APP_ID }}
run: |
./gradlew :app:assembleDebug :app:bundleRelease :app:packageZcashmainnetReleaseUniversalApk
- name: Collect Artifacts
@ -538,8 +556,8 @@ jobs:
# Performs a button mash test on the release build of the app
test_robo_release:
if: needs.check_firebase_secrets.outputs.has-secrets == 'true'
needs: [build, check_firebase_secrets]
if: needs.check_firebase_secrets.outputs.has-secrets == 'true' && needs.check_coinbase_secrets.outputs.has-secrets == 'true'
needs: [build, check_firebase_secrets, check_coinbase_secrets]
runs-on: ubuntu-latest
permissions:
packages: read
@ -579,6 +597,7 @@ jobs:
# This first environment variable is used by Flank, since the temporary token is missing the project name
GOOGLE_CLOUD_PROJECT: ${{ vars.FIREBASE_TEST_LAB_PROJECT }}
ORG_GRADLE_PROJECT_ZCASH_FIREBASE_TEST_LAB_API_KEY_PATH: ${{ steps.auth_test_lab.outputs.credentials_file_path }}
ORG_GRADLE_PROJECT_ZCASH_COINBASE_APP_ID: ${{ secrets.COINBASE_APP_ID }}
run: |
unzip ${BINARIES_ZIP_PATH}
./gradlew :app:runFlankSanityConfigRelease

View File

@ -10,6 +10,7 @@ and this application adheres to [Semantic Versioning](https://semver.org/spec/v2
- Transaction resubmission feature has been added. It periodically searches for unmined sent transactions that
are still within their expiry window and resubmits them if there are any.
- The Choose server screen now provides a new search for the three fastest servers feature
- Coinbase Onramp integration button has been added to the Advanced Settings screen
### Changed
- Choose server screen has been redesigned

View File

@ -121,6 +121,7 @@ tasks {
"ZCASH_GOOGLE_PLAY_DEPLOY_TRACK" to "internal",
"ZCASH_GOOGLE_PLAY_DEPLOY_STATUS" to "draft",
"ZCASH_COINBASE_APP_ID" to "",
"SDK_INCLUDED_BUILD_PATH" to "",
"BIP_39_INCLUDED_BUILD_PATH" to ""
)
@ -192,12 +193,16 @@ fladle {
flankVersion.set(libs.versions.flank.get())
filesToDownload.set(listOf(
".*/matrix_.*/.*test_results_merged\\.xml",
".*/matrix_.*/.*/artifacts/sdcard/googletest/test_outputfiles/.*\\.png"
))
filesToDownload.set(
listOf(
".*/matrix_.*/.*test_results_merged\\.xml",
".*/matrix_.*/.*/artifacts/sdcard/googletest/test_outputfiles/.*\\.png"
)
)
directoriesToPull.set(listOf(
"/sdcard/googletest/test_outputfiles"
))
directoriesToPull.set(
listOf(
"/sdcard/googletest/test_outputfiles"
)
)
}

View File

@ -13,6 +13,7 @@ directly impact users rather than highlighting other key architectural updates.*
- Transaction resubmission feature has been added. It periodically searches for unmined sent transactions that
are still within their expiry window and resubmits them if there are any.
- The Choose server screen now provides a new search for the three fastest servers feature
- Coinbase Onramp integration button has been added to the Advanced Settings screen
### Changed
- Choose server screen has been redesigned

View File

@ -84,6 +84,10 @@ IS_SECURE_SCREEN_PROTECTION_ACTIVE=true
# Set whether the screen rotation is enabled or the screen orientation is locked in the portrait mode.
IS_SCREEN_ROTATION_ENABLED=false
# Set the Coinbase app project ID to test the Coinbase Onramp integrations locally. Keep it empty as our CI actions
# set it up.
ZCASH_COINBASE_APP_ID=
# Set keystore details to enable build signing. Typically these
# are overridden via ~/.gradle/gradle.properties to allow secure injection.
# Debug keystore is useful if using Google Maps or Firebase, which require API keys to be linked
@ -184,6 +188,7 @@ ANDROIDX_STARTUP_VERSION=1.1.1
ANDROIDX_TEST_SERVICE_VERSION=1.4.2
ANDROIDX_UI_AUTOMATOR_VERSION=2.3.0
ANDROIDX_WORK_MANAGER_VERSION=2.9.0
ANDROIDX_BROWSER_VERSION=1.8.0
CORE_LIBRARY_DESUGARING_VERSION=2.0.4
FIREBASE_BOM_VERSION_MATCHER=32.8.1
GOOGLE_AUTH_LIB_JAVA_VERSION=1.18.0

View File

@ -167,6 +167,7 @@ dependencyResolutionManagement {
val androidxTestRunnerVersion = extra["ANDROIDX_TEST_RUNNER_VERSION"].toString()
val androidxUiAutomatorVersion = extra["ANDROIDX_UI_AUTOMATOR_VERSION"].toString()
val androidxWorkManagerVersion = extra["ANDROIDX_WORK_MANAGER_VERSION"].toString()
val androidxBrowserVersion = extra["ANDROIDX_BROWSER_VERSION"].toString()
val coreLibraryDesugaringVersion = extra["CORE_LIBRARY_DESUGARING_VERSION"].toString()
val flankVersion = extra["FLANK_VERSION"].toString()
val jacocoVersion = extra["JACOCO_VERSION"].toString()
@ -220,6 +221,7 @@ dependencyResolutionManagement {
library("androidx-startup", "androidx.startup:startup-runtime:$androidxStartupVersion")
library("androidx-viewmodel-compose", "androidx.lifecycle:lifecycle-viewmodel-compose:$androidxLifecycleVersion")
library("androidx-workmanager", "androidx.work:work-runtime-ktx:$androidxWorkManagerVersion")
library("androidx-browser", "androidx.browser:browser:$androidxBrowserVersion")
library("desugaring", "com.android.tools:desugar_jdk_libs:$coreLibraryDesugaringVersion")
library("firebase-bom", "com.google.firebase:firebase-bom:${extra["FIREBASE_BOM_VERSION_MATCHER"]}")
library("firebase-installations", "com.google.firebase", "firebase-installations").withoutVersion()

View File

@ -25,8 +25,28 @@ import androidx.compose.ui.unit.sp
import co.electriccoin.zcash.ui.design.R
import co.electriccoin.zcash.ui.design.newcomponent.PreviewScreens
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
import co.electriccoin.zcash.ui.design.util.getValue
import co.electriccoin.zcash.ui.design.util.orDark
@Composable
fun ZashiSettingsListItem(
state: ButtonState,
@DrawableRes icon: Int,
trailing: @Composable () -> Unit = {
Image(
painter = painterResource(R.drawable.ic_chevron_right orDark R.drawable.ic_chevron_right_dark),
contentDescription = state.text.getValue(),
)
}
) {
ZashiSettingsListItem(
text = state.text.getValue(),
icon = icon,
trailing = trailing,
onClick = state.onClick
)
}
@Composable
fun ZashiSettingsListItem(
text: String,

View File

@ -74,6 +74,14 @@ androidComponents {
comment = "Whether is the SecureScreen sensitive data protection enabled"
)
)
variant.buildConfigFields.put(
"ZCASH_COINBASE_APP_ID",
BuildConfigField(
type = "String",
value = "\"${project.property("ZCASH_COINBASE_APP_ID")?.toString().orEmpty()}\"",
comment = "App ID of the Coinbase Onramp integration"
)
)
// To configure screen orientation in runtime
variant.buildConfigFields.put(
"IS_SCREEN_ROTATION_ENABLED",
@ -96,6 +104,7 @@ dependencies {
implementation(libs.androidx.splash)
implementation(libs.androidx.workmanager)
api(libs.bundles.androidx.biometric)
implementation(libs.androidx.browser)
implementation(libs.bundles.androidx.camera)
implementation(libs.bundles.androidx.compose.core)
implementation(libs.bundles.androidx.compose.extended)

View File

@ -2,6 +2,7 @@ package co.electriccoin.zcash.di
import co.electriccoin.zcash.ui.common.provider.GetDefaultServersProvider
import co.electriccoin.zcash.ui.common.provider.GetVersionInfoProvider
import co.electriccoin.zcash.ui.common.provider.GetZcashCurrencyProvider
import org.koin.core.module.dsl.factoryOf
import org.koin.dsl.module
@ -9,4 +10,5 @@ val providerModule =
module {
factoryOf(::GetDefaultServersProvider)
factoryOf(::GetVersionInfoProvider)
factoryOf(::GetZcashCurrencyProvider)
}

View File

@ -3,6 +3,7 @@ package co.electriccoin.zcash.di
import co.electriccoin.zcash.ui.common.usecase.GetPersistableWalletUseCase
import co.electriccoin.zcash.ui.common.usecase.GetSelectedEndpointUseCase
import co.electriccoin.zcash.ui.common.usecase.GetSynchronizerUseCase
import co.electriccoin.zcash.ui.common.usecase.GetTransparentAddressUseCase
import co.electriccoin.zcash.ui.common.usecase.ObserveConfigurationUseCase
import co.electriccoin.zcash.ui.common.usecase.ObserveFastestServersUseCase
import co.electriccoin.zcash.ui.common.usecase.ObserveSelectedEndpointUseCase
@ -27,4 +28,5 @@ val useCaseModule =
singleOf(::GetSelectedEndpointUseCase)
singleOf(::ObserveConfigurationUseCase)
singleOf(::RescanBlockchainUseCase)
singleOf(::GetTransparentAddressUseCase)
}

View File

@ -0,0 +1,10 @@
package co.electriccoin.zcash.ui.common.provider
import android.app.Application
import cash.z.ecc.sdk.type.ZcashCurrency
class GetZcashCurrencyProvider(private val application: Application) {
operator fun invoke() = ZcashCurrency.fromResources(application)
fun getLocalizedName() = ZcashCurrency.getLocalizedName(application)
}

View File

@ -6,10 +6,12 @@ import cash.z.ecc.android.sdk.Synchronizer
import cash.z.ecc.android.sdk.WalletCoordinator
import cash.z.ecc.android.sdk.model.FastestServersResult
import cash.z.ecc.android.sdk.model.PersistableWallet
import cash.z.ecc.android.sdk.model.WalletAddresses
import cash.z.ecc.sdk.ANDROID_STATE_FLOW_TIMEOUT
import co.electriccoin.lightwallet.client.model.LightWalletEndpoint
import co.electriccoin.zcash.preference.EncryptedPreferenceProvider
import co.electriccoin.zcash.preference.StandardPreferenceProvider
import co.electriccoin.zcash.spackle.Twig
import co.electriccoin.zcash.ui.common.model.FastestServersState
import co.electriccoin.zcash.ui.common.model.OnboardingState
import co.electriccoin.zcash.ui.common.provider.GetDefaultServersProvider
@ -48,6 +50,7 @@ interface WalletRepository {
val secretState: StateFlow<SecretState?>
val fastestServers: StateFlow<FastestServersState>
val persistableWallet: Flow<PersistableWallet?>
val addresses: StateFlow<WalletAddresses?>
fun persistWallet(persistableWallet: PersistableWallet)
@ -160,6 +163,21 @@ class WalletRepositoryImpl(
(it as? SecretState.Ready?)?.persistableWallet
}
override val addresses: StateFlow<WalletAddresses?> =
synchronizer
.filterNotNull()
.map {
runCatching {
WalletAddresses.new(it)
}.onFailure {
Twig.warn { "Wait until the SDK starts providing the addresses" }
}.getOrNull()
}.stateIn(
scope,
SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT),
null
)
/**
* Persists a wallet asynchronously. Clients observe [secretState] to see the side effects.
*/

View File

@ -0,0 +1,12 @@
package co.electriccoin.zcash.ui.common.usecase
import co.electriccoin.zcash.ui.common.repository.WalletRepository
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
class GetTransparentAddressUseCase(
private val walletRepository: WalletRepository
) {
suspend operator fun invoke() = walletRepository.addresses.filterNotNull().map { it.transparent }.first()
}

View File

@ -223,20 +223,7 @@ class WalletViewModel(
null
)
val addresses: StateFlow<WalletAddresses?> =
synchronizer
.filterNotNull()
.map {
runCatching {
WalletAddresses.new(it)
}.onFailure {
Twig.warn { "Wait until the SDK starts providing the addresses" }
}.getOrNull()
}.stateIn(
viewModelScope,
SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT),
null
)
val addresses: StateFlow<WalletAddresses?> = walletRepository.addresses
@OptIn(ExperimentalCoroutinesApi::class)
val transactionHistoryState =

View File

@ -1,5 +1,7 @@
package co.electriccoin.zcash.ui.screen.advancedsettings
import co.electriccoin.zcash.ui.design.component.ButtonState
data class AdvancedSettingsState(
val onBack: () -> Unit,
val onRecoveryPhraseClick: () -> Unit,
@ -7,4 +9,5 @@ data class AdvancedSettingsState(
val onChooseServerClick: () -> Unit,
val onCurrencyConversionClick: () -> Unit,
val onDeleteZashiClick: () -> Unit,
val coinbaseButton: ButtonState?,
)

View File

@ -2,11 +2,14 @@
package co.electriccoin.zcash.ui.screen.advancedsettings
import android.net.Uri
import androidx.activity.compose.BackHandler
import androidx.browser.customtabs.CustomTabsIntent
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import co.electriccoin.zcash.di.koinActivityViewModel
import co.electriccoin.zcash.ui.common.compose.LocalActivity
import co.electriccoin.zcash.ui.common.compose.LocalNavController
import co.electriccoin.zcash.ui.common.viewmodel.WalletViewModel
import co.electriccoin.zcash.ui.screen.advancedsettings.view.AdvancedSettings
@ -20,6 +23,7 @@ internal fun WrapAdvancedSettings(
goExportPrivateData: () -> Unit,
goSeedRecovery: () -> Unit,
) {
val activity = LocalActivity.current
val navController = LocalNavController.current
val walletViewModel = koinActivityViewModel<WalletViewModel>()
val viewModel = koinViewModel<AdvancedSettingsViewModel>()
@ -41,6 +45,18 @@ internal fun WrapAdvancedSettings(
}
}
LaunchedEffect(Unit) {
viewModel.coinbaseNavigationCommand.collect { uri ->
val intent =
CustomTabsIntent.Builder()
.setUrlBarHidingEnabled(true)
.setShowTitle(true)
.setShareState(CustomTabsIntent.SHARE_STATE_OFF)
.build()
intent.launchUrl(activity, Uri.parse(uri))
}
}
LaunchedEffect(Unit) {
viewModel.backNavigationCommand.collect {
navController.popBackStack()

View File

@ -26,12 +26,14 @@ import androidx.compose.ui.unit.sp
import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.common.model.TopAppBarSubTitleState
import co.electriccoin.zcash.ui.design.component.BlankBgScaffold
import co.electriccoin.zcash.ui.design.component.ButtonState
import co.electriccoin.zcash.ui.design.component.ZashiSettingsListItem
import co.electriccoin.zcash.ui.design.component.ZashiSmallTopAppBar
import co.electriccoin.zcash.ui.design.component.ZashiTopAppBarBackNavigation
import co.electriccoin.zcash.ui.design.newcomponent.PreviewScreens
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
import co.electriccoin.zcash.ui.design.util.orDark
import co.electriccoin.zcash.ui.design.util.stringRes
import co.electriccoin.zcash.ui.screen.advancedsettings.AdvancedSettingsState
import co.electriccoin.zcash.ui.screen.advancedsettings.AdvancedSettingsTag
import co.electriccoin.zcash.ui.screen.exchangerate.ZashiButton
@ -92,6 +94,13 @@ fun AdvancedSettings(
R.drawable.ic_advanced_settings_currency_conversion_dark,
onClick = state.onCurrencyConversionClick
)
if (state.coinbaseButton != null) {
HorizontalDivider(color = ZcashTheme.zashiColors.divider)
ZashiSettingsListItem(
icon = R.drawable.ic_advanced_settings_coinbase,
state = state.coinbaseButton
)
}
Spacer(modifier = Modifier.height(24.dp))
Spacer(modifier = Modifier.weight(1f))
Row(
@ -161,6 +170,11 @@ private fun AdvancedSettingsPreview() =
onChooseServerClick = {},
onCurrencyConversionClick = {},
onDeleteZashiClick = {},
coinbaseButton =
ButtonState(
text = stringRes("Coinbase"),
onClick = {}
)
),
topAppBarSubTitleState = TopAppBarSubTitleState.None,
)

View File

@ -2,16 +2,28 @@ package co.electriccoin.zcash.ui.screen.advancedsettings.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import co.electriccoin.zcash.ui.BuildConfig
import co.electriccoin.zcash.ui.NavigationTargets
import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.common.provider.GetVersionInfoProvider
import co.electriccoin.zcash.ui.common.provider.GetZcashCurrencyProvider
import co.electriccoin.zcash.ui.common.usecase.GetTransparentAddressUseCase
import co.electriccoin.zcash.ui.design.component.ButtonState
import co.electriccoin.zcash.ui.design.util.stringRes
import co.electriccoin.zcash.ui.screen.advancedsettings.AdvancedSettingsState
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
class AdvancedSettingsViewModel : ViewModel() {
val state: StateFlow<AdvancedSettingsState> =
class AdvancedSettingsViewModel(
getVersionInfo: GetVersionInfoProvider,
getZcashCurrency: GetZcashCurrencyProvider,
private val getTransparentAddress: GetTransparentAddressUseCase,
) : ViewModel() {
private val forceShowCoinbaseForDebug = getVersionInfo().let { it.isDebuggable && !it.isRunningUnderTestService }
val state =
MutableStateFlow(
AdvancedSettingsState(
onBack = ::onBack,
@ -19,12 +31,23 @@ class AdvancedSettingsViewModel : ViewModel() {
onExportPrivateDataClick = {},
onChooseServerClick = ::onChooseServerClick,
onCurrencyConversionClick = ::onCurrencyConversionClick,
onDeleteZashiClick = {}
onDeleteZashiClick = {},
coinbaseButton =
ButtonState(
// Set the wallet currency by app build is more future-proof, although we hide it from the UI
// in the Testnet build
text = stringRes(R.string.advanced_settings_coinbase, getZcashCurrency.getLocalizedName()),
onClick = { onBuyWithCoinbaseClicked() }
).takeIf {
!getVersionInfo().isTestnet &&
(BuildConfig.ZCASH_COINBASE_APP_ID.isNotEmpty() || forceShowCoinbaseForDebug)
}
)
).asStateFlow()
val navigationCommand = MutableSharedFlow<String>()
val backNavigationCommand = MutableSharedFlow<Unit>()
val coinbaseNavigationCommand = MutableSharedFlow<String>()
private fun onChooseServerClick() =
viewModelScope.launch {
@ -36,6 +59,32 @@ class AdvancedSettingsViewModel : ViewModel() {
navigationCommand.emit(NavigationTargets.SETTINGS_EXCHANGE_RATE_OPT_IN)
}
private fun onBuyWithCoinbaseClicked() {
viewModelScope.launch {
val appId = BuildConfig.ZCASH_COINBASE_APP_ID
when {
appId.isEmpty() && forceShowCoinbaseForDebug ->
coinbaseNavigationCommand.emit("https://www.coinbase.com") // fallback debug url
appId.isEmpty() && forceShowCoinbaseForDebug -> {
// should not happen
}
appId.isNotEmpty() -> {
val address = getTransparentAddress().address
val url =
"https://pay.coinbase.com/buy/select-asset?appId=$appId&addresses={\"${address}\":[\"zcash\"]}"
coinbaseNavigationCommand.emit(url)
}
else -> {
// should not happen
}
}
}
}
fun onBack() =
viewModelScope.launch {
backNavigationCommand.emit(Unit)

View File

@ -94,7 +94,7 @@ class SettingsViewModel(
combine(isLoading, troubleshootingState) { isLoading, troubleshootingState ->
SettingsState(
isLoading = isLoading,
version = stringRes(R.string.settings_version, getVersionInfo().versionName),
version = stringRes(R.string.settings_version, versionInfo.versionName),
settingsTroubleshootingState = troubleshootingState,
onBack = ::onBack,
onAdvancedSettingsClick = ::onAdvancedSettingsClick,

View File

@ -4,7 +4,7 @@
<string name="advanced_settings_export">Export Private Data</string>
<string name="advanced_settings_choose_server">Choose a Server</string>
<string name="advanced_settings_currency_conversion">Currency Conversion</string>
<string name="advanced_settings_coinbase">Buy ZEC with Coinbase</string>
<string name="advanced_settings_coinbase">Buy <xliff:g id="currency" example="ZEC">%1$s</xliff:g> with Coinbase</string>
<string name="advanced_settings_info">You will be asked to confirm on the next screen</string>
<string name="advanced_settings_delete_button">Delete Zashi</string>
</resources>