[#1533] Flexa integration
* [#1533] Flexa integration Closes #1533 * [#1533] Code cleanup * [#1533] Finalisation * [#1618] Flexa payment biometrics * [#1618] Design update * [#1618] Local maven added * [#1618] Code cleanup * [#1533] Material3 version bump * Fix proguard rules * [#1533] Flexa hotfixes * [#1533] Flexa hotfixes * Changelogs update --------- Co-authored-by: Honza <rychnovsky.honza@gmail.com>
This commit is contained in:
parent
ba761c2f37
commit
c773e7d1c7
|
@ -157,6 +157,7 @@ jobs:
|
|||
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 }}
|
||||
ORG_GRADLE_PROJECT_ZCASH_FLEXA_KEY: ${{ secrets.FLEXA_PUBLISHABLE_KEY }}
|
||||
run: |
|
||||
./gradlew :app:publishToGooglePlay
|
||||
- name: Collect Artifacts
|
||||
|
|
|
@ -307,6 +307,7 @@ jobs:
|
|||
# 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 }}
|
||||
ORG_GRADLE_PROJECT_ZCASH_FLEXA_KEY: ${{ secrets.FLEXA_PUBLISHABLE_KEY }}
|
||||
run: |
|
||||
./gradlew runFlank
|
||||
- name: Collect Artifacts
|
||||
|
@ -355,6 +356,7 @@ jobs:
|
|||
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 }}
|
||||
ORG_GRADLE_PROJECT_ZCASH_FLEXA_KEY: ${{ secrets.FLEXA_PUBLISHABLE_KEY }}
|
||||
run: |
|
||||
./gradlew testDebugWithEmulatorWtf :ui-integration-test:testZcashmainnetDebugWithEmulatorWtf
|
||||
- name: Collect Artifacts
|
||||
|
@ -403,6 +405,7 @@ jobs:
|
|||
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 }}
|
||||
ORG_GRADLE_PROJECT_ZCASH_FLEXA_KEY: ${{ secrets.FLEXA_PUBLISHABLE_KEY }}
|
||||
run: |
|
||||
./gradlew :app:testZcashmainnetDebugWithEmulatorWtf :ui-screenshot-test:testZcashmainnetDebugWithEmulatorWtf
|
||||
- name: Collect Artifacts
|
||||
|
@ -462,6 +465,7 @@ jobs:
|
|||
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 }}
|
||||
ORG_GRADLE_PROJECT_ZCASH_FLEXA_KEY: ${{ secrets.FLEXA_PUBLISHABLE_KEY }}
|
||||
run: |
|
||||
./gradlew :app:assembleDebug
|
||||
- name: Authenticate to Google Cloud for Firebase Test Lab
|
||||
|
@ -533,6 +537,7 @@ jobs:
|
|||
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 }}
|
||||
ORG_GRADLE_PROJECT_ZCASH_FLEXA_KEY: ${{ secrets.FLEXA_PUBLISHABLE_KEY }}
|
||||
run: |
|
||||
./gradlew :app:assembleDebug :app:bundleRelease :app:packageZcashmainnetReleaseUniversalApk
|
||||
- name: Collect Artifacts
|
||||
|
@ -598,6 +603,7 @@ jobs:
|
|||
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 }}
|
||||
ORG_GRADLE_PROJECT_ZCASH_FLEXA_KEY: ${{ secrets.FLEXA_PUBLISHABLE_KEY }}
|
||||
run: |
|
||||
unzip ${BINARIES_ZIP_PATH}
|
||||
./gradlew :app:runFlankSanityConfigRelease
|
||||
|
|
|
@ -8,6 +8,7 @@ and this application adheres to [Semantic Versioning](https://semver.org/spec/v2
|
|||
|
||||
### Added
|
||||
- The device authentication feature on the Zashi app launch has been added
|
||||
- The Flexa SDK has been adopted to enable payments using the embedded Flexa UI
|
||||
|
||||
## [1.2.1 (760)] - 2024-10-22
|
||||
|
||||
|
|
|
@ -10,12 +10,13 @@
|
|||
-printconfiguration build/outputs/proguard-config.txt
|
||||
|
||||
# This is generated automatically by the Android Gradle plugin.
|
||||
-dontwarn com.google.j2objc.annotations.ReflectionSupport
|
||||
-dontwarn com.google.j2objc.annotations.RetainedWith
|
||||
-dontwarn androidx.compose.ui.util.MathHelpersKt
|
||||
-dontwarn com.google.common.util.concurrent.ListenableFuture
|
||||
-dontwarn com.google.errorprone.annotations.InlineMe
|
||||
-dontwarn com.google.errorprone.annotations.MustBeClosed
|
||||
-dontwarn com.google.j2objc.annotations.ReflectionSupport
|
||||
-dontwarn com.google.j2objc.annotations.ReflectionSupport$Level
|
||||
-dontwarn com.google.j2objc.annotations.RetainedWith
|
||||
-dontwarn javax.naming.directory.Attribute
|
||||
-dontwarn javax.naming.directory.Attributes
|
||||
-dontwarn javax.naming.directory.DirContext
|
||||
|
|
|
@ -10,6 +10,7 @@ import co.electriccoin.zcash.di.viewModelModule
|
|||
import co.electriccoin.zcash.preference.StandardPreferenceProvider
|
||||
import co.electriccoin.zcash.spackle.StrictModeCompat
|
||||
import co.electriccoin.zcash.spackle.Twig
|
||||
import co.electriccoin.zcash.ui.common.repository.FlexaRepository
|
||||
import co.electriccoin.zcash.ui.preference.StandardPreferenceKeys
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.android.ext.android.inject
|
||||
|
@ -20,6 +21,7 @@ import org.koin.core.context.startKoin
|
|||
@Suppress("unused")
|
||||
class ZcashApplication : CoroutineApplication() {
|
||||
private val standardPreferenceProvider by inject<StandardPreferenceProvider>()
|
||||
private val flexaRepository by inject<FlexaRepository>()
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
@ -44,6 +46,8 @@ class ZcashApplication : CoroutineApplication() {
|
|||
// Since analytics will need disk IO internally, we want this to be registered after strict
|
||||
// mode is configured to ensure none of that IO happens on the main thread
|
||||
configureAnalytics()
|
||||
|
||||
flexaRepository.init()
|
||||
}
|
||||
|
||||
private fun configureLogging() {
|
||||
|
|
|
@ -45,6 +45,7 @@ buildscript {
|
|||
}
|
||||
}
|
||||
}
|
||||
maven("${rootProject.projectDir}/maven") // url to a local maven in this repository
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
@ -121,6 +122,7 @@ tasks {
|
|||
"ZCASH_GOOGLE_PLAY_DEPLOY_TRACK" to "internal",
|
||||
"ZCASH_GOOGLE_PLAY_DEPLOY_STATUS" to "draft",
|
||||
|
||||
"ZCASH_FLEXA_KEY" to "",
|
||||
"ZCASH_COINBASE_APP_ID" to "",
|
||||
"SDK_INCLUDED_BUILD_PATH" to "",
|
||||
"BIP_39_INCLUDED_BUILD_PATH" to ""
|
||||
|
|
|
@ -11,6 +11,7 @@ directly impact users rather than highlighting other key architectural updates.*
|
|||
|
||||
### Added
|
||||
- The device authentication feature on the Zashi app launch has been added
|
||||
- The Flexa SDK has been adopted to enable payments using the embedded Flexa UI
|
||||
|
||||
## [1.2.1 (760)] - 2024-10-22
|
||||
|
||||
|
|
|
@ -88,6 +88,9 @@ IS_SCREEN_ROTATION_ENABLED=false
|
|||
# set it up.
|
||||
ZCASH_COINBASE_APP_ID=
|
||||
|
||||
# Set the flexa publishable key to setup local integration. Replaced by CI action.
|
||||
ZCASH_FLEXA_KEY=
|
||||
|
||||
# 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
|
||||
|
@ -167,7 +170,7 @@ ANDROIDX_ANNOTATION_VERSION=1.7.1
|
|||
ANDROIDX_BIOMETRIC_VERSION=1.2.0-alpha05
|
||||
ANDROIDX_CAMERA_VERSION=1.3.2
|
||||
ANDROIDX_COMPOSE_COMPILER_VERSION=1.5.11
|
||||
ANDROIDX_COMPOSE_MATERIAL3_VERSION=1.2.1
|
||||
ANDROIDX_COMPOSE_MATERIAL3_VERSION=1.3.1
|
||||
ANDROIDX_COMPOSE_MATERIAL_ICONS_VERSION=1.6.5
|
||||
ANDROIDX_COMPOSE_VERSION=1.6.6
|
||||
ANDROIDX_CONSTRAINTLAYOUT_VERSION=1.0.1
|
||||
|
@ -189,7 +192,7 @@ 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
|
||||
CORE_LIBRARY_DESUGARING_VERSION=2.1.2
|
||||
FIREBASE_BOM_VERSION_MATCHER=33.1.1
|
||||
GOOGLE_API_CLIENT_ANDROID_VERSION=1.26.0
|
||||
GOOGLE_API_SERVICES_DRIVE_VERSION=v3-rev136-1.25.0
|
||||
|
@ -213,6 +216,8 @@ ZXING_VERSION=3.5.3
|
|||
ZIP_321_VERSION = 0.0.6
|
||||
ZCASH_BIP39_VERSION=1.0.8
|
||||
|
||||
FLEXA_VERSION=1.0.5
|
||||
|
||||
# WARNING: Ensure a non-snapshot version is used before releasing to production
|
||||
ZCASH_SDK_VERSION=2.2.5
|
||||
|
||||
|
|
Binary file not shown.
|
@ -0,0 +1,120 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.flexa</groupId>
|
||||
<artifactId>core</artifactId>
|
||||
<version>1.0.5</version>
|
||||
<packaging>aar</packaging>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>androidx.core</groupId>
|
||||
<artifactId>core-ktx</artifactId>
|
||||
<version>1.13.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>androidx.compose.ui</groupId>
|
||||
<artifactId>ui</artifactId>
|
||||
<version>1.7.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>androidx.compose.ui</groupId>
|
||||
<artifactId>ui-tooling-preview</artifactId>
|
||||
<version>1.7.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>androidx.compose.material</groupId>
|
||||
<artifactId>material-icons-extended</artifactId>
|
||||
<version>1.7.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>androidx.compose.material3</groupId>
|
||||
<artifactId>material3</artifactId>
|
||||
<version>1.3.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>androidx.activity</groupId>
|
||||
<artifactId>activity-compose</artifactId>
|
||||
<version>1.9.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>androidx.compose.runtime</groupId>
|
||||
<artifactId>runtime-livedata</artifactId>
|
||||
<version>1.7.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>androidx.navigation</groupId>
|
||||
<artifactId>navigation-compose</artifactId>
|
||||
<version>2.8.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>androidx.webkit</groupId>
|
||||
<artifactId>webkit</artifactId>
|
||||
<version>1.12.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>androidx.lifecycle</groupId>
|
||||
<artifactId>lifecycle-runtime-compose</artifactId>
|
||||
<version>2.8.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>androidx.lifecycle</groupId>
|
||||
<artifactId>lifecycle-viewmodel-compose</artifactId>
|
||||
<version>2.8.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>androidx.appcompat</groupId>
|
||||
<artifactId>appcompat</artifactId>
|
||||
<version>1.7.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.coil-kt</groupId>
|
||||
<artifactId>coil-compose</artifactId>
|
||||
<version>2.7.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>joda-time</groupId>
|
||||
<artifactId>joda-time</artifactId>
|
||||
<version>2.12.7</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>okhttp</artifactId>
|
||||
<version>4.12.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>logging-interceptor</artifactId>
|
||||
<version>4.12.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>okhttp-sse</artifactId>
|
||||
<version>4.12.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>androidx.security</groupId>
|
||||
<artifactId>security-crypto</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.crypto.tink</groupId>
|
||||
<artifactId>tink-android</artifactId>
|
||||
<version>1.8.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
<version>2.11.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlinx</groupId>
|
||||
<artifactId>kotlinx-serialization-json</artifactId>
|
||||
<version>1.6.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>androidx.room</groupId>
|
||||
<artifactId>room-runtime</artifactId>
|
||||
<version>2.6.1</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<metadata>
|
||||
<groupId>com.flexa</groupId>
|
||||
<artifactId>core</artifactId>
|
||||
<versioning>
|
||||
<latest>1.0.5</latest>
|
||||
<release>1.0.5</release>
|
||||
<versions>
|
||||
<version>1.0.5</version>
|
||||
</versions>
|
||||
<lastUpdated>20241030092850</lastUpdated>
|
||||
</versioning>
|
||||
</metadata>
|
Binary file not shown.
|
@ -0,0 +1,105 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.flexa</groupId>
|
||||
<artifactId>spend</artifactId>
|
||||
<version>1.0.5</version>
|
||||
<packaging>aar</packaging>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.flexa</groupId>
|
||||
<artifactId>core</artifactId>
|
||||
<version>1.0.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>androidx.core</groupId>
|
||||
<artifactId>core-ktx</artifactId>
|
||||
<version>1.13.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>androidx.compose.ui</groupId>
|
||||
<artifactId>ui</artifactId>
|
||||
<version>1.7.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>androidx.compose.ui</groupId>
|
||||
<artifactId>ui-util</artifactId>
|
||||
<version>1.7.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>androidx.compose.ui</groupId>
|
||||
<artifactId>ui-tooling-preview</artifactId>
|
||||
<version>1.7.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>androidx.compose.material</groupId>
|
||||
<artifactId>material-icons-extended</artifactId>
|
||||
<version>1.7.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>androidx.compose.material3</groupId>
|
||||
<artifactId>material3</artifactId>
|
||||
<version>1.3.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>androidx.activity</groupId>
|
||||
<artifactId>activity-compose</artifactId>
|
||||
<version>1.9.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>androidx.lifecycle</groupId>
|
||||
<artifactId>lifecycle-viewmodel-compose</artifactId>
|
||||
<version>2.8.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>androidx.navigation</groupId>
|
||||
<artifactId>navigation-compose</artifactId>
|
||||
<version>2.8.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>androidx.compose.runtime</groupId>
|
||||
<artifactId>runtime-livedata</artifactId>
|
||||
<version>1.7.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>androidx.webkit</groupId>
|
||||
<artifactId>webkit</artifactId>
|
||||
<version>1.12.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.coil-kt</groupId>
|
||||
<artifactId>coil-compose</artifactId>
|
||||
<version>2.7.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.coil-kt</groupId>
|
||||
<artifactId>coil-svg</artifactId>
|
||||
<version>2.7.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.caverock</groupId>
|
||||
<artifactId>androidsvg-aar</artifactId>
|
||||
<version>1.4</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.zxing</groupId>
|
||||
<artifactId>core</artifactId>
|
||||
<version>3.5.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-codec</groupId>
|
||||
<artifactId>commons-codec</artifactId>
|
||||
<version>1.15</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>androidx.work</groupId>
|
||||
<artifactId>work-runtime-ktx</artifactId>
|
||||
<version>2.9.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.flexa</groupId>
|
||||
<artifactId>core</artifactId>
|
||||
<version>1.0.5</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<metadata>
|
||||
<groupId>com.flexa</groupId>
|
||||
<artifactId>spend</artifactId>
|
||||
<versioning>
|
||||
<latest>1.0.5</latest>
|
||||
<release>1.0.5</release>
|
||||
<versions>
|
||||
<version>1.0.5</version>
|
||||
</versions>
|
||||
<lastUpdated>20241030092854</lastUpdated>
|
||||
</versioning>
|
||||
</metadata>
|
|
@ -135,6 +135,7 @@ dependencyResolutionManagement {
|
|||
}
|
||||
}
|
||||
}
|
||||
maven("${rootProject.projectDir}/maven") // url to a local maven in this repository
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
|
@ -190,6 +191,7 @@ dependencyResolutionManagement {
|
|||
val googleApiClientAndroidVersion = extra["GOOGLE_API_CLIENT_ANDROID_VERSION"].toString()
|
||||
val googleApiServicesDriveVersion = extra["GOOGLE_API_SERVICES_DRIVE_VERSION"].toString()
|
||||
val playServicesAuthVersion = extra["PLAY_SERVICES_AUTH_VERSION"].toString()
|
||||
val flexaVersion = extra["FLEXA_VERSION"].toString()
|
||||
|
||||
|
||||
// Standalone versions
|
||||
|
@ -259,6 +261,8 @@ dependencyResolutionManagement {
|
|||
library("zxing", "com.google.zxing:core:$zxingVersion")
|
||||
library("koin", "io.insert-koin:koin-android:$koinVersion")
|
||||
library("koin-compose", "io.insert-koin:koin-androidx-compose:$koinVersion")
|
||||
library("flexa-core", "com.flexa:core:$flexaVersion")
|
||||
library("flexa-spend", "com.flexa:spend:$flexaVersion")
|
||||
|
||||
// Test libraries
|
||||
library("androidx-compose-test-junit", "androidx.compose.ui:ui-test-junit4:$androidxComposeVersion")
|
||||
|
|
|
@ -18,6 +18,7 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||
import co.electriccoin.zcash.ui.design.theme.colors.ZashiColors
|
||||
import co.electriccoin.zcash.ui.design.util.StringResource
|
||||
import co.electriccoin.zcash.ui.design.util.getValue
|
||||
|
||||
|
@ -109,8 +110,9 @@ fun AppAlertDialog(
|
|||
text = text,
|
||||
icon = icon?.let { { Icon(imageVector = icon, null) } },
|
||||
properties = properties,
|
||||
titleContentColor = ZcashTheme.colors.textPrimary,
|
||||
textContentColor = ZcashTheme.colors.textPrimary,
|
||||
containerColor = ZashiColors.Surfaces.bgPrimary,
|
||||
titleContentColor = ZashiColors.Text.textPrimary,
|
||||
textContentColor = ZashiColors.Text.textPrimary,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -92,10 +92,12 @@ fun SmallLinearProgressIndicator(
|
|||
modifier: Modifier = Modifier
|
||||
) {
|
||||
LinearProgressIndicator(
|
||||
drawStopIndicator = {},
|
||||
progress = { progress },
|
||||
color = ZcashTheme.colors.linearProgressBarBackground,
|
||||
trackColor = ZcashTheme.colors.linearProgressBarTrack,
|
||||
strokeCap = StrokeCap.Butt,
|
||||
gapSize = 0.dp,
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
|
|
|
@ -18,8 +18,8 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
|||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.ripple.rememberRipple
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.ripple
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
|
@ -56,7 +56,7 @@ fun RadioButton(
|
|||
modifier
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
.clickable(
|
||||
indication = rememberRipple(),
|
||||
indication = ripple(),
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
onClick = state.onClick,
|
||||
role = Role.Button,
|
||||
|
|
|
@ -13,9 +13,10 @@ import androidx.compose.foundation.layout.height
|
|||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.ripple.rememberRipple
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.ripple
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
|
@ -145,7 +146,7 @@ fun ZashiSettingsListContentItem(
|
|||
titleIcons.forEach {
|
||||
Spacer(Modifier.width(6.dp))
|
||||
Image(
|
||||
modifier = Modifier.size(20.dp),
|
||||
modifier = Modifier.size(20.dp).clip(CircleShape),
|
||||
painter = painterResource(it),
|
||||
contentDescription = null,
|
||||
)
|
||||
|
@ -176,7 +177,7 @@ fun ZashiSettingsListItem(
|
|||
.clip(RoundedCornerShape(12.dp)) then
|
||||
if (onClick != null) {
|
||||
Modifier.clickable(
|
||||
indication = rememberRipple(),
|
||||
indication = ripple(),
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
onClick = onClick,
|
||||
role = Role.Button,
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
package co.electriccoin.zcash.ui.design.theme
|
||||
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.LocalRippleConfiguration
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.RippleConfiguration
|
||||
import androidx.compose.material3.RippleDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import co.electriccoin.zcash.ui.design.theme.colors.DarkZashiColorsInternal
|
||||
|
@ -26,6 +31,7 @@ import co.electriccoin.zcash.ui.design.theme.typography.ZashiTypographyInternal
|
|||
* @param forceDarkMode Set this to true to force the app to use the dark mode theme, which is helpful, e.g.,
|
||||
* for the compose previews.
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun ZcashTheme(
|
||||
forceDarkMode: Boolean = false,
|
||||
|
@ -39,7 +45,8 @@ fun ZcashTheme(
|
|||
CompositionLocalProvider(
|
||||
LocalExtendedColors provides extendedColors,
|
||||
LocalZashiColors provides zashiColors,
|
||||
LocalZashiTypography provides ZashiTypographyInternal
|
||||
LocalZashiTypography provides ZashiTypographyInternal,
|
||||
LocalRippleConfiguration provides MaterialRippleConfig,
|
||||
) {
|
||||
ProvideDimens {
|
||||
MaterialTheme(
|
||||
|
@ -71,3 +78,8 @@ object ZcashTheme {
|
|||
@Composable
|
||||
get() = localDimens.current
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
private val MaterialRippleConfig: RippleConfiguration
|
||||
@Composable
|
||||
get() = RippleConfiguration(color = LocalContentColor.current, rippleAlpha = RippleDefaults.RippleAlpha)
|
||||
|
|
|
@ -82,6 +82,14 @@ androidComponents {
|
|||
comment = "Whether is the SecureScreen sensitive data protection enabled"
|
||||
)
|
||||
)
|
||||
variant.buildConfigFields.put(
|
||||
"ZCASH_FLEXA_KEY",
|
||||
BuildConfigField(
|
||||
type = "String",
|
||||
value = "\"${project.property("ZCASH_FLEXA_KEY")?.toString().orEmpty()}\"",
|
||||
comment = "Publishable key of the Flexa integration"
|
||||
)
|
||||
)
|
||||
variant.buildConfigFields.put(
|
||||
"ZCASH_COINBASE_APP_ID",
|
||||
BuildConfigField(
|
||||
|
@ -129,6 +137,9 @@ dependencies {
|
|||
implementation(libs.zcash.bip39)
|
||||
implementation(libs.zxing)
|
||||
|
||||
api(libs.flexa.core)
|
||||
api(libs.flexa.spend)
|
||||
|
||||
implementation(projects.buildInfoLib)
|
||||
implementation(projects.configurationApiLib)
|
||||
implementation(projects.crashAndroidLib)
|
||||
|
|
|
@ -22,6 +22,11 @@
|
|||
android:label="@string/app_name"
|
||||
android:theme="@style/Theme.App.Starting" />
|
||||
|
||||
<activity
|
||||
android:name=".BiometricActivity"
|
||||
android:exported="false"
|
||||
android:theme="@style/Theme.App.Transparent" />
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
|
|
@ -4,10 +4,14 @@ import co.electriccoin.zcash.ui.common.repository.AddressBookRepository
|
|||
import co.electriccoin.zcash.ui.common.repository.AddressBookRepositoryImpl
|
||||
import co.electriccoin.zcash.ui.common.repository.BalanceRepository
|
||||
import co.electriccoin.zcash.ui.common.repository.BalanceRepositoryImpl
|
||||
import co.electriccoin.zcash.ui.common.repository.BiometricRepository
|
||||
import co.electriccoin.zcash.ui.common.repository.BiometricRepositoryImpl
|
||||
import co.electriccoin.zcash.ui.common.repository.ConfigurationRepository
|
||||
import co.electriccoin.zcash.ui.common.repository.ConfigurationRepositoryImpl
|
||||
import co.electriccoin.zcash.ui.common.repository.ExchangeRateRepository
|
||||
import co.electriccoin.zcash.ui.common.repository.ExchangeRateRepositoryImpl
|
||||
import co.electriccoin.zcash.ui.common.repository.FlexaRepository
|
||||
import co.electriccoin.zcash.ui.common.repository.FlexaRepositoryImpl
|
||||
import co.electriccoin.zcash.ui.common.repository.WalletRepository
|
||||
import co.electriccoin.zcash.ui.common.repository.WalletRepositoryImpl
|
||||
import org.koin.core.module.dsl.singleOf
|
||||
|
@ -21,4 +25,6 @@ val repositoryModule =
|
|||
singleOf(::ExchangeRateRepositoryImpl) bind ExchangeRateRepository::class
|
||||
singleOf(::BalanceRepositoryImpl) bind BalanceRepository::class
|
||||
singleOf(::AddressBookRepositoryImpl) bind AddressBookRepository::class
|
||||
singleOf(::FlexaRepositoryImpl) bind FlexaRepository::class
|
||||
singleOf(::BiometricRepositoryImpl) bind BiometricRepository::class
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import co.electriccoin.zcash.ui.common.usecase.GetSpendingKeyUseCase
|
|||
import co.electriccoin.zcash.ui.common.usecase.GetSynchronizerUseCase
|
||||
import co.electriccoin.zcash.ui.common.usecase.GetTransparentAddressUseCase
|
||||
import co.electriccoin.zcash.ui.common.usecase.IsCoinbaseAvailableUseCase
|
||||
import co.electriccoin.zcash.ui.common.usecase.IsFlexaAvailableUseCase
|
||||
import co.electriccoin.zcash.ui.common.usecase.ObserveAddressBookContactsUseCase
|
||||
import co.electriccoin.zcash.ui.common.usecase.ObserveConfigurationUseCase
|
||||
import co.electriccoin.zcash.ui.common.usecase.ObserveContactByAddressUseCase
|
||||
|
@ -60,6 +61,7 @@ val useCaseModule =
|
|||
singleOf(::ObserveContactPickedUseCase)
|
||||
singleOf(::GetAddressesUseCase)
|
||||
singleOf(::CopyToClipboardUseCase)
|
||||
singleOf(::IsFlexaAvailableUseCase)
|
||||
singleOf(::ShareImageUseCase)
|
||||
singleOf(::Zip321BuildUriUseCase)
|
||||
singleOf(::Zip321ProposalFromUriUseCase)
|
||||
|
|
|
@ -79,4 +79,5 @@ val viewModelModule =
|
|||
zip321ParseUriValidationUseCase = get(),
|
||||
)
|
||||
}
|
||||
viewModelOf(::IntegrationsViewModel)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
package co.electriccoin.zcash.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.biometric.BiometricPrompt
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import co.electriccoin.zcash.ui.common.repository.BiometricRepository
|
||||
import co.electriccoin.zcash.ui.common.repository.BiometricResult
|
||||
import org.koin.android.ext.android.inject
|
||||
|
||||
class BiometricActivity : FragmentActivity() {
|
||||
private val biometricRepository by inject<BiometricRepository>()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
val requestCode = intent.getStringExtra(EXTRA_REQUEST_CODE).orEmpty()
|
||||
val subtitle = intent.getStringExtra(EXTRA_SUBTITLE).orEmpty()
|
||||
|
||||
val biometricPrompt =
|
||||
BiometricPrompt(
|
||||
this,
|
||||
ContextCompat.getMainExecutor(application),
|
||||
object : BiometricPrompt.AuthenticationCallback() {
|
||||
override fun onAuthenticationError(
|
||||
errorCode: Int,
|
||||
errString: CharSequence
|
||||
) {
|
||||
super.onAuthenticationError(errorCode, errString)
|
||||
biometricRepository.onBiometricResult(BiometricResult.Failure(requestCode))
|
||||
finish()
|
||||
}
|
||||
|
||||
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
|
||||
super.onAuthenticationSucceeded(result)
|
||||
biometricRepository.onBiometricResult(BiometricResult.Success(requestCode))
|
||||
finish()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
val promptInfo =
|
||||
BiometricPrompt.PromptInfo.Builder()
|
||||
.setTitle(
|
||||
getString(R.string.authentication_system_ui_title, getString(R.string.app_name))
|
||||
)
|
||||
.setSubtitle(subtitle)
|
||||
.setAllowedAuthenticators(biometricRepository.allowedAuthenticators)
|
||||
.build()
|
||||
|
||||
biometricPrompt.authenticate(promptInfo)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val EXTRA_REQUEST_CODE = "EXTRA_REQUEST_CODE"
|
||||
private const val EXTRA_SUBTITLE = "EXTRA_SUBTITLE"
|
||||
|
||||
fun createIntent(
|
||||
context: Context,
|
||||
requestCode: String,
|
||||
subtitle: String
|
||||
) = Intent(context, BiometricActivity::class.java).apply {
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
putExtra(EXTRA_REQUEST_CODE, requestCode)
|
||||
putExtra(EXTRA_SUBTITLE, subtitle)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -73,6 +73,7 @@ private fun BalanceWidgetNotAvailableYetPreview() {
|
|||
balanceState =
|
||||
BalanceState.Loading(
|
||||
totalBalance = Zatoshi(value = 0L),
|
||||
spendableBalance = Zatoshi(value = 0L),
|
||||
exchangeRate = ObserveFiatCurrencyResultFixture.new()
|
||||
),
|
||||
isHideBalances = false,
|
||||
|
@ -96,6 +97,7 @@ private fun BalanceWidgetHiddenAmountPreview() {
|
|||
balanceState =
|
||||
BalanceState.Loading(
|
||||
totalBalance = Zatoshi(0L),
|
||||
spendableBalance = Zatoshi(0L),
|
||||
exchangeRate = ObserveFiatCurrencyResultFixture.new()
|
||||
),
|
||||
isHideBalances = true,
|
||||
|
@ -109,23 +111,26 @@ private fun BalanceWidgetHiddenAmountPreview() {
|
|||
|
||||
sealed interface BalanceState {
|
||||
val totalBalance: Zatoshi
|
||||
val spendableBalance: Zatoshi
|
||||
val exchangeRate: ExchangeRateState
|
||||
|
||||
data class None(
|
||||
override val exchangeRate: ExchangeRateState
|
||||
) : BalanceState {
|
||||
override val totalBalance: Zatoshi = Zatoshi(0L)
|
||||
override val spendableBalance: Zatoshi = Zatoshi(0L)
|
||||
}
|
||||
|
||||
data class Loading(
|
||||
override val totalBalance: Zatoshi,
|
||||
override val spendableBalance: Zatoshi,
|
||||
override val exchangeRate: ExchangeRateState
|
||||
) : BalanceState
|
||||
|
||||
data class Available(
|
||||
override val totalBalance: Zatoshi,
|
||||
override val exchangeRate: ExchangeRateState,
|
||||
val spendableBalance: Zatoshi
|
||||
override val spendableBalance: Zatoshi,
|
||||
override val exchangeRate: ExchangeRateState
|
||||
) : BalanceState
|
||||
}
|
||||
|
||||
|
|
|
@ -48,6 +48,7 @@ class BalanceRepositoryImpl(
|
|||
) -> {
|
||||
BalanceState.Loading(
|
||||
totalBalance = snapshot.totalBalance(),
|
||||
spendableBalance = snapshot.spendableBalance(),
|
||||
exchangeRate = exchangeRateUsd
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,108 @@
|
|||
package co.electriccoin.zcash.ui.common.repository
|
||||
|
||||
import android.content.Context
|
||||
import androidx.biometric.BiometricManager
|
||||
import co.electriccoin.zcash.spackle.AndroidApiVersion
|
||||
import co.electriccoin.zcash.ui.BiometricActivity
|
||||
import co.electriccoin.zcash.ui.design.util.StringResource
|
||||
import co.electriccoin.zcash.ui.design.util.getString
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.UUID
|
||||
|
||||
interface BiometricRepository {
|
||||
val allowedAuthenticators: Int
|
||||
|
||||
fun onBiometricResult(result: BiometricResult)
|
||||
|
||||
@Throws(BiometricsFailureException::class, BiometricsCancelledException::class)
|
||||
suspend fun requestBiometrics(request: BiometricRequest)
|
||||
}
|
||||
|
||||
data class BiometricRequest(
|
||||
val message: StringResource,
|
||||
val requestCode: String = UUID.randomUUID().toString(),
|
||||
)
|
||||
|
||||
sealed interface BiometricResult {
|
||||
val requestCode: String
|
||||
|
||||
data class Success(override val requestCode: String) : BiometricResult
|
||||
|
||||
data class Failure(override val requestCode: String) : BiometricResult
|
||||
|
||||
data class Cancelled(override val requestCode: String) : BiometricResult
|
||||
}
|
||||
|
||||
class BiometricsFailureException : Exception()
|
||||
|
||||
class BiometricsCancelledException : Exception()
|
||||
|
||||
class BiometricRepositoryImpl(
|
||||
private val context: Context,
|
||||
private val biometricManager: BiometricManager
|
||||
) : BiometricRepository {
|
||||
private val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())
|
||||
|
||||
private val onBiometricsResult = MutableSharedFlow<BiometricResult>()
|
||||
|
||||
override val allowedAuthenticators: Int
|
||||
get() =
|
||||
when {
|
||||
// Android SDK version == 27
|
||||
(AndroidApiVersion.isExactlyO) ->
|
||||
BiometricManager.Authenticators.BIOMETRIC_STRONG or
|
||||
BiometricManager.Authenticators.DEVICE_CREDENTIAL
|
||||
// Android SDK version >= 30
|
||||
(AndroidApiVersion.isAtLeastR) ->
|
||||
BiometricManager.Authenticators.BIOMETRIC_STRONG or
|
||||
BiometricManager.Authenticators.DEVICE_CREDENTIAL
|
||||
// Android SDK version == 28 || 29
|
||||
(AndroidApiVersion.isExactlyP || AndroidApiVersion.isExactlyQ) ->
|
||||
BiometricManager.Authenticators.BIOMETRIC_WEAK or
|
||||
BiometricManager.Authenticators.DEVICE_CREDENTIAL
|
||||
|
||||
else -> error("Unsupported Android SDK version")
|
||||
}
|
||||
|
||||
override fun onBiometricResult(result: BiometricResult) {
|
||||
scope.launch {
|
||||
onBiometricsResult.emit(result)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun requestBiometrics(request: BiometricRequest) {
|
||||
if (!canAuthenticate()) {
|
||||
// do nothing
|
||||
return
|
||||
}
|
||||
|
||||
context.startActivity(
|
||||
BiometricActivity.createIntent(
|
||||
context = context,
|
||||
requestCode = request.requestCode,
|
||||
subtitle = request.message.getString(context)
|
||||
)
|
||||
)
|
||||
when (
|
||||
onBiometricsResult.filter { it.requestCode == request.requestCode }.first()
|
||||
) {
|
||||
is BiometricResult.Cancelled -> throw BiometricsCancelledException()
|
||||
is BiometricResult.Failure -> throw BiometricsFailureException()
|
||||
is BiometricResult.Success -> {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun canAuthenticate(): Boolean =
|
||||
when (biometricManager.canAuthenticate(allowedAuthenticators)) {
|
||||
BiometricManager.BIOMETRIC_SUCCESS -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
package co.electriccoin.zcash.ui.common.repository
|
||||
|
||||
import android.app.Application
|
||||
import cash.z.ecc.android.sdk.ext.convertZatoshiToZec
|
||||
import cash.z.ecc.android.sdk.internal.Twig
|
||||
import co.electriccoin.zcash.ui.BuildConfig
|
||||
import com.flexa.core.Flexa
|
||||
import com.flexa.core.shared.AssetAccount
|
||||
import com.flexa.core.shared.AvailableAsset
|
||||
import com.flexa.core.shared.CustodyModel
|
||||
import com.flexa.core.shared.FlexaClientConfiguration
|
||||
import com.flexa.core.theme.FlexaTheme
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import java.security.MessageDigest
|
||||
import java.util.UUID
|
||||
|
||||
interface FlexaRepository {
|
||||
fun init()
|
||||
}
|
||||
|
||||
class FlexaRepositoryImpl(
|
||||
private val balanceRepository: BalanceRepository,
|
||||
private val application: Application,
|
||||
) : FlexaRepository {
|
||||
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||
|
||||
private val publishableKey: String?
|
||||
get() = BuildConfig.ZCASH_FLEXA_KEY.takeIf { it.isNotEmpty() }
|
||||
|
||||
override fun init() {
|
||||
scope.launch {
|
||||
val configuration = getFlexaClientConfiguration()
|
||||
if (configuration != null) {
|
||||
Flexa.init(configuration)
|
||||
Twig.info { "Flexa initialized" }
|
||||
|
||||
balanceRepository.state
|
||||
.map { it.spendableBalance }
|
||||
.collect {
|
||||
Flexa.updateAssetAccounts(
|
||||
arrayListOf(
|
||||
createFlexaAccount(
|
||||
zecBalance = it.convertZatoshiToZec().toDouble()
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
Twig.info { "Flexa updated by ${it.convertZatoshiToZec().toDouble()}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return an instance of [FlexaClientConfiguration] or null if no publishable key set up
|
||||
*/
|
||||
private fun getFlexaClientConfiguration() =
|
||||
publishableKey?.let { publishableKey ->
|
||||
FlexaClientConfiguration(
|
||||
context = application,
|
||||
publishableKey = publishableKey,
|
||||
theme =
|
||||
FlexaTheme(
|
||||
useDynamicColorScheme = true,
|
||||
),
|
||||
assetAccounts = arrayListOf(createFlexaAccount(DEFAULT_ZEC_BALANCE)),
|
||||
webViewThemeConfig =
|
||||
"{\n" +
|
||||
" \"android\": {\n" +
|
||||
" \"light\": {\n" +
|
||||
" \"backgroundColor\": \"#100e29\",\n" +
|
||||
" \"sortTextColor\": \"#ed7f60\",\n" +
|
||||
" \"titleColor\": \"#ffffff\",\n" +
|
||||
" \"cardColor\": \"#2a254e\",\n" +
|
||||
" \"borderRadius\": \"15px\",\n" +
|
||||
" \"textColor\": \"#ffffff\"\n" +
|
||||
" },\n" +
|
||||
" \"dark\": {\n" +
|
||||
" \"backgroundColor\": \"#100e29\",\n" +
|
||||
" \"sortTextColor\": \"#ed7f60\",\n" +
|
||||
" \"titleColor\": \"#ffffff\",\n" +
|
||||
" \"cardColor\": \"#2a254e\",\n" +
|
||||
" \"borderRadius\": \"15px\",\n" +
|
||||
" \"textColor\": \"#ffffff\"\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
"}"
|
||||
)
|
||||
}
|
||||
|
||||
private fun createFlexaAccount(zecBalance: Double) =
|
||||
AssetAccount(
|
||||
displayName = "",
|
||||
icon = "https://flexa.network/static/4bbb1733b3ef41240ca0f0675502c4f7/d8419/flexa-logo%403x.png",
|
||||
availableAssets =
|
||||
listOf(
|
||||
AvailableAsset(
|
||||
assetId = "bip122:00040fe8ec8471911baa1db1266ea15d/slip44:133",
|
||||
balance = zecBalance,
|
||||
symbol = "ZEC",
|
||||
)
|
||||
),
|
||||
custodyModel = CustodyModel.LOCAL,
|
||||
assetAccountHash = UUID.randomUUID().toString().toSha256()
|
||||
)
|
||||
|
||||
private fun String.toSha256() =
|
||||
MessageDigest.getInstance("SHA-256")
|
||||
.digest(toByteArray())
|
||||
.fold("") { str, value -> str + "%02x".format(value) }
|
||||
}
|
||||
|
||||
private const val DEFAULT_ZEC_BALANCE = .0
|
|
@ -0,0 +1,14 @@
|
|||
package co.electriccoin.zcash.ui.common.usecase
|
||||
|
||||
import co.electriccoin.zcash.ui.BuildConfig
|
||||
import co.electriccoin.zcash.ui.common.provider.GetVersionInfoProvider
|
||||
|
||||
class IsFlexaAvailableUseCase(
|
||||
private val getVersionInfo: GetVersionInfoProvider,
|
||||
) {
|
||||
operator fun invoke(): Boolean {
|
||||
val versionInfo = getVersionInfo()
|
||||
val isDebug = versionInfo.let { it.isDebuggable && !it.isRunningUnderTestService }
|
||||
return !versionInfo.isTestnet && (BuildConfig.ZCASH_FLEXA_KEY.isNotEmpty() || isDebug)
|
||||
}
|
||||
}
|
|
@ -29,11 +29,15 @@ import co.electriccoin.zcash.ui.common.repository.BalanceRepository
|
|||
import co.electriccoin.zcash.ui.common.repository.ExchangeRateRepository
|
||||
import co.electriccoin.zcash.ui.common.repository.WalletRepository
|
||||
import co.electriccoin.zcash.ui.common.usecase.DeleteAddressBookUseCase
|
||||
import co.electriccoin.zcash.ui.common.usecase.IsFlexaAvailableUseCase
|
||||
import co.electriccoin.zcash.ui.preference.StandardPreferenceKeys
|
||||
import co.electriccoin.zcash.ui.screen.account.ext.TransactionOverviewExt
|
||||
import co.electriccoin.zcash.ui.screen.account.ext.getSortHeight
|
||||
import co.electriccoin.zcash.ui.screen.account.state.TransactionHistorySyncState
|
||||
import com.flexa.core.Flexa
|
||||
import com.flexa.identity.buildIdentity
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
@ -45,8 +49,11 @@ import kotlinx.coroutines.flow.combine
|
|||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
// To make this more multiplatform compatible, we need to remove the dependency on Context
|
||||
// for loading the preferences.
|
||||
|
@ -63,6 +70,7 @@ class WalletViewModel(
|
|||
private val standardPreferenceProvider: StandardPreferenceProvider,
|
||||
private val getAvailableServers: GetDefaultServersProvider,
|
||||
private val deleteAddressBookUseCase: DeleteAddressBookUseCase,
|
||||
private val isFlexaAvailable: IsFlexaAvailableUseCase
|
||||
) : AndroidViewModel(application) {
|
||||
val navigationCommand = exchangeRateRepository.navigationCommand
|
||||
|
||||
|
@ -234,29 +242,27 @@ class WalletViewModel(
|
|||
fun deleteWalletFlow(activity: Activity): Flow<Boolean> =
|
||||
callbackFlow {
|
||||
Twig.info { "Delete wallet: Requested" }
|
||||
|
||||
disconnectFlexa()
|
||||
val synchronizer = synchronizer.value
|
||||
if (null != synchronizer) {
|
||||
viewModelScope.launch {
|
||||
(synchronizer as SdkSynchronizer).closeFlow().collect {
|
||||
Twig.info { "Delete wallet: SDK closed" }
|
||||
(synchronizer as SdkSynchronizer).closeFlow().collect {
|
||||
Twig.info { "Delete wallet: SDK closed" }
|
||||
|
||||
walletCoordinator.deleteSdkDataFlow().collect { isSdkErased ->
|
||||
Twig.info { "Delete wallet: Erase SDK result: $isSdkErased" }
|
||||
if (!isSdkErased) {
|
||||
walletCoordinator.deleteSdkDataFlow().collect { isSdkErased ->
|
||||
Twig.info { "Delete wallet: Erase SDK result: $isSdkErased" }
|
||||
if (!isSdkErased) {
|
||||
trySend(false)
|
||||
}
|
||||
|
||||
clearAppStateFlow().collect { isAppErased ->
|
||||
Twig.info { "Delete wallet: Erase App result: $isAppErased" }
|
||||
if (!isAppErased) {
|
||||
trySend(false)
|
||||
}
|
||||
|
||||
clearAppStateFlow().collect { isAppErased ->
|
||||
Twig.info { "Delete wallet: Erase App result: $isAppErased" }
|
||||
if (!isAppErased) {
|
||||
trySend(false)
|
||||
} else {
|
||||
trySend(true)
|
||||
activity.run {
|
||||
finish()
|
||||
startActivity(Intent(this, MainActivity::class.java))
|
||||
}
|
||||
} else {
|
||||
trySend(true)
|
||||
activity.run {
|
||||
finish()
|
||||
startActivity(Intent(this, MainActivity::class.java))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -266,6 +272,16 @@ class WalletViewModel(
|
|||
awaitClose {
|
||||
// Nothing to close
|
||||
}
|
||||
}.flowOn(Dispatchers.Main)
|
||||
|
||||
private suspend fun disconnectFlexa() =
|
||||
suspendCoroutine { cont ->
|
||||
if (isFlexaAvailable()) {
|
||||
Flexa.buildIdentity().build().disconnect()
|
||||
cont.resume(Unit)
|
||||
} else {
|
||||
cont.resume(Unit)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,11 +18,11 @@ import androidx.compose.foundation.layout.padding
|
|||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.ripple.rememberRipple
|
||||
import androidx.compose.material3.DividerDefaults
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.ripple
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
|
@ -628,7 +628,7 @@ private fun HistoryItemExpandedAddressPart(
|
|||
Modifier
|
||||
.clickable(
|
||||
role = Role.Button,
|
||||
indication = rememberRipple(radius = 2.dp, color = ZashiColors.Text.textTertiary),
|
||||
indication = ripple(radius = 2.dp, color = ZashiColors.Text.textTertiary),
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) { onAction(TrxItemAction.AddressClick(recipient)) }
|
||||
)
|
||||
|
@ -646,7 +646,7 @@ private fun HistoryItemExpandedAddressPart(
|
|||
.weight(1f)
|
||||
.clickable(
|
||||
role = Role.Button,
|
||||
indication = rememberRipple(radius = 2.dp, color = ZashiColors.Text.textTertiary),
|
||||
indication = ripple(radius = 2.dp, color = ZashiColors.Text.textTertiary),
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) { onAction(TrxItemAction.AddToAddressBookClick(recipient)) }
|
||||
)
|
||||
|
@ -781,7 +781,7 @@ private fun HistoryItemTransactionIdPart(
|
|||
Modifier
|
||||
.clickable(
|
||||
role = Role.Button,
|
||||
indication = rememberRipple(radius = 2.dp, color = ZashiColors.Text.textTertiary),
|
||||
indication = ripple(radius = 2.dp, color = ZashiColors.Text.textTertiary),
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) { onAction(TrxItemAction.TransactionIdClick(txIdString)) }
|
||||
)
|
||||
|
@ -944,7 +944,7 @@ private fun HistoryItemMessagePart(
|
|||
.clickable(
|
||||
onClick = { onAction(TrxItemAction.MessageClick(message)) },
|
||||
role = Role.Button,
|
||||
indication = rememberRipple(radius = 2.dp, color = ZashiColors.Text.textTertiary),
|
||||
indication = ripple(radius = 2.dp, color = ZashiColors.Text.textTertiary),
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
)
|
||||
)
|
||||
|
|
|
@ -253,7 +253,7 @@ private fun ExchangeRateButton(
|
|||
if (isEnabled && enableBorder) {
|
||||
ZashiColors.Surfaces.bgPrimary orDark ZashiColors.Surfaces.bgTertiary
|
||||
} else {
|
||||
Color.Unspecified
|
||||
Color.Transparent
|
||||
},
|
||||
disabledContainerColor = Color.Transparent,
|
||||
disabledContentColor = textColor,
|
||||
|
|
|
@ -112,7 +112,6 @@ private fun HomeContent(
|
|||
key = { index ->
|
||||
subScreens[index].title
|
||||
},
|
||||
beyondBoundsPageCount = 1,
|
||||
modifier =
|
||||
Modifier.constrainAs(pager) {
|
||||
top.linkTo(parent.top)
|
||||
|
|
|
@ -13,6 +13,8 @@ import co.electriccoin.zcash.ui.common.compose.LocalNavController
|
|||
import co.electriccoin.zcash.ui.common.viewmodel.WalletViewModel
|
||||
import co.electriccoin.zcash.ui.screen.integrations.view.Integrations
|
||||
import co.electriccoin.zcash.ui.screen.integrations.viewmodel.IntegrationsViewModel
|
||||
import com.flexa.core.Flexa
|
||||
import com.flexa.spend.buildSpend
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
|
||||
@Composable
|
||||
|
@ -42,6 +44,17 @@ internal fun WrapIntegrations() {
|
|||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
viewModel.flexaNavigationCommand.collect {
|
||||
Flexa.buildSpend()
|
||||
.onTransactionRequest {
|
||||
viewModel.onFlexaResultCallback(it)
|
||||
}
|
||||
.build()
|
||||
.open(activity)
|
||||
}
|
||||
}
|
||||
|
||||
BackHandler {
|
||||
viewModel.onBack()
|
||||
}
|
||||
|
|
|
@ -8,5 +8,5 @@ data class IntegrationsState(
|
|||
val version: StringResource,
|
||||
val disabledInfo: StringResource?,
|
||||
val onBack: () -> Unit,
|
||||
val items: ImmutableList<ZashiSettingsListItemState>
|
||||
val items: ImmutableList<ZashiSettingsListItemState>,
|
||||
)
|
||||
|
|
|
@ -1,19 +1,41 @@
|
|||
package co.electriccoin.zcash.ui.screen.integrations.viewmodel
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import cash.z.ecc.android.sdk.SdkSynchronizer
|
||||
import cash.z.ecc.android.sdk.Synchronizer
|
||||
import cash.z.ecc.android.sdk.model.Proposal
|
||||
import cash.z.ecc.android.sdk.model.TransactionSubmitResult
|
||||
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
|
||||
import cash.z.ecc.android.sdk.model.WalletAddress
|
||||
import cash.z.ecc.android.sdk.model.ZecSend
|
||||
import cash.z.ecc.android.sdk.model.ZecSendExt
|
||||
import cash.z.ecc.android.sdk.model.proposeSend
|
||||
import cash.z.ecc.android.sdk.type.AddressType
|
||||
import cash.z.ecc.sdk.ANDROID_STATE_FLOW_TIMEOUT
|
||||
import co.electriccoin.zcash.spackle.Twig
|
||||
import co.electriccoin.zcash.ui.BuildConfig
|
||||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.common.model.TopAppBarSubTitleState
|
||||
import co.electriccoin.zcash.ui.common.provider.GetVersionInfoProvider
|
||||
import co.electriccoin.zcash.ui.common.provider.GetZcashCurrencyProvider
|
||||
import co.electriccoin.zcash.ui.common.repository.BiometricRepository
|
||||
import co.electriccoin.zcash.ui.common.repository.BiometricRequest
|
||||
import co.electriccoin.zcash.ui.common.usecase.GetSpendingKeyUseCase
|
||||
import co.electriccoin.zcash.ui.common.usecase.GetSynchronizerUseCase
|
||||
import co.electriccoin.zcash.ui.common.usecase.GetTransparentAddressUseCase
|
||||
import co.electriccoin.zcash.ui.common.usecase.IsCoinbaseAvailableUseCase
|
||||
import co.electriccoin.zcash.ui.common.usecase.IsFlexaAvailableUseCase
|
||||
import co.electriccoin.zcash.ui.common.usecase.ObserveWalletStateUseCase
|
||||
import co.electriccoin.zcash.ui.design.component.ZashiSettingsListItemState
|
||||
import co.electriccoin.zcash.ui.design.util.stringRes
|
||||
import co.electriccoin.zcash.ui.screen.integrations.model.IntegrationsState
|
||||
import co.electriccoin.zcash.ui.screen.send.model.RecipientAddressState
|
||||
import co.electriccoin.zcash.ui.screen.sendconfirmation.model.SubmitResult
|
||||
import com.flexa.core.Flexa
|
||||
import com.flexa.spend.Transaction
|
||||
import com.flexa.spend.buildSpend
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
|
@ -26,10 +48,16 @@ class IntegrationsViewModel(
|
|||
getVersionInfo: GetVersionInfoProvider,
|
||||
getZcashCurrency: GetZcashCurrencyProvider,
|
||||
observeWalletState: ObserveWalletStateUseCase,
|
||||
private val getSynchronizer: GetSynchronizerUseCase,
|
||||
private val getTransparentAddress: GetTransparentAddressUseCase,
|
||||
private val isFlexaAvailable: IsFlexaAvailableUseCase,
|
||||
private val isCoinbaseAvailable: IsCoinbaseAvailableUseCase,
|
||||
private val getSpendingKey: GetSpendingKeyUseCase,
|
||||
private val context: Context,
|
||||
private val biometricRepository: BiometricRepository
|
||||
) : ViewModel() {
|
||||
val backNavigationCommand = MutableSharedFlow<Unit>()
|
||||
val flexaNavigationCommand = MutableSharedFlow<Unit>()
|
||||
val coinbaseNavigationCommand = MutableSharedFlow<String>()
|
||||
|
||||
private val versionInfo = getVersionInfo()
|
||||
|
@ -60,7 +88,21 @@ class IntegrationsViewModel(
|
|||
getZcashCurrency.getLocalizedName()
|
||||
),
|
||||
onClick = ::onBuyWithCoinbaseClicked
|
||||
).takeIf { isCoinbaseAvailable() }
|
||||
).takeIf { isCoinbaseAvailable() },
|
||||
ZashiSettingsListItemState(
|
||||
// Set the wallet currency by app build is more future-proof, although we hide it from
|
||||
// the UI in the Testnet build
|
||||
isEnabled = isEnabled,
|
||||
icon =
|
||||
if (isEnabled) {
|
||||
R.drawable.ic_integrations_flexa
|
||||
} else {
|
||||
R.drawable.ic_integrations_flexa_disabled
|
||||
},
|
||||
text = stringRes(R.string.integrations_flexa),
|
||||
subtitle = stringRes(R.string.integrations_flexa_subtitle),
|
||||
onClick = ::onFlexaClicked
|
||||
).takeIf { isFlexaAvailable() }
|
||||
).toImmutableList()
|
||||
)
|
||||
}.stateIn(
|
||||
|
@ -98,4 +140,168 @@ class IntegrationsViewModel(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onFlexaClicked() =
|
||||
viewModelScope.launch {
|
||||
flexaNavigationCommand.emit(Unit)
|
||||
}
|
||||
|
||||
fun onFlexaResultCallback(transaction: Result<Transaction>) =
|
||||
viewModelScope.launch {
|
||||
runCatching {
|
||||
biometricRepository.requestBiometrics(
|
||||
BiometricRequest(message = stringRes(R.string.integrations_biometric_message))
|
||||
)
|
||||
Twig.debug { "Getting send transaction proposal" }
|
||||
getSynchronizer()
|
||||
.proposeSend(
|
||||
account = getSpendingKey().account,
|
||||
send = getZecSend(transaction.getOrNull())
|
||||
)
|
||||
}.onSuccess { proposal ->
|
||||
Twig.debug { "Transaction proposal successful: ${proposal.toPrettyString()}" }
|
||||
val result = submitTransactions(proposal = proposal, spendingKey = getSpendingKey())
|
||||
when (result.first) {
|
||||
SubmitResult.Success -> {
|
||||
Twig.debug { "Transaction successful $result" }
|
||||
Flexa.buildSpend()
|
||||
.transactionSent(
|
||||
commerceSessionId = transaction.getOrNull()?.commerceSessionId.orEmpty(),
|
||||
txSignature = result.second.orEmpty()
|
||||
)
|
||||
}
|
||||
|
||||
else -> {
|
||||
Twig.error { "Transaction submission failed" }
|
||||
}
|
||||
}
|
||||
}.onFailure {
|
||||
Twig.error(it) { "Transaction proposal failed" }
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun submitTransactions(
|
||||
proposal: Proposal,
|
||||
spendingKey: UnifiedSpendingKey
|
||||
): Pair<SubmitResult, String?> {
|
||||
Twig.debug { "Sending transactions..." }
|
||||
|
||||
val result =
|
||||
runCreateTransactions(
|
||||
synchronizer = getSynchronizer(),
|
||||
spendingKey = spendingKey,
|
||||
proposal = proposal
|
||||
)
|
||||
|
||||
// Triggering the transaction history and balances refresh to be notified immediately
|
||||
// about the wallet's updated state
|
||||
(getSynchronizer() as SdkSynchronizer).run {
|
||||
refreshTransactions()
|
||||
refreshAllBalances()
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private suspend fun runCreateTransactions(
|
||||
synchronizer: Synchronizer,
|
||||
spendingKey: UnifiedSpendingKey,
|
||||
proposal: Proposal
|
||||
): Pair<SubmitResult, String?> {
|
||||
val submitResults = mutableListOf<TransactionSubmitResult>()
|
||||
|
||||
return runCatching {
|
||||
synchronizer.createProposedTransactions(
|
||||
proposal = proposal,
|
||||
usk = spendingKey
|
||||
).collect { submitResult ->
|
||||
Twig.info { "Transaction submit result: $submitResult" }
|
||||
submitResults.add(submitResult)
|
||||
}
|
||||
if (submitResults.find { it is TransactionSubmitResult.Failure } != null) {
|
||||
if (submitResults.size == 1) {
|
||||
// The first transaction submission failed - user might just be able to re-submit the transaction
|
||||
// proposal. Simple error pop up is fine then
|
||||
val result = (submitResults[0] as TransactionSubmitResult.Failure)
|
||||
if (result.grpcError) {
|
||||
SubmitResult.SimpleTrxFailure.SimpleTrxFailureGrpc(result) to null
|
||||
} else {
|
||||
SubmitResult.SimpleTrxFailure.SimpleTrxFailureSubmit(result) to null
|
||||
}
|
||||
} else {
|
||||
// Any subsequent transaction submission failed - user needs to resolve this manually. Multiple
|
||||
// transaction failure screen presented
|
||||
SubmitResult.MultipleTrxFailure to null
|
||||
}
|
||||
} else {
|
||||
// All transaction submissions were successful
|
||||
SubmitResult.Success to
|
||||
submitResults.filterIsInstance<TransactionSubmitResult.Success>()
|
||||
.map { it.txIdString() }.firstOrNull()
|
||||
}
|
||||
}.onSuccess {
|
||||
Twig.debug { "Transactions submitted successfully" }
|
||||
}.onFailure {
|
||||
Twig.error(it) { "Transactions submission failed" }
|
||||
}.getOrElse {
|
||||
SubmitResult.SimpleTrxFailure.SimpleTrxFailureOther(it) to null
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("TooGenericExceptionThrown")
|
||||
private suspend fun getZecSend(transaction: Transaction?): ZecSend {
|
||||
if (transaction == null) throw NullPointerException("Transaction is null")
|
||||
|
||||
val address = transaction.destinationAddress.split(":").last()
|
||||
|
||||
val recipientAddressState =
|
||||
RecipientAddressState.new(
|
||||
address = address,
|
||||
// TODO [#342]: Verify Addresses without Synchronizer
|
||||
// TODO [#342]: https://github.com/zcash/zcash-android-wallet-sdk/issues/342
|
||||
type = getSynchronizer().validateAddress(address)
|
||||
)
|
||||
|
||||
return when (
|
||||
val zecSendValidation =
|
||||
ZecSendExt.new(
|
||||
context = context,
|
||||
destinationString = address,
|
||||
zecString = transaction.amount,
|
||||
// Take memo for a valid non-transparent receiver only
|
||||
memoString = ""
|
||||
)
|
||||
) {
|
||||
is ZecSendExt.ZecSendValidation.Valid ->
|
||||
zecSendValidation.zecSend.copy(
|
||||
destination =
|
||||
when (recipientAddressState.type) {
|
||||
is AddressType.Invalid ->
|
||||
WalletAddress.Unified.new(recipientAddressState.address)
|
||||
|
||||
AddressType.Shielded ->
|
||||
WalletAddress.Unified.new(recipientAddressState.address)
|
||||
|
||||
AddressType.Tex ->
|
||||
WalletAddress.Tex.new(recipientAddressState.address)
|
||||
|
||||
AddressType.Transparent ->
|
||||
WalletAddress.Transparent.new(recipientAddressState.address)
|
||||
|
||||
AddressType.Unified ->
|
||||
WalletAddress.Unified.new(recipientAddressState.address)
|
||||
|
||||
null -> WalletAddress.Unified.new(recipientAddressState.address)
|
||||
}
|
||||
)
|
||||
|
||||
is ZecSendExt.ZecSendValidation.Invalid -> {
|
||||
// We do not expect this validation to fail, so logging is enough here
|
||||
// An error popup could be reasonable here as well
|
||||
Twig.warn { "Send failed with: ${zecSendValidation.validationErrors}" }
|
||||
|
||||
throw RuntimeException("Validation failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -596,7 +596,7 @@ private fun SeedGridWithText(
|
|||
keyboardOptions =
|
||||
KeyboardOptions(
|
||||
KeyboardCapitalization.None,
|
||||
autoCorrect = false,
|
||||
autoCorrectEnabled = false,
|
||||
imeAction = ImeAction.Done,
|
||||
keyboardType = KeyboardType.Password
|
||||
),
|
||||
|
@ -809,7 +809,7 @@ private fun RestoreBirthdayMainContent(
|
|||
keyboardOptions =
|
||||
KeyboardOptions(
|
||||
KeyboardCapitalization.None,
|
||||
autoCorrect = false,
|
||||
autoCorrectEnabled = false,
|
||||
imeAction = ImeAction.Done,
|
||||
keyboardType = KeyboardType.Number
|
||||
),
|
||||
|
|
|
@ -62,7 +62,6 @@ import androidx.compose.ui.layout.onSizeChanged
|
|||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalInspectionMode
|
||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
@ -74,6 +73,7 @@ import androidx.compose.ui.viewinterop.AndroidView
|
|||
import androidx.constraintlayout.compose.ConstraintLayout
|
||||
import androidx.constraintlayout.compose.Dimension
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||
import co.electriccoin.zcash.spackle.Twig
|
||||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.common.model.TopAppBarSubTitleState
|
||||
|
|
|
@ -23,9 +23,9 @@ import androidx.compose.foundation.rememberScrollState
|
|||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.ripple.rememberRipple
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.ripple
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
|
@ -609,7 +609,7 @@ fun SendFormAddressTextField(
|
|||
Modifier.clickable(
|
||||
onClick = sendAddressBookState.onButtonClick,
|
||||
role = Role.Button,
|
||||
indication = rememberRipple(radius = 4.dp),
|
||||
indication = ripple(radius = 4.dp),
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
),
|
||||
painter = painterResource(sendAddressBookState.mode.icon),
|
||||
|
@ -623,7 +623,7 @@ fun SendFormAddressTextField(
|
|||
Modifier.clickable(
|
||||
onClick = onQrScannerOpen,
|
||||
role = Role.Button,
|
||||
indication = rememberRipple(radius = 4.dp),
|
||||
indication = ripple(radius = 4.dp),
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
),
|
||||
painter = painterResource(R.drawable.qr_code_icon),
|
||||
|
|
|
@ -11,6 +11,7 @@ import co.electriccoin.zcash.ui.NavigationTargets.INTEGRATIONS
|
|||
import co.electriccoin.zcash.ui.NavigationTargets.SUPPORT
|
||||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.common.provider.GetVersionInfoProvider
|
||||
import co.electriccoin.zcash.ui.common.usecase.IsFlexaAvailableUseCase
|
||||
import co.electriccoin.zcash.ui.common.usecase.ObserveConfigurationUseCase
|
||||
import co.electriccoin.zcash.ui.common.usecase.RescanBlockchainUseCase
|
||||
import co.electriccoin.zcash.ui.configuration.ConfigurationEntries
|
||||
|
@ -22,6 +23,7 @@ import co.electriccoin.zcash.ui.screen.settings.model.SettingsState
|
|||
import co.electriccoin.zcash.ui.screen.settings.model.SettingsTroubleshootingState
|
||||
import co.electriccoin.zcash.ui.screen.settings.model.TroubleshootingItemState
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
@ -38,7 +40,8 @@ class SettingsViewModel(
|
|||
observeConfiguration: ObserveConfigurationUseCase,
|
||||
private val standardPreferenceProvider: StandardPreferenceProvider,
|
||||
private val getVersionInfo: GetVersionInfoProvider,
|
||||
private val rescanBlockchain: RescanBlockchainUseCase
|
||||
private val rescanBlockchain: RescanBlockchainUseCase,
|
||||
private val isFlexaAvailable: IsFlexaAvailableUseCase
|
||||
) : ViewModel() {
|
||||
private val versionInfo by lazy { getVersionInfo() }
|
||||
|
||||
|
@ -112,7 +115,11 @@ class SettingsViewModel(
|
|||
text = stringRes(R.string.settings_integrations),
|
||||
icon = R.drawable.ic_settings_integrations,
|
||||
onClick = ::onIntegrationsClick,
|
||||
titleIcons = persistentListOf(R.drawable.ic_integrations_coinbase)
|
||||
titleIcons =
|
||||
listOfNotNull(
|
||||
R.drawable.ic_integrations_coinbase,
|
||||
R.drawable.ic_integrations_flexa.takeIf { isFlexaAvailable() }
|
||||
).toImmutableList()
|
||||
),
|
||||
ZashiSettingsListItemState(
|
||||
text = stringRes(R.string.settings_advanced_settings),
|
||||
|
|
|
@ -5,4 +5,9 @@
|
|||
<item name="windowSplashScreenAnimatedIcon">@drawable/no_icon_splash_logo</item>
|
||||
<item name="postSplashScreenTheme">@android:style/Theme.Material.NoActionBar</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.App.Transparent" parent="Theme.AppCompat.Light.NoActionBar">
|
||||
<item name="android:windowIsTranslucent">true</item>
|
||||
<item name="android:windowBackground">@android:color/transparent</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 5.9 KiB |
Binary file not shown.
After Width: | Height: | Size: 2.8 KiB |
|
@ -2,6 +2,9 @@
|
|||
<string name="integrations_title">Integrations</string>
|
||||
<string name="integrations_coinbase">Buy <xliff:g id="currency" example="ZEC">%1$s</xliff:g> with Coinbase</string>
|
||||
<string name="integrations_coinbase_subtitle">A hassle-free way to buy <xliff:g id="currency" example="ZEC">%1$s</xliff:g> and get it directly into your Zashi wallet.</string>
|
||||
<string name="integrations_version">Version %s</string>
|
||||
<string name="integrations_flexa">Pay with Flexa</string>
|
||||
<string name="integrations_flexa_subtitle">Pay with Flexa payment clips and explore a new way of spending Zcash.</string>
|
||||
<string name="integrations_version">Version <xliff:g id="version" example="1.2.1">%1$s</xliff:g></string>
|
||||
<string name="integrations_disabled_info">During the Restore process, it is not possible to use payment integrations.</string>
|
||||
<string name="integrations_biometric_message">Authenticate yourself to pay with Flexa</string>
|
||||
</resources>
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="40"
|
||||
android:viewportHeight="40">
|
||||
<path
|
||||
android:pathData="M0,20C0,8.954 8.954,0 20,0C31.046,0 40,8.954 40,20C40,31.046 31.046,40 20,40C8.954,40 0,31.046 0,20Z"
|
||||
android:fillColor="#454243"/>
|
||||
<path
|
||||
android:pathData="M24.167,26.667H24C22.6,26.667 21.9,26.667 21.365,26.394C20.895,26.154 20.512,25.772 20.272,25.302C20,24.767 20,24.067 20,22.667V17.333C20,15.933 20,15.233 20.272,14.698C20.512,14.228 20.895,13.845 21.365,13.606C21.9,13.333 22.6,13.333 24,13.333H24.167M24.167,26.667C24.167,27.587 24.913,28.333 25.833,28.333C26.754,28.333 27.5,27.587 27.5,26.667C27.5,25.746 26.754,25 25.833,25C24.913,25 24.167,25.746 24.167,26.667ZM24.167,13.333C24.167,14.254 24.913,15 25.833,15C26.754,15 27.5,14.254 27.5,13.333C27.5,12.413 26.754,11.667 25.833,11.667C24.913,11.667 24.167,12.413 24.167,13.333ZM15.833,20L24.167,20M15.833,20C15.833,20.92 15.087,21.667 14.167,21.667C13.246,21.667 12.5,20.92 12.5,20C12.5,19.079 13.246,18.333 14.167,18.333C15.087,18.333 15.833,19.079 15.833,20ZM24.167,20C24.167,20.92 24.913,21.667 25.833,21.667C26.754,21.667 27.5,20.92 27.5,20C27.5,19.079 26.754,18.333 25.833,18.333C24.913,18.333 24.167,19.079 24.167,20Z"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="1.66667"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#E8E8E8"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
Loading…
Reference in New Issue