diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 8cb2c15951..0000000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -# Default ignored files -/shelf/ \ No newline at end of file diff --git a/.idea/dictionaries/.xml b/.idea/dictionaries/.xml deleted file mode 100644 index fc9ea1f34e..0000000000 --- a/.idea/dictionaries/.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - blockchains - - - \ No newline at end of file diff --git a/.idea/dictionaries/romanpotapov.xml b/.idea/dictionaries/romanpotapov.xml new file mode 100644 index 0000000000..9973a9778a --- /dev/null +++ b/.idea/dictionaries/romanpotapov.xml @@ -0,0 +1,9 @@ + + + + blockchain + passcode + tangem + + + \ No newline at end of file diff --git a/.idea/dictionaries/tangem.xml b/.idea/dictionaries/tangem.xml deleted file mode 100644 index e77bbeb7ed..0000000000 --- a/.idea/dictionaries/tangem.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - binance - blockchain - cardano - ducatus - ethereum - litecoin - matic - tangem - testnet - tezos - - - \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml deleted file mode 100644 index 15a15b218a..0000000000 --- a/.idea/encodings.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index cd2f3a654d..7bfef59df1 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,45 +1,5 @@ - - - - - - - - - diff --git a/app/build.gradle b/app/build.gradle index e80024ee8a..40a61be987 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -3,23 +3,15 @@ apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-kapt' apply plugin: 'com.google.firebase.crashlytics' -apply plugin: 'com.google.firebase.firebase-perf' +//apply plugin: 'com.google.firebase.firebase-perf' apply plugin: 'com.google.gms.google-services' -static def generateVersionName() { - def gitCommitsCount = "git rev-list HEAD --count".execute().text.trim() -// def gitCurrentBranch = "git rev-parse --abbrev-ref HEAD".execute().text.trim() - return gitCommitsCount.toInteger() + 831// + "\\n" + gitCurrentBranch -} - -Properties properties = new Properties() -properties.load(project.rootProject.file('local.properties').newDataInputStream()) - android { - compileSdkVersion 28 + compileSdkVersion 29 defaultConfig { + applicationId "com.tangem.wallet" minSdkVersion 21 - targetSdkVersion 28 + targetSdkVersion 29 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } androidExtensions { @@ -44,26 +36,19 @@ android { applicationIdSuffix ".debug" } } - flavorDimensions "" - productFlavors { - tangemAccess { - versionCode project.hasProperty('versionCode') ? project.property('versionCode') as int : 116 - versionName project.hasProperty('versionName') ? project.property('versionName') : "0.1.0.-SNAPSHOT" - applicationId "com.tangem.wallet" - signingConfig signingConfigs.debug - } + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8.toString() } + compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 + coreLibraryDesugaringEnabled true } packagingOptions { - pickFirst('META-INF/proguard/okhttp3.pro') -// for one.block:eosiojava:0.1.0 exclude 'lib/x86_64/darwin/libscrypt.dylib' exclude 'lib/x86_64/freebsd/libscrypt.so' exclude 'lib/x86_64/linux/libscrypt.so' - exclude 'org.slf4j:slf4j-jdk14:1.7.25' } buildToolsVersion '29.0.3' } @@ -71,100 +56,43 @@ android { repositories { maven { url "https://jitpack.io" } } -configurations { - all*.exclude group: 'com.google.guava', module: 'listenablefuture' - implementation.exclude module:'protobuf-lite' -} dependencies { - implementation project(':tangem-card-old') - implementation project(':tangem-sdk-old') - implementation project(':server-android') + implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin" + implementation 'androidx.core:core-ktx:1.3.1' + implementation 'androidx.appcompat:appcompat:1.2.0' + implementation 'androidx.constraintlayout:constraintlayout:2.0.1' + implementation 'com.google.android.material:material:1.2.1' + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.10' - implementation 'androidx.navigation:navigation-fragment:2.2.2' - implementation 'androidx.navigation:navigation-ui-ktx:2.2.2' - implementation 'androidx.legacy:legacy-support-v4:1.0.0' - implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0' - implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0' - implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' + implementation 'com.tangem:blockchain:1.70.0' + implementation 'com.tangem:core:1.63.0' + implementation 'com.tangem:sdk:1.63.0' + + //lifecycle implementation "androidx.lifecycle:lifecycle-runtime:2.2.0" implementation "androidx.lifecycle:lifecycle-common-java8:2.2.0" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0" + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0" - implementation 'androidx.appcompat:appcompat:1.1.0' - implementation 'androidx.fragment:fragment:1.2.4' - implementation 'androidx.constraintlayout:constraintlayout:1.1.3' - implementation "androidx.preference:preference:1.1.1" - implementation 'androidx.biometric:biometric:1.0.1' - implementation 'androidx.core:core-ktx:1.3.0' - implementation 'com.google.android.material:material:1.1.0' + implementation "org.rekotlin:rekotlin:1.0.4" - implementation 'com.google.dagger:dagger:2.24' - kapt 'com.google.dagger:dagger-compiler:2.21' - annotationProcessor 'com.google.dagger:dagger-compiler:2.21' - implementation 'com.google.zxing:core:3.3.3' // Do not update to 3.4.0, it requires minSdk 24 - implementation 'com.google.code.gson:gson:2.8.5' - implementation 'com.madgag.spongycastle:core:1.56.0.0' - implementation 'com.madgag.spongycastle:prov:1.56.0.0' - implementation 'com.scottyab:rootbeer-lib:0.0.7' - implementation 'com.squareup.retrofit2:converter-gson:2.6.0' - implementation 'com.squareup.retrofit2:retrofit:2.6.0' - implementation 'com.squareup.retrofit2:converter-scalars:2.6.0' - implementation 'com.squareup.retrofit2:converter-jackson:2.6.0' - implementation 'com.squareup.okhttp3:logging-interceptor:3.11.0' - implementation "com.squareup.okhttp3:okhttp-sse:3.11.0" - implementation 'com.skyfishjy.ripplebackground:library:1.0.1' - implementation 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0' - implementation "com.orhanobut:hawk:2.0.1" - implementation 'co.nstant.in:cbor:0.8' - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$versions.kotlin" - implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.3' - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.3" -// implementation 'org.bitcoinj:bitcoinj-parent:0.14.7' //TODO: is this needed? - implementation 'org.bitcoinj:bitcoinj-core:0.15.2' - implementation 'org.greenrobot:eventbus:3.1.1' + // QR + implementation 'com.google.zxing:core:3.3.3' implementation 'me.dm7.barcodescanner:zxing:1.9.8' - implementation 'info.hoang8f:android-segmented:1.0.6' - implementation 'io.reactivex.rxjava2:rxjava:2.2.0' - implementation 'io.reactivex.rxjava2:rxandroid:2.0.2' - implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0' -// implementation 'com.github.stellar:java-stellar-sdk:0.4.1' - implementation 'net.i2p.crypto:eddsa:0.3.0' - implementation 'com.google.firebase:firebase-analytics:17.3.0' - implementation 'com.google.firebase:firebase-crashlytics:17.0.0-beta04' - implementation 'com.google.firebase:firebase-perf:19.0.6' + // Camera + implementation 'com.otaliastudios:cameraview:2.6.3' - //dependencies for XRP - implementation files('libs/ripple-core-0.0.1.jar') - //4 dependencies for ripple-core TODO: move to module? - implementation 'net.i2p.crypto:eddsa:0.3.0' - implementation 'org.bouncycastle:bcprov-jdk15on:1.61' - //noinspection DuplicatePlatformClasses - implementation 'org.json:json:20180813' - implementation 'com.fasterxml.jackson.core:jackson-databind:2.9.8' + // Image Loading - Picasso + implementation 'com.squareup.picasso:picasso:2.71828' - //dependencies for binance - implementation 'com.google.protobuf:protobuf-java:3.11.4' - implementation 'io.grpc:grpc-protobuf:1.17.1' - implementation 'io.grpc:grpc-stub:1.17.1' - implementation 'commons-codec:commons-codec:1.10' - implementation 'org.apache.commons:commons-lang3:3.6' - implementation 'com.fasterxml.jackson.datatype:jackson-datatype-joda:2.9.8' - implementation 'joda-time:joda-time:2.10.1' - testImplementation 'junit:junit:4.12' + // Firebase + implementation 'com.google.firebase:firebase-crashlytics:17.2.2' - //dependencies for EOS - implementation 'io.jafka:jeos:0.9.15' -// implementation 'one.block:eosiojava:0.1.0' - implementation 'org.bouncycastle:bcpkix-jdk15on:1.61' + testImplementation 'junit:junit:4.13' + testImplementation "com.google.truth:truth:1.0.1" + androidTestImplementation 'androidx.test.ext:junit:1.1.2' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' - implementation 'io.fotoapparat:fotoapparat:2.6.1' - implementation 'com.otaliastudios:cameraview:2.6.1' - - //dependencies for flow demo - implementation 'io.grpc:grpc-okhttp:1.28.0' - implementation 'io.grpc:grpc-stub:1.28.0' - - //dependencies for TokenEmvEngine - implementation 'com.github.walleth.kethereum:extensions_kotlin:0.81.4' } \ No newline at end of file diff --git a/app/src/debug/AndroidManifest.xml b/app/src/debug/AndroidManifest.xml index 8cf74b318f..2784e858d0 100644 --- a/app/src/debug/AndroidManifest.xml +++ b/app/src/debug/AndroidManifest.xml @@ -1,18 +1,17 @@ - - + + + + + + diff --git a/app/src/debug/ic_launcher-web.png b/app/src/debug/ic_launcher-web.png deleted file mode 100644 index 6ea2968959..0000000000 Binary files a/app/src/debug/ic_launcher-web.png and /dev/null differ diff --git a/app/src/debug/java/com/tangem/Test.java b/app/src/debug/java/com/tangem/Test.java deleted file mode 100644 index df5872e621..0000000000 --- a/app/src/debug/java/com/tangem/Test.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.tangem; - -public class Test { -} diff --git a/app/src/debug/res/drawable/ic_launcher_background.xml b/app/src/debug/res/drawable/ic_launcher_background.xml deleted file mode 100644 index 2408e30d17..0000000000 --- a/app/src/debug/res/drawable/ic_launcher_background.xml +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml deleted file mode 100644 index c4a603d4cc..0000000000 --- a/app/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/debug/res/mipmap-hdpi/ic_launcher.png b/app/src/debug/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 32c50f2332..0000000000 Binary files a/app/src/debug/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/debug/res/mipmap-hdpi/ic_launcher_foreground.png b/app/src/debug/res/mipmap-hdpi/ic_launcher_foreground.png deleted file mode 100644 index d5b16e9389..0000000000 Binary files a/app/src/debug/res/mipmap-hdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/app/src/debug/res/mipmap-hdpi/ic_launcher_round.png b/app/src/debug/res/mipmap-hdpi/ic_launcher_round.png deleted file mode 100644 index 27632f83cf..0000000000 Binary files a/app/src/debug/res/mipmap-hdpi/ic_launcher_round.png and /dev/null differ diff --git a/app/src/debug/res/mipmap-mdpi/ic_launcher.png b/app/src/debug/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 5b7966cdd4..0000000000 Binary files a/app/src/debug/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/debug/res/mipmap-mdpi/ic_launcher_foreground.png b/app/src/debug/res/mipmap-mdpi/ic_launcher_foreground.png deleted file mode 100644 index 626b524a19..0000000000 Binary files a/app/src/debug/res/mipmap-mdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/app/src/debug/res/mipmap-mdpi/ic_launcher_round.png b/app/src/debug/res/mipmap-mdpi/ic_launcher_round.png deleted file mode 100644 index 2d7e470a28..0000000000 Binary files a/app/src/debug/res/mipmap-mdpi/ic_launcher_round.png and /dev/null differ diff --git a/app/src/debug/res/mipmap-xhdpi/ic_launcher.png b/app/src/debug/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 54a64c94f6..0000000000 Binary files a/app/src/debug/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/debug/res/mipmap-xhdpi/ic_launcher_foreground.png b/app/src/debug/res/mipmap-xhdpi/ic_launcher_foreground.png deleted file mode 100644 index a88c8e24f4..0000000000 Binary files a/app/src/debug/res/mipmap-xhdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/app/src/debug/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/debug/res/mipmap-xhdpi/ic_launcher_round.png deleted file mode 100644 index c87f5f573d..0000000000 Binary files a/app/src/debug/res/mipmap-xhdpi/ic_launcher_round.png and /dev/null differ diff --git a/app/src/debug/res/mipmap-xxhdpi/ic_launcher.png b/app/src/debug/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 9847879c7c..0000000000 Binary files a/app/src/debug/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/debug/res/mipmap-xxhdpi/ic_launcher_foreground.png b/app/src/debug/res/mipmap-xxhdpi/ic_launcher_foreground.png deleted file mode 100644 index 40b595210b..0000000000 Binary files a/app/src/debug/res/mipmap-xxhdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/app/src/debug/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/debug/res/mipmap-xxhdpi/ic_launcher_round.png deleted file mode 100644 index be2d53828c..0000000000 Binary files a/app/src/debug/res/mipmap-xxhdpi/ic_launcher_round.png and /dev/null differ diff --git a/app/src/debug/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/debug/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index dab3199415..0000000000 Binary files a/app/src/debug/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_foreground.png deleted file mode 100644 index 92e1668b94..0000000000 Binary files a/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_round.png deleted file mode 100644 index d4c5bcabcb..0000000000 Binary files a/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_round.png and /dev/null differ diff --git a/app/src/debug/res/values/strings.xml b/app/src/debug/res/values/strings.xml index 1c13a1dbc0..4cc2b19474 100644 --- a/app/src/debug/res/values/strings.xml +++ b/app/src/debug/res/values/strings.xml @@ -1,7 +1,6 @@ - DEBUG - com.tangem.debug.data.LogFileProvider + DEBUG \ No newline at end of file diff --git a/app/src/debug_beta/AndroidManifest.xml b/app/src/debug_beta/AndroidManifest.xml index 8cf74b318f..2784e858d0 100644 --- a/app/src/debug_beta/AndroidManifest.xml +++ b/app/src/debug_beta/AndroidManifest.xml @@ -1,18 +1,17 @@ - - + + + + + + diff --git a/app/src/debug_beta/res/drawable/ic_launcher_background.xml b/app/src/debug_beta/res/drawable/ic_launcher_background.xml deleted file mode 100644 index 2408e30d17..0000000000 --- a/app/src/debug_beta/res/drawable/ic_launcher_background.xml +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/debug_beta/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/debug_beta/res/mipmap-anydpi-v26/ic_launcher.xml deleted file mode 100644 index c4a603d4cc..0000000000 --- a/app/src/debug_beta/res/mipmap-anydpi-v26/ic_launcher.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/debug_beta/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/debug_beta/res/mipmap-anydpi-v26/ic_launcher_round.xml deleted file mode 100644 index c4a603d4cc..0000000000 --- a/app/src/debug_beta/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/debug_beta/res/mipmap-hdpi/ic_launcher.png b/app/src/debug_beta/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 2789587d99..0000000000 Binary files a/app/src/debug_beta/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/debug_beta/res/mipmap-hdpi/ic_launcher_foreground.png b/app/src/debug_beta/res/mipmap-hdpi/ic_launcher_foreground.png deleted file mode 100644 index 33ac538a5c..0000000000 Binary files a/app/src/debug_beta/res/mipmap-hdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/app/src/debug_beta/res/mipmap-hdpi/ic_launcher_round.png b/app/src/debug_beta/res/mipmap-hdpi/ic_launcher_round.png deleted file mode 100644 index db6d60f2c8..0000000000 Binary files a/app/src/debug_beta/res/mipmap-hdpi/ic_launcher_round.png and /dev/null differ diff --git a/app/src/debug_beta/res/mipmap-mdpi/ic_launcher.png b/app/src/debug_beta/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 807a7a3be8..0000000000 Binary files a/app/src/debug_beta/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/debug_beta/res/mipmap-mdpi/ic_launcher_foreground.png b/app/src/debug_beta/res/mipmap-mdpi/ic_launcher_foreground.png deleted file mode 100644 index c91c20c714..0000000000 Binary files a/app/src/debug_beta/res/mipmap-mdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/app/src/debug_beta/res/mipmap-mdpi/ic_launcher_round.png b/app/src/debug_beta/res/mipmap-mdpi/ic_launcher_round.png deleted file mode 100644 index 8c87ad2ac3..0000000000 Binary files a/app/src/debug_beta/res/mipmap-mdpi/ic_launcher_round.png and /dev/null differ diff --git a/app/src/debug_beta/res/mipmap-xhdpi/ic_launcher.png b/app/src/debug_beta/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index d525ea41b9..0000000000 Binary files a/app/src/debug_beta/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/debug_beta/res/mipmap-xhdpi/ic_launcher_foreground.png b/app/src/debug_beta/res/mipmap-xhdpi/ic_launcher_foreground.png deleted file mode 100644 index 578f07c490..0000000000 Binary files a/app/src/debug_beta/res/mipmap-xhdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/app/src/debug_beta/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/debug_beta/res/mipmap-xhdpi/ic_launcher_round.png deleted file mode 100644 index fb770fede5..0000000000 Binary files a/app/src/debug_beta/res/mipmap-xhdpi/ic_launcher_round.png and /dev/null differ diff --git a/app/src/debug_beta/res/mipmap-xxhdpi/ic_launcher.png b/app/src/debug_beta/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 5e640bd233..0000000000 Binary files a/app/src/debug_beta/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/debug_beta/res/mipmap-xxhdpi/ic_launcher_foreground.png b/app/src/debug_beta/res/mipmap-xxhdpi/ic_launcher_foreground.png deleted file mode 100644 index 0489350858..0000000000 Binary files a/app/src/debug_beta/res/mipmap-xxhdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/app/src/debug_beta/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/debug_beta/res/mipmap-xxhdpi/ic_launcher_round.png deleted file mode 100644 index 31e3b01ea5..0000000000 Binary files a/app/src/debug_beta/res/mipmap-xxhdpi/ic_launcher_round.png and /dev/null differ diff --git a/app/src/debug_beta/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/debug_beta/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index f1730013fc..0000000000 Binary files a/app/src/debug_beta/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/debug_beta/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/app/src/debug_beta/res/mipmap-xxxhdpi/ic_launcher_foreground.png deleted file mode 100644 index e1c04f31c9..0000000000 Binary files a/app/src/debug_beta/res/mipmap-xxxhdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/app/src/debug_beta/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/debug_beta/res/mipmap-xxxhdpi/ic_launcher_round.png deleted file mode 100644 index 1847464cbd..0000000000 Binary files a/app/src/debug_beta/res/mipmap-xxxhdpi/ic_launcher_round.png and /dev/null differ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 02a922f1a6..0def014c91 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,7 +7,6 @@ android:name="android.hardware.nfc" android:required="true" /> - @@ -20,7 +19,7 @@ - - - - - - + android:windowSoftInputMode="adjustResize"> @@ -50,6 +43,7 @@ + @@ -71,9 +65,9 @@ + android:pathPrefix="/android.com:pkg" + android:scheme="vnd.android.nfc" /> @@ -84,6 +78,8 @@ android:name="android.nfc.action.TECH_DISCOVERED" android:resource="@xml/nfc_tech_filter" /> + + \ No newline at end of file diff --git a/app/src/main/assets/issuers.json b/app/src/main/assets/issuers.json deleted file mode 100644 index b1863c76ab..0000000000 --- a/app/src/main/assets/issuers.json +++ /dev/null @@ -1,119 +0,0 @@ -[ - { - "id": "TANGEM SDK", - "dataKey": { - "privateKey": "11121314151617184771ED81F2BACF57479E4735EB1405083927372D40DA9E92" - }, - "transactionKey": { - "privateKey": "11121314151617184771ED81F2BACF57479E4735EB1405081918171615141312" - } - }, - { - "id": "TANGEM", - "dataKey": { - "publicKey": "048196AA4B410AC44A3B9CCE18E7BE226AEA070ACC83A9CF67540FAC49AF25129F6A538A28AD6341358E3C4F9963064F7E365372A651D374E5C23CDD37FD099BF2" - }, - "transactionKey": { - "publicKey": "04343D40496CBE1FE8A8C026575C435A29141EA3BC335DA5549AB6C64685A646848036D481CF9A989390A8B034B229D99BD49E6F07D2FF02746EA265EF99380A80" - } - }, - { - "id": "SUPERBLOOM", - "dataKey": { - "publicKey": "04EAB7F2444CFFA2E9CB4AA01CF1CA3694888811BDF0F8E0CDBCCFB5F497F14A5B86655FEF0C7E8F86841B0C14660E09F28DDFB9690F7302DA56F661A6A064564E" - }, - "transactionKey": { - "publicKey": "04D292BFC802FFA70ABB1C094A19F6C6766DD353114C25415871BE4586129648CFD77B22FBC1AF3C612497732A04CF845B41D760AFDD820051AB250798503DB340" - } - }, - { - "id": "Qlear Protocol", - "dataKey": { - "publicKey": "048B02F0D912B74294E296654E7A008946A0F51E8B4F1141BCB302716C4D868BADCB495681BEA670FFB125C62816CEB3B3AA4AB5EE7336F9ACEC18955493C40058" - }, - "transactionKey": { - "publicKey": "04CBC1131BB4BB717C22CCF91C716A8BDACFB09D962A34FBFCE923B6070A40A35CC4E417B2E5ECC19EC78FA372CFF5A1D937D152981E4F0AFFDC1E60F9F0945272" - } - }, - { - "id": "CYCLE[bit]", - "dataKey": { - "publicKey": "04F743D0CFA3D3B7A01F9224ECA40A2F0E09558555D0961417A7B00964ECD0FA3338784C89363A695742AF2B13D73BB30B6E0B4E59CA0A14490ED73DCECE3BCF52" - }, - "transactionKey": { - "publicKey": "04ECA685AFD135EF2AB24D07BED71ED82CC1882C282976616DAC655ED3E5B8E75487C131F5D79A87895ECDE89AC58CD1BB220A0C29DD544D8D9AD5156184456F3F" - } - }, - { - "id": "Whirl", - "dataKey": { - "publicKey": "040B3484CB923DAB12915B7B4FC174543C242A4E49AD9046B2816F03DAFC344E7570CBCC245F5D82FAFDB7E413538238EDE2C196EEC2F9F91911AFC9C37F757810" - }, - "transactionKey": { - "publicKey": "042EC0898ADF5AF4883D2F077E89CE464766E8E47FA03612F5248494C88A923CA2C444EE71D3A2BE67F13FD65C19E114B577F2B12CE856F1AA80DEBB406F325171" - } - }, - { - "id": "D1", - "dataKey": { - "publicKey": "046F1155C3E75C2E0A4B816F53B739BA9DB3FC27C4CDE20E634AF09CA2D88A5199F97AE474B5833E69AD5A43FFB2F62E4E9B7A8BCC271805A5DD19A7AB9C9888D6" - }, - "transactionKey": { - "publicKey": "04AE9E9C50073ABCF3219A96806BE8B95DCA97A12A0DE3516C25959266F5DFD032B75E1CC926DAAC274EE0E638A2BDB717C4B9C6F28808D6BEAB2F4CBF91E2DD52" - } - }, - { - "id": "Coinstruction", - "dataKey": { - "publicKey": "042D6D305E2927FE18762CF2E74B207B3D7239279248450C31C88C4FEBDBC62D07976D10F7838CA52AF5A7ACB052D09EC8C513E7AC204673D2D3D078DBD859DF49" - }, - "transactionKey": { - "publicKey": "045B0561BD9E089E54D730D75F5205A1A3E094A2209D0F11002F2423D63F508BFD161F8BBA8FBAC441AD893287D67141BBEE39E403C43D12A9FE1A25BCF3A144E9" - } - }, - { - "id": "EvenToken", - "dataKey": { - "publicKey": "040FB60B37682AED10AB8B9DB324BBB10D7CEDF9D189332212C36A641CF81314E6CC8C219245DF04813DBB26931C547B6A4D55A10B5FFB163F54643A97213276F8" - }, - "transactionKey": { - "publicKey": "044CEB1A3C2931F55441B39EB06F938542309DDD436DA486DE605187418163B777684C15D10EE5029CDEFB73DBA2BF90173D27CACEBF4CABFD4553C63021ABBEBB" - } - }, - { - "id": "CARDANO", - "dataKey": { - "publicKey": "0445C312089E57A6952F96D74FF75C962E1E2C2DDA5FB4195661B92E2744791350FE6C60E1A1D145C6A34D8BEAD57506A90FE4A149A9AA43CFF3C6CAE0748B771E" - }, - "transactionKey": { - "publicKey": "04969A008E8A61F92EF8D380DB7BB7B8F6F667CA169934371F544F6697D1D2AF97FB87B8D75F550CD782472D46BFCADF354AF17D807C1D2EB82E184C367CACF1FC" - } - }, - { - "id": "RSK", - "dataKey": { - "publicKey": "04997C29B1158D5A5B7EDCF7A7844EA76C630C80CD326C83F2983C2EF0E632B37282D69CEEDB6445B79B26D73073122E22AB0FE1E9A1CA41D991C45057D9DF0F24" - }, - "transactionKey": { - "publicKey": "040E9DDC53387F1006F39451AF11EBD377B98484FC81D24A96A55DEFDDF6F1EE35F07B089EFD05E4E93BDE20372171274ECD8D8805D0CB1FFEF012673F4E330C95" - } - }, - { - "id": "DIGIX", - "dataKey": { - "publicKey": "049533C082D074ACC285E9B7E97F50F0883DF0308871232ED2370F19CA8993F01C5D4ABAF848949C7628D48F60F68DA3E5726A53C394619179D42D7F20D347001E" - }, - "transactionKey": { - "publicKey": "04346D597CBE34A409C4CC77AED4DB2332D18C181DD6EB0A695FEA8672FF790B1C2518DF3A357484BB1EC89BCB1C3F2A962B2781DF094C8EE0F9C45D98BBA92610" - } - }, - { - "id": "Start2Coin", - "dataKey": { - "publicKey": "043D21AFD0E20468F210547079819CB947031F4A31E591A4193E98DEE84AFB5976066CDD43F94820E0B5F82C063603B48C9A84B4C28C3B6DD9E54BB66A1C75036F" - }, - "transactionKey": { - "publicKey": "0442C6413C81FCCF6FA18318ECEA270F7B7242E82863A9208253A85BEC394E5F58E6A12F9FE8A708BD05B73862C8833EB0634FF98E527277F625D4AB139B86AEAC" - } - } -] \ No newline at end of file diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png new file mode 100644 index 0000000000..4c55d07eb3 Binary files /dev/null and b/app/src/main/ic_launcher-playstore.png differ diff --git a/app/src/main/java/com/tangem/App.kt b/app/src/main/java/com/tangem/App.kt deleted file mode 100644 index ba734b7bba..0000000000 --- a/app/src/main/java/com/tangem/App.kt +++ /dev/null @@ -1,106 +0,0 @@ -package com.tangem - -import android.app.Application -import androidx.appcompat.app.AppCompatDelegate -import com.google.gson.Gson -import com.google.gson.reflect.TypeToken -import com.tangem.data.dp.PrefsManager -import com.tangem.data.local.PendingTransactionsStorage -import com.tangem.di.DaggerNetworkComponent -import com.tangem.di.DaggerToastHelperComponent -import com.tangem.di.NetworkComponent -import com.tangem.di.ToastHelperComponent -import com.tangem.server_android.data.LocalStorage -import com.tangem.tangem_card.data.Issuer -import com.tangem.tangem_sdk.android.data.Firmwares -import com.tangem.tangem_sdk.android.data.PINStorage -import com.tangem.util.Analytics -import com.tangem.wallet.BuildConfig -import java.io.InputStreamReader -import java.nio.charset.StandardCharsets - -class App : Application() { - companion object { - @get:Synchronized - var instance: App? = null - private set - - init { - AppCompatDelegate.setCompatVectorFromResourcesEnabled(true) - } - - lateinit var networkComponent: NetworkComponent - lateinit var toastHelperComponent: ToastHelperComponent - - lateinit var firmwaresStorage: Firmwares - lateinit var localStorage: LocalStorage - lateinit var pinStorage: PINStorage - lateinit var pendingTransactionsStorage: PendingTransactionsStorage - } - - override fun onCreate() { - super.onCreate() - // initialize the singleton - instance = this - - networkComponent = DaggerNetworkComponent.create() - toastHelperComponent = buildToastHelperComponent() - - PrefsManager.getInstance().init(this) - - // common init - if (PINStorage.needInit()) - PINStorage.init(applicationContext) - - initIssuers() - - firmwaresStorage = Firmwares(applicationContext) - localStorage = LocalStorage(applicationContext) - pinStorage = PINStorage() - pendingTransactionsStorage = PendingTransactionsStorage(applicationContext) - - if (BuildConfig.DEBUG) { - Runtime.getRuntime().exec("logcat -G 16M") - com.tangem.tangem_card.util.Log.setLogger( - object : com.tangem.tangem_card.util.LoggerInterface { - override fun i(logTag: String, message: String) { - android.util.Log.i(logTag, message) - } - - override fun e(logTag: String, message: String) { - android.util.Log.e(logTag, message) - } - - override fun v(logTag: String, message: String) { - android.util.Log.v(logTag, message) - } - } - ) - } - - Analytics.setFirebaseEnabled(this) - } - - private fun buildToastHelperComponent(): ToastHelperComponent { - return DaggerToastHelperComponent.builder() - .build() - } - - private fun initIssuers() { - try { - applicationContext.assets.open("issuers.json").use { `is` -> - InputStreamReader(`is`, StandardCharsets.UTF_8).use { reader -> - val listType = object : TypeToken>() { - - }.type - - Issuer.fillIssuers(Gson().fromJson(reader, listType)) - } - } - } catch (e: Exception) { - e.printStackTrace() - } - - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/Constant.kt b/app/src/main/java/com/tangem/Constant.kt deleted file mode 100644 index db0b7531de..0000000000 --- a/app/src/main/java/com/tangem/Constant.kt +++ /dev/null @@ -1,102 +0,0 @@ -package com.tangem - -import android.app.Activity - -object Constant { - - const val FLAVOR_TANGEM_ACCESS = "tangemAccess" - const val FLAVOR_TANGEM_CARDANO = "tangemCardano" - - - const val PREF_LAST_WALLET_ADDRESS = "last_wallet_address" - - const val URL_TANGEM = "https://play.google.com/store/apps/details?id=com.tangem.wallet" - - const val EXTRA_BLOCKCHAIN_DATA = "BLOCKCHAIN_DATA" - - const val WALLET_ADDRESS = "Wallet address" - - const val EXTRA_MESSAGE = "message" - - const val EXTRA_MODIFICATION = "modification" - const val EXTRA_MODIFICATION_DELETE = "delete" - const val EXTRA_MODIFICATION_UPDATE = "update" - - const val EXTRA_MODE = "mode" - - const val INTENT_TYPE_TEXT_PLAIN = "text/plain" - - // LoadedWalletFragment, VerifyCardFragment - const val REQUEST_CODE_SEND_TRANSACTION = "REQUEST_CODE_SEND_TRANSACTION" - const val REQUEST_CODE_PURGE = "REQUEST_CODE_PURGE" - const val REQUEST_CODE_REQUEST_PIN2_FOR_PURGE = "REQUEST_CODE_REQUEST_PIN2_FOR_PURGE" - const val REQUEST_CODE_VERIFY_CARD = "REQUEST_CODE_VERIFY_CARD" - const val REQUEST_CODE_ENTER_NEW_PIN = "REQUEST_CODE_ENTER_NEW_PIN" - const val REQUEST_CODE_ENTER_NEW_PIN2 = "REQUEST_CODE_ENTER_NEW_PIN2" - const val REQUEST_CODE_REQUEST_PIN2_FOR_SWAP_PIN = "REQUEST_CODE_REQUEST_PIN2_FOR_SWAP_PIN" - const val REQUEST_CODE_SWAP_PIN = "REQUEST_CODE_SWAP_PIN" - const val REQUEST_CODE_RECEIVE_TRANSACTION = "REQUEST_CODE_RECEIVE_TRANSACTION" - - // MainFragment - const val REQUEST_CODE_SHOW_CARD_ACTIVITY = "REQUEST_CODE_SHOW_CARD_ACTIVITY" - const val REQUEST_CODE_ENTER_PIN_ACTIVITY = "REQUEST_CODE_ENTER_PIN_ACTIVITY" - const val REQUEST_CODE_SEND_EMAIL = "REQUEST_CODE_SEND_EMAIL" - const val REQUEST_CODE_REQUEST_CAMERA_PERMISSIONS = 3 - - const val EXTRA_LAST_DISCOVERED_TAG = "extra_last_tag" - const val EXTRA_PIN2 = "PIN2" - - // LogoFragment - const val EXTRA_AUTO_HIDE = "extra_auto_hide" - const val MILLIS_AUTO_HIDE = 1000 - - // PinRequestFragment - - const val KEY_ALIAS = "pinKey" - const val KEYSTORE = "AndroidKeyStore" - - // QrScanFragment - const val EXTRA_QR_CODE = "QRCode" - - // PinSwapFragment - const val EXTRA_CONFIRM_PIN = "confirmPIN" - const val EXTRA_CONFIRM_PIN_2 = "confirmPIN2" - const val EXTRA_NEW_PIN = "newPIN" - const val EXTRA_NEW_PIN_2 = "newPIN2" - - // CreateNewWalletFragment - const val RESULT_INVALID_PIN = Activity.RESULT_FIRST_USER - - // EmptyWalletFragment - const val REQUEST_CODE_CREATE_NEW_WALLET_ACTIVITY = "REQUEST_CODE_CREATE_NEW_WALLET_ACTIVITY" - const val REQUEST_CODE_REQUEST_PIN2 = "REQUEST_CODE_REQUEST_PIN2" - - // ConfirmTransactionFragment - const val REQUEST_CODE_SIGN_TRANSACTION = "REQUEST_CODE_SIGN_TRANSACTION" - const val REQUEST_CODE_REQUEST_PIN2_ = "REQUEST_CODE_REQUEST_PIN2_" - - // SendTransactionFragment - const val EXTRA_TX: String = "TX" - - // SignTransactionFragment - const val EXTRA_AMOUNT = "Amount" - const val EXTRA_AMOUNT_CURRENCY = "AmountCurrency" - const val EXTRA_FEE = "Fee" - const val EXTRA_FEE_CURRENCY = "FeeCurrency" - const val EXTRA_FEE_INCLUDED = "FeeIncluded" - const val EXTRA_TARGET_ADDRESS = "TargetAddress" - const val REQUEST_CODE_SEND_TRANSACTION_ = "REQUEST_CODE_SEND_TRANSACTION_" - const val RESULT_INVALID_PIN_ = Activity.RESULT_FIRST_USER - - // PrepareTransactionFragment - const val REQUEST_CODE_SCAN_QR = "REQUEST_CODE_SCAN_QR" - const val REQUEST_CODE_SEND_TRANSACTION__ = "REQUEST_CODE_SEND_TRANSACTION__" - - // PrepareCryptonitOtherApiWithdrawalFragment - const val REQUEST_CODE_SCAN_QR_KEY = "REQUEST_CODE_SCAN_QR_KEY" - const val REQUEST_CODE_SCAN_QR_SECRET = "REQUEST_CODE_SCAN_QR_SECRET" - const val REQUEST_CODE_SCAN_QR_USER_ID = "REQUEST_CODE_SCAN_QR_USER_ID" - - const val TERMINAL_PRIVATE_KEY = "terminalPrivateKey" - const val TERMINAL_PUBLIC_KEY = "terminalPublicKey" -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/data/Blockchain.java b/app/src/main/java/com/tangem/data/Blockchain.java deleted file mode 100644 index fc729c6dba..0000000000 --- a/app/src/main/java/com/tangem/data/Blockchain.java +++ /dev/null @@ -1,129 +0,0 @@ -package com.tangem.data; - -import com.tangem.tangem_sdk.R; - -/** - * Created by dvol on 06.08.2017. - */ -public enum Blockchain { - Unknown("", "", 1.0, R.drawable.ic_logo_unknown, "Unknown"), - Bitcoin("BTC", "BTC", 100000000.0, R.drawable.ic_logo_bitcoin, "Bitcoin"), - BitcoinTestNet("BTC/test", "BTC", 100000000.0, R.drawable.ic_logo_bitcoin_testnet, "Bitcoin Testnet"), - BitcoinDual("BTC/dual", "BTC", 100000000.0, R.drawable.ic_logo_bitcoin, "Bitcoin"), - Ethereum("ETH", "ETH", 1.0, R.drawable.ic_logo_ethereum, "Ethereum"), - EthereumId("ETH/ID", "ETH", 1.0, R.drawable.ic_logo_ethereum, "Ethereum"), - EthereumTestNet("ETH/test", "ETH", 1.0, R.drawable.ic_logo_ethereum_testnet, "Ethereum Testnet"), - Token("Token", "ETH", 1.0, R.drawable.ic_logo_ethereum, "Ethereum"), - NftToken("NftToken", "", 1.0, R.drawable.ic_logo_ethereum, "Ethereum"), - BitcoinCash("BCH", "BCH", 100000000.0, R.drawable.ic_logo_bitcoin_cash, "Bitcoin Cash"), - Litecoin("LTC", "LTC", 100000000.0, R.drawable.ic_logo_litecoin, "Litecoin"), - Rootstock("RSK", "RBTC", 1.0, R.drawable.tangem2, "RSK"), - RootstockToken("RskToken", "RBTC", 1.0, R.drawable.tangem2, "RSK"), - Cardano("CARDANO", "ADA", 1000000.0, R.drawable.tangem2, "Cardano"), - CardanoShelley("CARDANO-S", "ADA", 1000000.0, R.drawable.tangem2, "Cardano"), - Ripple("XRP", "XRP", 1000000.0, R.drawable.ic_logo_xrp, "XRP Ledger"), - Binance("BINANCE", "BNB", 100000000.0, R.drawable.ic_logo_binance, "Binance"), - BinanceTestNet("BINANCE/test", "BNB", 100000000.0, R.drawable.ic_logo_binance, "Binance Testnet"), - BinanceAsset("BinanceAsset", "BNB", 100000000.0, R.drawable.ic_logo_binance, "Binance"), - Matic("MATIC", "MTX", 1.0, R.drawable.tangem2, "Matic"), - MaticTestNet("MATIC/test", "MTX", 1.0, R.drawable.tangem2, "Matic Testnet"), - Stellar("XLM", "XLM", 10000000.0, R.drawable.ic_logo_stellar, "Stellar"), - StellarTestNet("XLM/test", "XLM", 10000000.0, R.drawable.ic_logo_stellar, "Stellar Testnet"), - StellarAsset("Asset", "XLM", 10000000.0, R.drawable.ic_logo_stellar, "Stellar"), - StellarTag("XLM-Tag", "XLM", 1000000.0, R.drawable.ic_logo_stellar, "Stellar"), - Eos("EOS", "EOS", 10000.0, R.drawable.tangem2, "EOS"), - Ducatus("DUC", "DUC", 100000000.0, R.drawable.tangem2, "Ducatus"), - Tezos("TEZOS", "XTZ", 10000000.0, R.drawable.ic_logo_tezos, "Tezos"), - FlowDemo("FLOW/demo", "", 1.0, R.drawable.tangem2, "Flow demo"), - TokenEmv("TTW", "ETH", 1.0, R.drawable.ic_logo_ethereum, "Ethereum"); - - Blockchain(String ID, String currency, double multiplier, int imageResource, String officialName) { - mID = ID; - mCurrency = currency; -// mMultiplier = multiplier; - mImageResource = imageResource; - mOfficialName = officialName; - } - - private String mID, mOfficialName; - //private double mMultiplier; - private String mCurrency; - private int mImageResource; - - public String getID() { - return mID; - } - - public String getOfficialName() { - return mOfficialName; - } - -// public double getMultiplier() { -// return mMultiplier; -// } - - public String getCurrency() { - return mCurrency; - } - - public static Blockchain fromId(String id) { - for (Blockchain blockchain : values()) { - if (blockchain.getID().equals(id)) return blockchain; - } - return Blockchain.Unknown; - } - - public static Blockchain fromCurrency(String currency) { - for (Blockchain blockchain : values()) { - if (blockchain.getCurrency() == currency) return blockchain; - } - return null; - } - - public static String[] getCurrencies() { - String[] result = new String[values().length - 1]; - for (int i = 0; i < result.length - 1; i++) { - result[i] = values()[i + 1].getCurrency(); - } - return result; - } - - private int getImageResource() { - return mImageResource; - } - - public int getLogoImageResource(String symbolName) { - switch (this) { - case Token: - if (symbolName.equals("SEED")) - return R.drawable.ic_logo_seed; - break; - case RootstockToken: - if (symbolName.equals("RIF")) - return R.drawable.ic_logo_rif; - } - - return getImageResource(); - } - - public String getUriScheme() { - String scheme = null; - switch (this) { - case Bitcoin: - case BitcoinDual: - scheme = "bitcoin"; - break; - case Ethereum: - case Token: - case TokenEmv: - scheme = "ethereum"; - break; - case Litecoin: - scheme = "litecoin"; - break; - case Ripple: - scheme = "ripple"; - } - return scheme; - } -} diff --git a/app/src/main/java/com/tangem/data/LogFileProvider.java b/app/src/main/java/com/tangem/data/LogFileProvider.java deleted file mode 100644 index b5461ae90d..0000000000 --- a/app/src/main/java/com/tangem/data/LogFileProvider.java +++ /dev/null @@ -1,103 +0,0 @@ -package com.tangem.data; - -import android.content.ContentProvider; -import android.content.ContentValues; -import android.content.UriMatcher; -import android.database.Cursor; -import android.net.Uri; -import android.os.ParcelFileDescriptor; -import android.util.Log; - -import com.tangem.wallet.R; - -import java.io.File; -import java.io.FileNotFoundException; -import java.util.Objects; - -/** - * Created by dvol on 15.02.2018. - */ - -public class LogFileProvider extends ContentProvider { - - private static final String TAG = LogFileProvider.class.getSimpleName() + "-oF"; - - // UriMatcher used to match against incoming requests - private UriMatcher uriMatcher; - - @Override - public boolean onCreate() { - uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); - - // Add a URI to the matcher which will match against the form - // 'content://it.my.app.LogFileProvider/*' - // and return 1 in the case that the incoming Uri matches this pattern - - uriMatcher.addURI(Objects.requireNonNull(getContext()).getString(R.string.log_file_provider_authorities), "*", 1); - - return true; - } - - @Override - public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { - - Log.v(TAG, "Called with uri: '" + uri + "'." + uri.getLastPathSegment()); - - // check incoming Uri against the matcher - switch (uriMatcher.match(uri)) { - - // If it returns 1 - then it matches the Uri defined in onCreate - case 1: - - // The desired file name is specified by the last segment of the - // path - // E.g. - // 'content://it.my.app.LogFileProvider/Test1.txt' - // Take this and build the path to the file - String fileLocation = getContext().getCacheDir() + File.separator - + uri.getLastPathSegment(); - - // Create & return a ParcelFileDescriptor pointing to the file - // Note: I don't care what mode they ask for - they're only getting - // read only - ParcelFileDescriptor pfd = ParcelFileDescriptor.open(new File(fileLocation), ParcelFileDescriptor.MODE_READ_ONLY); - return pfd; - - // Otherwise unrecognised Uri - default: - Log.v(TAG, "Unsupported uri: '" + uri + "'."); - throw new FileNotFoundException("Unsupported uri: " + uri.toString()); - } - } - - // ////////////////////////////////////////////////////////////// - // Not supported / used / required for this example - // ////////////////////////////////////////////////////////////// - - @Override - public int update(Uri uri, ContentValues contentvalues, String s, - String[] as) { - return 0; - } - - @Override - public int delete(Uri uri, String s, String[] as) { - return 0; - } - - @Override - public Uri insert(Uri uri, ContentValues contentvalues) { - return null; - } - - @Override - public String getType(Uri uri) { - return null; - } - - @Override - public Cursor query(Uri uri, String[] projection, String s, String[] as1, - String s1) { - return null; - } -} diff --git a/app/src/main/java/com/tangem/data/Logger.java b/app/src/main/java/com/tangem/data/Logger.java deleted file mode 100644 index 8cf42d6737..0000000000 --- a/app/src/main/java/com/tangem/data/Logger.java +++ /dev/null @@ -1,285 +0,0 @@ -package com.tangem.data; - -import android.content.Context; -import android.util.Log; - -import com.tangem.tangem_card.util.Util; - -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileWriter; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.Date; - -public class Logger { - - public static File collectLogs(Context context) { - File f = new File(context.getCacheDir().getAbsolutePath() + "/Wallet_" + Util.formatDateTimeToFileName(new Date()) + ".log"); - try { - if (f.createNewFile()) { - f.setReadable(true); - FileWriter fileWriter = new FileWriter(f, true); - Process process = Runtime.getRuntime().exec("logcat -d -v time"); - try { - InputStream is = process.getInputStream(); - InputStreamReader isr = new InputStreamReader(is); - BufferedReader bufferedReader = new BufferedReader(isr); - BufferedWriter buf = new BufferedWriter(fileWriter); - buf.append("Tangem Wallet logs"); - buf.newLine(); - - int i = 0; - String line; - while ((line = bufferedReader.readLine()) != null) { - buf.append(line); - buf.newLine(); - i++; - } - Log.e("Logger", String.format("%d log lines collected", i)); - buf.newLine(); - buf.flush(); - buf.close(); - - } finally { - process.destroy(); - } - - return f; - } - } catch (Exception e) { - e.printStackTrace(); - } - - return null; - } - - -} - -//public class Logger { -// -// public static File[] getLastLogFiles() { -// File path = new File(Environment.getExternalStorageDirectory(), "Tangem/logs"); -// if (!path.exists()) { -// return null; -// } -// File[] files = path.listFiles(); -// Arrays.sort(files, new Comparator() { -// @Override -// public int compare(File o1, File o2) { -// if (o1.lastModified() < o2.lastModified()) { -// return -1; -// } else if (o1.lastModified() > o2.lastModified()) { -// return 1; -// } -// return 0; -// } -// }); -// if (files.length < 5) return files; -// return Arrays.copyOfRange(files, files.length - 5, files.length); -// } -// -// private static File logFile = null; -// -// private static void initLogFile(Context context) { -// try { -// File path = new File(Environment.getExternalStorageDirectory(), "Tangem/logs"); -// if (!path.exists()) { -// path.mkdirs(); -// MediaScannerConnection.scanFile(context, new String[]{path.getParentFile().toString()}, null, null); -// } -// logFile = new File(path, String.format("wallet_%s.log", Util.formatDateTimeToFileName(new Date()))); -// logFile.createNewFile(); -// logFile.setReadable(true); -// -// // initiate media scan and put the new things into the path array to -// // make the scanner aware of the location and the files you want to see -// MediaScannerConnection.scanFile(context, new String[]{logFile.getAbsolutePath()}, null, null); -// } catch (IOException e) { -// e.printStackTrace(); -// } -// -// } -// -// public static boolean isCurrent(File f) { -// if (f == null || logFile == null) return false; -// return f.getAbsolutePath().equals(logFile.getAbsolutePath()); -// } -// -// -// private static class LogCatThread extends Thread { -// private boolean Terminated; -// -// private static final Object oSync = new Object(); -// -// public void Terminate() { -// Terminated = true; -// synchronized (oSync) { -// oSync.notifyAll(); -// } -// try { -// join(1000); -// } catch (InterruptedException e) { -// e.printStackTrace(); -// interrupt(); -// } -// } -// -// public void collectLogs(Writer out) { -// try { -// Process process = Runtime.getRuntime().exec("logcat -d -b main -v time"); -// try { -// InputStream is = process.getInputStream(); -// InputStreamReader isr = new InputStreamReader(is); -// BufferedReader bufferedReader = new BufferedReader(isr); -// try { -// -// try { -// //BufferedWriter for performance, true to set append to file flag -// BufferedWriter buf = new BufferedWriter(out); -// -// while (!Terminated && isr.ready()) { -// String line = bufferedReader.readLine(); -// buf.append(line); -// buf.newLine(); -// } -// //Log.i("Logger",String.format("%d lines added",i)); -// buf.newLine(); -// buf.close(); -// } catch (IOException e) { -// e.printStackTrace(); -// } -// -// -// } catch (Exception e) { -// e.printStackTrace(); -// } -// -// } finally { -// process.destroy(); -// } -// } catch (IOException e) { -// e.printStackTrace(); -// } -// -// } -// -// @Override -// public void run() { -// Process process = null; -// try { -// if (logFile == null) return; -// process = Runtime.getRuntime().exec("logcat -b main -v time"); -// try { -// InputStream is = process.getInputStream(); -// InputStreamReader isr = new InputStreamReader(is); -// BufferedReader bufferedReader = new BufferedReader(isr); -// while (!Terminated) { -// try { -// synchronized (oSync) { -// oSync.wait(1000); -// } -// if (!logFile.exists()) { -// try { -// logFile.createNewFile(); -// } catch (IOException e) { -// e.printStackTrace(); -// } -// } -// try { -// //BufferedWriter for performance, true to set append to file flag -// BufferedWriter buf = new BufferedWriter(new FileWriter(logFile, true)); -// -// while (isr.ready()) { -// String line = bufferedReader.readLine(); -// buf.append(line); -// buf.newLine(); -// } -// //Log.i("Logger",String.format("%d lines added",i)); -// buf.newLine(); -// buf.close(); -// } catch (IOException e) { -// e.printStackTrace(); -// } -// -// -// } catch (Exception e) { -// e.printStackTrace(); -// } -// } -// } finally { -// process.destroy(); -// } -// } catch (IOException e) { -// e.printStackTrace(); -// } -// } -// } -// -// static LogCatThread t = new LogCatThread(); -// -// public static void StartSaveToFile(Activity activity) { -// try { -// if (logFile != null) return; -// -// verifyStoragePermissions(activity); -// initLogFile(activity.getApplicationContext()); -// t.init(); -// } catch (Exception e) { -// e.printStackTrace(); -// } -// } -// -// public static void StopSaveToFile(Context context) { -// final Object oSync = new Object(); -// if (t.isAlive()) { -// t.Terminate(); -// } -// if (logFile != null) { -// MediaScannerConnection.scanFile(context, new String[]{logFile.getAbsolutePath().toString()}, null, new MediaScannerConnection.OnScanCompletedListener() { -// @Override -// public void onScanCompleted(String path, Uri uri) { -//// synchronized (oSync) { -//// oSync.notifyAll(); -//// } -// } -// }); -//// try { -//// synchronized (oSync) { -//// oSync.wait(10000); -// logFile = null; -//// } -//// } catch (InterruptedException e) { -//// e.printStackTrace(); -//// } -// } -// -// } -// -// // Storage Permissions -// private static final int REQUEST_EXTERNAL_STORAGE = 1; -// private static String[] PERMISSIONS_STORAGE = { -// Manifest.permission.READ_EXTERNAL_STORAGE, -// Manifest.permission.WRITE_EXTERNAL_STORAGE -// }; -// -// //Checks if the app has permission to write to device storage -// //If the app does not has permission then the user will be prompted to grant permissions -// public static void verifyStoragePermissions(Activity activity) { -// // check if we have write permission -// int permission = ActivityCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE); -// -// if (permission != PackageManager.PERMISSION_GRANTED) { -// // We don't have permission so prompt the user -// ActivityCompat.requestPermissions( -// activity, -// PERMISSIONS_STORAGE, -// REQUEST_EXTERNAL_STORAGE -// ); -// } -// } -// -//} - diff --git a/app/src/main/java/com/tangem/data/PayId.kt b/app/src/main/java/com/tangem/data/PayId.kt deleted file mode 100644 index 34fab51687..0000000000 --- a/app/src/main/java/com/tangem/data/PayId.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.tangem.data - -import java.util.* - -private val payIdSupported = EnumSet.of( - Blockchain.Ripple, - Blockchain.Ethereum, - Blockchain.Bitcoin, - Blockchain.Token, - Blockchain.Litecoin, - Blockchain.Stellar, - Blockchain.StellarAsset, - Blockchain.Cardano, - Blockchain.CardanoShelley, - Blockchain.Ducatus, - Blockchain.BitcoinCash, - Blockchain.Binance, - Blockchain.BinanceAsset, - Blockchain.Rootstock, - Blockchain.RootstockToken -) - -fun Blockchain.isPayIdSupported(): Boolean { - return payIdSupported.contains(this) -} - -fun Blockchain.getPayIdNetwork(): String { - return when (this) { - Blockchain.Ripple -> "XRPL" - Blockchain.Rootstock, Blockchain.RootstockToken -> "RSK" - else -> this.currency - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/data/dp/PrefsManager.kt b/app/src/main/java/com/tangem/data/dp/PrefsManager.kt deleted file mode 100644 index 598c719e2b..0000000000 --- a/app/src/main/java/com/tangem/data/dp/PrefsManager.kt +++ /dev/null @@ -1,71 +0,0 @@ -package com.tangem.data.dp - -import android.annotation.SuppressLint -import android.content.Context -import android.content.SharedPreferences -import androidx.preference.PreferenceManager -import com.orhanobut.hawk.Hawk -import com.tangem.Constant -import com.tangem.tangem_card.reader.CardCrypto - -class PrefsManager { - companion object { - const val PREF_NAME = "tangem_access" - - @SuppressLint("StaticFieldLeak") - private var instance: PrefsManager? = null - - fun getInstance(): PrefsManager { - if (instance == null) { - instance = PrefsManager() - } - return instance as PrefsManager - } - } - - private lateinit var context: Context - private lateinit var preferences: SharedPreferences - - fun init(context: Context) { - this.context = context - preferences = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE) - Hawk.init(context).build() - } - - val lastWalletAddress: String - get() = Hawk.get(Constant.PREF_LAST_WALLET_ADDRESS, "") - - fun saveLastWalletAddress(value: String) { - Hawk.put(Constant.PREF_LAST_WALLET_ADDRESS, value) - } - - fun clearLastWalletAddress() { - Hawk.delete(Constant.PREF_LAST_WALLET_ADDRESS) - } - - fun getSettingsBoolean(key: Int, default: Boolean): Boolean { - return PreferenceManager.getDefaultSharedPreferences(context) - .getBoolean(context.getString(key), default) - } - - fun appendCid(newCid: String) { - val prevCids: String = Hawk.get("CID_Key", "") - if (!prevCids.contains(newCid)) Hawk.put("CID_Key", "$prevCids$newCid, ") - } - - fun getAllCids(): String = Hawk.get("CID_Key", "") - - val terminalKeys: Map - get() { - val privateKey = Hawk.get(Constant.TERMINAL_PRIVATE_KEY) ?: byteArrayOf() - val publicKey = Hawk.get(Constant.TERMINAL_PUBLIC_KEY) ?: byteArrayOf() - return if (privateKey.isNotEmpty() && publicKey.isNotEmpty()) { - mapOf(Constant.TERMINAL_PRIVATE_KEY to privateKey, Constant.TERMINAL_PUBLIC_KEY to publicKey) - } else { - val keys = CardCrypto.generateTerminalKeys() - Hawk.put(Constant.TERMINAL_PRIVATE_KEY, keys[Constant.TERMINAL_PRIVATE_KEY]) - Hawk.put(Constant.TERMINAL_PUBLIC_KEY, keys[Constant.TERMINAL_PUBLIC_KEY]) - keys - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/data/fingerprint/ConfirmWithFingerprintTask.java b/app/src/main/java/com/tangem/data/fingerprint/ConfirmWithFingerprintTask.java deleted file mode 100644 index e4562d7688..0000000000 --- a/app/src/main/java/com/tangem/data/fingerprint/ConfirmWithFingerprintTask.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.tangem.data.fingerprint; - -import android.os.AsyncTask; -import android.widget.Toast; - -import com.tangem.ui.fragment.pin.PinSaveFragment; -import com.tangem.wallet.R; - -import java.lang.ref.WeakReference; - -import javax.crypto.Cipher; - -public class ConfirmWithFingerprintTask extends AsyncTask { - private WeakReference reference; - - public ConfirmWithFingerprintTask(PinSaveFragment context) { - reference = new WeakReference<>(context); - - reference.get().setFingerprintHelper(new FingerprintHelper(reference.get())); - } - - @Override - protected Boolean doInBackground(Void... params) { - PinSaveFragment pinSaveFragment = reference.get(); - - if (!pinSaveFragment.getKeyStore()) - return false; - - if (!pinSaveFragment.createNewKey(false)) - return false; - - if (!pinSaveFragment.getCipher()) - return false; - - return pinSaveFragment.initCipher(Cipher.ENCRYPT_MODE) && pinSaveFragment.initCryptObject(); - - } - - @Override - protected void onPostExecute(final Boolean success) { - PinSaveFragment pinSaveFragment = reference.get(); - - onCancelled(); - - if (!success) { - Toast.makeText(pinSaveFragment.getContext(), R.string.pin_save_notification_failed, Toast.LENGTH_LONG).show(); - } else { - pinSaveFragment.getFingerprintHelper().startAuth(pinSaveFragment.getFingerprintManager(), pinSaveFragment.getCryptoObject()); - } - } - - @Override - protected void onCancelled() { - PinSaveFragment pinSaveFragment = reference.get(); - - if (pinSaveFragment.getDFingerPrintConfirmation() != null) { - pinSaveFragment.getDFingerPrintConfirmation().cancel(); - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/data/fingerprint/FingerprintHelper.java b/app/src/main/java/com/tangem/data/fingerprint/FingerprintHelper.java deleted file mode 100644 index 3f36830d2d..0000000000 --- a/app/src/main/java/com/tangem/data/fingerprint/FingerprintHelper.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.tangem.data.fingerprint; - -import android.annotation.TargetApi; -import android.hardware.fingerprint.FingerprintManager; -import android.os.Build; -import android.os.CancellationSignal; - -/** - * Created by dtaka on 8/20/2016. - */ -@TargetApi(Build.VERSION_CODES.M) -public class FingerprintHelper extends FingerprintManager.AuthenticationCallback { - private FingerprintHelperListener listener; - - public FingerprintHelper(FingerprintHelperListener listener) { - this.listener = listener; - } - - private CancellationSignal cancellationSignal; - - public void startAuth(FingerprintManager manager, FingerprintManager.CryptoObject cryptoObject) { - cancellationSignal = new CancellationSignal(); - - try { - manager.authenticate(cryptoObject, cancellationSignal, 0, this, null); - } catch (SecurityException ex) { - listener.authenticationFailed("An error occurred:\n" + ex.getMessage()); - } catch (Exception ex) { - listener.authenticationFailed("An error occurred\n" + ex.getMessage()); - } - } - - public void cancel() { - if (cancellationSignal != null) - cancellationSignal.cancel(); - } - - public interface FingerprintHelperListener { - public void authenticationFailed(String error); - public void authenticationSucceeded(FingerprintManager.AuthenticationResult result); - } - - @Override - public void onAuthenticationError(int errMsgId, CharSequence errString) { - listener.authenticationFailed("Authentication error\n" + errString); - } - - @Override - public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) { - listener.authenticationFailed("Authentication help\n" + helpString); - } - - @Override - public void onAuthenticationFailed() { - listener.authenticationFailed("Authentication failed."); - } - - @Override - public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) { - listener.authenticationSucceeded(result); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/data/fingerprint/StartFingerprintReaderTask.java b/app/src/main/java/com/tangem/data/fingerprint/StartFingerprintReaderTask.java deleted file mode 100644 index 90b3456b45..0000000000 --- a/app/src/main/java/com/tangem/data/fingerprint/StartFingerprintReaderTask.java +++ /dev/null @@ -1,193 +0,0 @@ -package com.tangem.data.fingerprint; - -import android.annotation.TargetApi; -import android.hardware.fingerprint.FingerprintManager; -import android.os.AsyncTask; -import android.os.Build; -import android.security.keystore.KeyGenParameterSpec; -import android.security.keystore.KeyPermanentlyInvalidatedException; -import android.security.keystore.KeyProperties; - -import com.tangem.Constant; -import com.tangem.tangem_sdk.android.data.PINStorage; -import com.tangem.ui.fragment.pin.PinRequestFragment; -import com.tangem.util.LOG; - -import java.io.IOException; -import java.lang.ref.WeakReference; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.cert.CertificateException; - -import javax.crypto.Cipher; -import javax.crypto.KeyGenerator; -import javax.crypto.NoSuchPaddingException; -import javax.crypto.SecretKey; -import javax.crypto.spec.IvParameterSpec; - -public class StartFingerprintReaderTask extends AsyncTask { - public static final String TAG = StartFingerprintReaderTask.class.getSimpleName(); - - private WeakReference reference; - - private KeyStore keyStore; - private Cipher cipher; - - private FingerprintManager.CryptoObject cryptoObject; - - FingerprintManager fingerprintManager; - FingerprintHelper fingerprintHelper; - - PinRequestFragment fragment; - - public StartFingerprintReaderTask(PinRequestFragment fragment, FingerprintManager fingerprintManager, FingerprintHelper fingerprintHelper) { - reference = new WeakReference<>(fragment); - this.fingerprintManager = fingerprintManager; - this.fingerprintHelper = fingerprintHelper; - this.fragment = fragment; - } - - @Override - protected Boolean doInBackground(Void... params) { - if (!getKeyStore()) - return false; - - if (!createNewKey(false)) - return false; - - if (!getCipher()) - return false; - - if (!initCipher(Cipher.DECRYPT_MODE)) - return false; - - return initCryptObject(); - } - - @Override - protected void onPostExecute(final Boolean success) { - - onCancelled(); - - if (!success) { - LOG.i(TAG, "Authentication failed!"); - } else { - fingerprintHelper.startAuth(fingerprintManager, cryptoObject); - LOG.i(TAG, "Authenticate using fingerprint!"); - } - } - - @Override - protected void onCancelled() { - PinRequestFragment fragment = reference.get(); - - fragment.setStartFingerprintReaderTask(null); - } - - private boolean getKeyStore() { - LOG.i(TAG, "Getting keystore..."); - try { - keyStore = KeyStore.getInstance(Constant.KEYSTORE); - keyStore.load(null); // Create empty keystore - return true; - } catch (KeyStoreException | CertificateException | NoSuchAlgorithmException | IOException e) { - e.printStackTrace(); - } - - return false; - } - - @TargetApi(Build.VERSION_CODES.M) - public boolean createNewKey(boolean forceCreate) { - LOG.i(TAG, "Creating new key..."); - try { - if (forceCreate) - keyStore.deleteEntry(Constant.KEY_ALIAS); - - if (!keyStore.containsAlias(Constant.KEY_ALIAS)) { - KeyGenerator generator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, Constant.KEYSTORE); - - generator.init(new KeyGenParameterSpec.Builder(Constant.KEY_ALIAS, - KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) - .setBlockModes(KeyProperties.BLOCK_MODE_CBC) - .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7) - .setUserAuthenticationRequired(true) - .build() - ); - - generator.generateKey(); - LOG.i(TAG, "Key created."); - } else - LOG.i(TAG, "Key exists."); - - return true; - } catch (Exception e) { - e.printStackTrace(); - } - - return false; - } - - private boolean getCipher() { - LOG.i(TAG, "Getting cipher..."); - try { - cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_CBC + "/" + KeyProperties.ENCRYPTION_PADDING_PKCS7); - return true; - } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { - e.printStackTrace(); - } - - return false; - } - - @TargetApi(Build.VERSION_CODES.M) - private boolean initCipher(int mode) { - PinRequestFragment fragment = reference.get(); - - LOG.i(TAG, "Initializing cipher..."); - try { - keyStore.load(null); - SecretKey keyspec = (SecretKey) keyStore.getKey(Constant.KEY_ALIAS, null); - - if (mode == Cipher.ENCRYPT_MODE) { - cipher.init(mode, keyspec); - } else { - byte[] iv = null; - if (fragment.getMode() == PinRequestFragment.Mode.RequestPIN || - fragment.getMode() == PinRequestFragment.Mode.RequestNewPIN || - fragment.getMode() == PinRequestFragment.Mode.ConfirmNewPIN) { - iv = PINStorage.loadEncryptedIV(); - } else if (fragment.getMode() == PinRequestFragment.Mode.RequestPIN2 || - fragment.getMode() == PinRequestFragment.Mode.RequestNewPIN2 || - fragment.getMode() == PinRequestFragment.Mode.ConfirmNewPIN2) { - iv = PINStorage.loadEncryptedIV2(); - } - IvParameterSpec ivspec = new IvParameterSpec(iv); - cipher.init(mode, keyspec, ivspec); - } - - return true; - } catch (KeyPermanentlyInvalidatedException e) { - e.printStackTrace(); - createNewKey(true); // Retry after clearing entry - } catch (Exception e) { - e.printStackTrace(); - } - - return false; - } - - @TargetApi(Build.VERSION_CODES.M) - private boolean initCryptObject() { - LOG.i(TAG, "Initializing crypt object..."); - try { - cryptoObject = new FingerprintManager.CryptoObject(cipher); - return true; - } catch (Exception e) { - e.printStackTrace(); - } - return false; - } - -} diff --git a/app/src/main/java/com/tangem/data/local/PendingTransactionsStorage.kt b/app/src/main/java/com/tangem/data/local/PendingTransactionsStorage.kt deleted file mode 100644 index dca7483d44..0000000000 --- a/app/src/main/java/com/tangem/data/local/PendingTransactionsStorage.kt +++ /dev/null @@ -1,106 +0,0 @@ -package com.tangem.data.local - -import android.content.Context -import com.google.gson.Gson -import com.google.gson.reflect.TypeToken -import com.tangem.tangem_card.data.TangemCard -import java.io.File -import java.util.* -import kotlin.collections.HashMap - -class PendingTransactionsStorage -( - val context: Context -) { - private lateinit var cardTransactions: MutableMap - private val transactionsFile: File = File(context.filesDir, "transactions.json") - - private var cacheDir: File? = null - - init { - cacheDir = File(context.filesDir, "artworks") - if (!cacheDir!!.exists()) - cacheDir!!.mkdirs() - - if (transactionsFile.exists()) { - try { - transactionsFile.bufferedReader().use { cardTransactions = Gson().fromJson(it, object : TypeToken>() {}.type) } - } catch (e: Exception) { - e.printStackTrace() - cardTransactions = HashMap() - } - } else { - cardTransactions = HashMap() - } - } - - private fun clearExpired() - { - for(cardId in cardTransactions.keys) { - cardTransactions[cardId]!!.transactions.removeAll { it.isExpired() } - } - cardTransactions.entries.removeAll { it.value.isEmpty() } - } - - fun save() { - clearExpired() - val sTransactions = Gson().toJson(cardTransactions) - - transactionsFile.bufferedWriter().use { it.write(sTransactions) } - } - - fun putTransaction(card: TangemCard, txId: String, expireTimeoutInSeconds: Int) { - val sendDate=Date() - val calendar=Calendar.getInstance() - calendar.time=sendDate - calendar.add(Calendar.SECOND, expireTimeoutInSeconds) - val expireDate=calendar.time - putTransaction(card.cidDescription, txId, sendDate, expireDate) - } - - private fun putTransaction(cardId: String, txId: String, sendDate: Date, expireDate: Date) { - val transactionInfo = TransactionInfo( - txId, sendDate, expireDate - ) - if (cardTransactions[cardId] == null) { - cardTransactions[cardId] = CardTransactionsInfo(arrayListOf()) - } - cardTransactions[cardId]?.transactions?.add(transactionInfo) - save() - } - - fun getTransactions(card: TangemCard): CardTransactionsInfo? { - clearExpired() - return cardTransactions[card.cidDescription] - } - - fun hasTransactions(card: TangemCard): Boolean { - val cardTransactionsInfo=getTransactions(card) - if( cardTransactionsInfo!=null ) return cardTransactionsInfo.transactions.count()>0 - return false - } - - fun removeTransaction(card: TangemCard, txId: String) - { - cardTransactions[card.cidDescription]?.transactions?.removeAll { it.tx==txId } - save() - } - - data class TransactionInfo( - val tx: String, - val sendDate: Date, - val expireDate: Date - ) { - fun isExpired(): Boolean { - return Date().after(expireDate) - } - } - - data class CardTransactionsInfo( - var transactions: MutableList - ) { - fun isEmpty(): Boolean { - return transactions.count()==0 - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/data/network/AdaliteApi.java b/app/src/main/java/com/tangem/data/network/AdaliteApi.java deleted file mode 100644 index 3a6b380945..0000000000 --- a/app/src/main/java/com/tangem/data/network/AdaliteApi.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.tangem.data.network; - -import com.tangem.data.network.model.AdaliteBody; -import com.tangem.data.network.model.AdaliteResponse; -import com.tangem.data.network.model.AdaliteResponseUtxo; - -import java.util.List; - -import retrofit2.Call; -import retrofit2.http.Body; -import retrofit2.http.GET; -import retrofit2.http.Headers; -import retrofit2.http.POST; -import retrofit2.http.Path; - -public interface AdaliteApi { - @GET(ServerApiAdalite.ADALITE_ADDRESS) - Call adaliteAddress(@Path("address") String address); - - @Headers("Content-Type: application/json") - @POST(ServerApiAdalite.ADALITE_UNSPENT_OUTPUTS) - Call adaliteUnspent(@Body String address); - -// @GET(ServerApiAdalite.ADALITE_TRANSACTION) -// Call adaliteTransaction(@Path("txId") String txId); - - @Headers("Content-Type: application/json") - @POST(ServerApiAdalite.ADALITE_SEND) - Call adaliteSend(@Body AdaliteBody adaliteBody); -} diff --git a/app/src/main/java/com/tangem/data/network/BinanceApi.java b/app/src/main/java/com/tangem/data/network/BinanceApi.java deleted file mode 100644 index 5bc0b95059..0000000000 --- a/app/src/main/java/com/tangem/data/network/BinanceApi.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.tangem.data.network; - -import com.tangem.data.network.model.BinanceFees; - -import java.util.List; - -import retrofit2.Call; -import retrofit2.http.GET; - -public interface BinanceApi { - @GET("fees") - Call> binanceFees(); -} diff --git a/app/src/main/java/com/tangem/data/network/BlockchainInfoApi.java b/app/src/main/java/com/tangem/data/network/BlockchainInfoApi.java deleted file mode 100644 index 7584a12d64..0000000000 --- a/app/src/main/java/com/tangem/data/network/BlockchainInfoApi.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.tangem.data.network; - -import com.tangem.data.network.model.BlockchainInfoAddress; -import com.tangem.data.network.model.BlockchainInfoUnspents; - -import io.reactivex.Single; -import okhttp3.ResponseBody; -import retrofit2.http.Field; -import retrofit2.http.FormUrlEncoded; -import retrofit2.http.GET; -import retrofit2.http.POST; -import retrofit2.http.Path; -import retrofit2.http.Query; - -public interface BlockchainInfoApi { - @GET(Server.ApiBlockchainInfo.Method.ADDRESS) - Single blockchainInfoAddress(@Path("address") String address, @Query("offset") Integer offset); - - @GET(Server.ApiBlockchainInfo.Method.UTXO) - Single blockchainInfoUnspents(@Query("active") String address); - - @FormUrlEncoded - @POST(Server.ApiBlockchainInfo.Method.PUSH) - Single blockchainInfoPush(@Field("tx") String tx); -} diff --git a/app/src/main/java/com/tangem/data/network/BlockchairApi.java b/app/src/main/java/com/tangem/data/network/BlockchairApi.java deleted file mode 100644 index ae6f01624b..0000000000 --- a/app/src/main/java/com/tangem/data/network/BlockchairApi.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.tangem.data.network; - -import com.tangem.data.network.model.BlockchairAddressResponse; -import com.tangem.data.network.model.BlockchairSendBody; -import com.tangem.data.network.model.BlockchairStatsResponse; -import com.tangem.data.network.model.BlockchairTransactionResponse; - -import io.reactivex.Completable; -import io.reactivex.Single; -import retrofit2.http.Body; -import retrofit2.http.GET; -import retrofit2.http.POST; -import retrofit2.http.Path; - -public interface BlockchairApi { - @GET(Server.ApiBlockchair.Method.ADDRESS) - Single getAddress(@Path("blockchain") String blockchain, @Path("address") String address); - - @GET(Server.ApiBlockchair.Method.TRANSACTION) - Single getTransaction(@Path("blockchain") String blockchain, @Path("transaction") String transaction); - - @GET(Server.ApiBlockchair.Method.STATS) - Single getStats(@Path("blockchain") String blockchain); - - @POST(Server.ApiBlockchair.Method.PUSH) - Completable sendTransaction(@Path("blockchain") String blockchain, @Body BlockchairSendBody body); -} diff --git a/app/src/main/java/com/tangem/data/network/BlockcypherApi.java b/app/src/main/java/com/tangem/data/network/BlockcypherApi.java deleted file mode 100644 index 8bba6dfdee..0000000000 --- a/app/src/main/java/com/tangem/data/network/BlockcypherApi.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.tangem.data.network; - -import com.tangem.data.network.model.BlockcypherBody; -import com.tangem.data.network.model.BlockcypherResponse; -import com.tangem.data.network.model.BlockcypherFee; -import com.tangem.data.network.model.BlockcypherTx; - -import retrofit2.Call; -import retrofit2.http.Body; -import retrofit2.http.GET; -import retrofit2.http.Headers; -import retrofit2.http.POST; -import retrofit2.http.Path; -import retrofit2.http.Query; - -public interface BlockcypherApi { - @GET(Server.ApiBlockcypher.Method.MAIN) - Call blockcypherMain(@Path("blockchain") String blockchain, @Path("network") String network, @Query("token") String token); - - @GET(Server.ApiBlockcypher.Method.ADDRESS) - Call blockcypherAddress(@Path("blockchain") String blockchain, @Path("network") String network, @Path("address") String address, @Query("token") String token); - - @GET(Server.ApiBlockcypher.Method.TXS) - Call blockcypherTxs(@Path("blockchain") String blockchain, @Path("network") String network, @Path("txHash") String txHash, @Query("token") String token); - - @Headers("Content-Type: application/json") - @POST(Server.ApiBlockcypher.Method.PUSH) - Call blockcypherPush(@Path("blockchain") String blockchain, @Path("network") String network, @Body BlockcypherBody blockcypherBody, @Query("token") String token); -} diff --git a/app/src/main/java/com/tangem/data/network/BlockcypherToken.kt b/app/src/main/java/com/tangem/data/network/BlockcypherToken.kt deleted file mode 100644 index 32bf303df9..0000000000 --- a/app/src/main/java/com/tangem/data/network/BlockcypherToken.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.tangem.data.network - -enum class BlockcypherToken(val token: String) { - T_001("aa8184b0e0894b88a5688e01b3dc1e82"), - T_002("56c4ca23c6484c8f8864c32fde4def8d"), - T_003("66a8a37c5e9d4d2c9bb191acfe7f93aa") -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/data/network/CoinmarketApi.java b/app/src/main/java/com/tangem/data/network/CoinmarketApi.java deleted file mode 100644 index fd3699ba08..0000000000 --- a/app/src/main/java/com/tangem/data/network/CoinmarketApi.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.tangem.data.network; - -import com.tangem.data.network.model.RateInfoResponse; - -import io.reactivex.Observable; -import retrofit2.http.GET; -import retrofit2.http.Headers; -import retrofit2.http.Query; - -public interface CoinmarketApi { - @Headers("X-CMC_PRO_API_KEY: f6622117-c043-47a0-8975-9d673ce484de") - @GET(Server.ApiCoinmarket.Method.PRICE_CONVERSION) - Observable getRateInfo(@Query("amount") int amount, @Query("symbol") String cryptoId); -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/data/network/Cryptonit.java b/app/src/main/java/com/tangem/data/network/Cryptonit.java deleted file mode 100644 index 75f9586baa..0000000000 --- a/app/src/main/java/com/tangem/data/network/Cryptonit.java +++ /dev/null @@ -1,317 +0,0 @@ -package com.tangem.data.network; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.SharedPreferences; -import android.preference.PreferenceManager; - -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import com.google.gson.internal.LinkedTreeMap; -import com.jakewharton.retrofit2.adapter.rxjava2.HttpException; -import com.jakewharton.retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; -import com.tangem.wallet.R; - -import java.io.IOException; - -import io.reactivex.Observable; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.schedulers.Schedulers; -import okhttp3.Interceptor; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; -//import okhttp3.logging.HttpLoggingInterceptor; -import retrofit2.Call; -import retrofit2.Retrofit; -import retrofit2.converter.gson.GsonConverterFactory; -import retrofit2.http.Body; -import retrofit2.http.Header; -import retrofit2.http.Headers; -import retrofit2.http.POST; - -/** - * HTTP - * Used in Cryptonit service - */ -public class Cryptonit { - private static final String SERVER_URL = "https://www.cryptonit.net:443/gateway/"; - - private static class Method { - public static final String AUTHENTICATE = SERVER_URL + "public/authenticate"; - public static final String BALANCE = SERVER_URL + "private/balance"; - public static final String WITHDRAW_COINS = SERVER_URL + "private/withdrawCoins"; - } - - public static class Model { - - static class Authenticate - { - public static class Request - { - public String username; - public String password; - } - public static class Response - { - boolean success; - String[] missing_authenticators; - public Object[] infos; - public Object[] warnings; - public Object[] errors; - AuthenticationResult results; - } - - static class AuthenticationResult { -// token (string): authentication token, -// nick (string), -// stayLoggedIn (boolean), -// integer lastLogin (integer): JavaScript time OR NEVER for first login, -// preferredLanguage (string) = ['en' or 'de'] - - } - - } - - public static class Balance { - static class Request { - String[] currencies; - } - public static class Response { - public boolean success; - public Object[] infos; - public Object[] warnings; - public Object[] errors; - public BalanceResult[] results; - - public static class BalanceResult { - public String currency; - public double balance; - public String receiveAddress; //the current address to receive funds for this account, if available, - public boolean fiat; // Is this a FIAT currency? If false, this is a CRYPTO currency., - public Object[] unprocessedTransactions; // (array, optional): list of unprocessed transactions, if pass field withTransactions - } - } - } - - public static class WithdrawCoins { - public static class Request { - public String currency; - public Double amount; - String toAddress; - Double includeMinerFee; - String password; - } - public static class Response { - public Boolean success; - public String[] missing_authenticators; - public Object[] infos; - public Object[] warnings; - public Object[] errors; - } - } - } - - public interface Api { - @Headers("Content-Type: application/json") - @POST(Method.AUTHENTICATE) - Call authenticate(@Body Model.Authenticate.Request request); - - @Headers("Content-Type: application/json") - @POST(Method.BALANCE) - Observable getBalance(@Header("Auth-Token") String authToken, @Body Model.Balance.Request request); - - @Headers("Content-Type: application/json") - @POST(Method.WITHDRAW_COINS) - Observable withdrawCoins(@Header("Auth-Token") String authToken, @Body Model.WithdrawCoins.Request request); - } - - private Api api = null; - private BalanceListener balanceListener; - private WithdrawalListener withdrawalListener; - private ErrorListener errorListener; - private Context context; - private String authToken; - - public Cryptonit(Context context) { - this.context = context; - SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); - username = sp.getString(context.getResources().getString(R.string.key_cryptonit_username), ""); - password = sp.getString(context.getResources().getString(R.string.key_cryptonit_password), ""); - fee = sp.getString(context.getResources().getString(R.string.key_cryptonit_fee), "0.0"); - } - - public String username; - public String password; - private String fee; - - public String getFee() { - return fee; - } - - public void setFee(String value) - { - fee=value; - SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); - sp.edit() - .putString(context.getResources().getString(R.string.key_cryptonit_fee), fee) - .apply(); - } - - public Boolean haveAccountInfo() { - return !username.isEmpty() && !password.isEmpty(); - } - - public void saveAccountInfo() { - SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); - sp.edit() - .putString(context.getResources().getString(R.string.key_cryptonit_username), username) - .putString(context.getResources().getString(R.string.key_cryptonit_password), password) - .apply(); - - } - - public interface BalanceListener { - void onBalanceData(Model.Balance.Response response); - } - - public interface WithdrawalListener { - void onWithdrawalComplete(Model.WithdrawCoins.Response response); - } - - public interface ErrorListener { - void onError(Throwable throwable); - } - - public void setBalanceListener(BalanceListener listener) { - balanceListener = listener; - } - - public void setWithdrawalListener(WithdrawalListener listener) { - withdrawalListener = listener; - } - - public void setErrorListener(ErrorListener listener) { - errorListener = listener; - } - - @SuppressLint("CheckResult") - public void requestBalance(String currency) { - - initApi(); - -// Log.e("CRYPTONIT2", "username: " + username); -// Log.e("CRYPTONIT2", "password: " + password); -// Log.e("CRYPTONIT2", "auth-token: " + authToken); - - Model.Balance.Request request=new Model.Balance.Request(); - request.currencies=new String[] {currency}; - api.getBalance(authToken, request) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(balanceModel -> balanceListener.onBalanceData(balanceModel), - // handle error - this::FireError - ); - } - - @SuppressLint("CheckResult") - public void requestWithdrawCoins(String currency, Double amount, String address) { - - initApi(); - -// Log.e("CRYPTONIT2", "username: " + username); -// Log.e("CRYPTONIT2", "password: " + password); -// Log.e("CRYPTONIT2", "auth-token: " + authToken); - - Model.WithdrawCoins.Request request=new Model.WithdrawCoins.Request(); - request.currency=currency; - request.amount=amount; - request.toAddress=address; - request.includeMinerFee=Double.parseDouble(fee); - request.password=password; - - api.withdrawCoins(authToken, request) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(response -> { - if (response.success != null && response.success) - withdrawalListener.onWithdrawalComplete(response); - else { - errorListener.onError(new Exception(((LinkedTreeMap) response.errors[0]).entrySet().toArray()[0].toString())); - } - }, - // handle error - this::FireError - ); - } - - private void FireError(Throwable e) throws IOException { - if (e.getClass() == HttpException.class && ((HttpException) e).code() == 500) { - JsonObject jsonObject = new JsonParser().parse(((HttpException) e).response().errorBody().string()).getAsJsonObject(); - errorListener.onError(new Exception(e.getMessage() + ": " + jsonObject.get("errors").getAsString())); -// if (jsonObject.get("reason").getAsString().equals("Invalid nonce")) nonce += 1000; - } else { - errorListener.onError(e); - } - } - - private void initApi() { - - if (api != null) return; - -// HttpLoggingInterceptor logging = new HttpLoggingInterceptor(); -// logging.setLevel(HttpLoggingInterceptor.Level.BODY); - - OkHttpClient httpClient = new OkHttpClient.Builder(). -// addInterceptor(logging). - addInterceptor(new AuthorizationInterceptor()).build(); - - Retrofit retrofit = new Retrofit.Builder() - .baseUrl(SERVER_URL) - .addConverterFactory(GsonConverterFactory.create()) - .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) - .client(httpClient) - .build(); - - api = retrofit.create(Api.class); - } - - public class AuthorizationInterceptor implements Interceptor { - - AuthorizationInterceptor() { - } - - @Override - public Response intercept(Chain chain) throws IOException { - Request mainRequest=chain.request(); - - if( !mainRequest.url().toString().endsWith("authenticate")&& authToken==null ) - { - Model.Authenticate.Request authRequest=new Model.Authenticate.Request(); - authRequest.username=username; - authRequest.password=password; - retrofit2.Response authResponse=api.authenticate(authRequest).execute(); - if( authResponse.isSuccessful() && authResponse.body().success) - { - String newToken = authResponse.headers().get("auth-token"); - if (newToken != null) { - authToken = newToken; - } - }else{ - throw new IOException("Authentication error: "+authResponse.message()); - } - } - - if( authToken!=null ) { - mainRequest = mainRequest.newBuilder().addHeader("auth-token", authToken).build(); - } - Response mainResponse = chain.proceed(mainRequest); - if (!mainResponse.isSuccessful()) { - authToken=null; - } - return mainResponse; - - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/data/network/CryptonitOtherApi.java b/app/src/main/java/com/tangem/data/network/CryptonitOtherApi.java deleted file mode 100644 index 48f1480ebd..0000000000 --- a/app/src/main/java/com/tangem/data/network/CryptonitOtherApi.java +++ /dev/null @@ -1,283 +0,0 @@ -package com.tangem.data.network; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.SharedPreferences; -import android.preference.PreferenceManager; - -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import com.google.gson.annotations.SerializedName; -import com.google.gson.internal.LinkedTreeMap; -import com.jakewharton.retrofit2.adapter.rxjava2.HttpException; -import com.jakewharton.retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; -import com.tangem.tangem_card.util.Util; -import com.tangem.wallet.R; - -import java.io.IOException; - -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; - -import io.reactivex.Observable; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.schedulers.Schedulers; -import okhttp3.MediaType; -import okhttp3.OkHttpClient; -import okhttp3.RequestBody; -import retrofit2.Retrofit; -import retrofit2.converter.gson.GsonConverterFactory; -import retrofit2.http.Headers; -import retrofit2.http.Multipart; -import retrofit2.http.POST; -import retrofit2.http.Part; -import retrofit2.http.Path; - -//import okhttp3.logging.HttpLoggingInterceptor; - -/** - * HTTP - * Used in CryptonitOtherApi service - */ -public class CryptonitOtherApi { - private static final String SERVER_URL = "https://api.cryptonit.net/api/"; - - private static class Method { - public static final String BALANCE = SERVER_URL + "balance/{cryptoCurrency}%2F{fiatCurrency}"; - public static final String CRYPTO_WITHDRAWAL = SERVER_URL + "crypto_withdrawal/"; - } - - public static class Response { - public static class Balance{ - @SerializedName("btc_balance") - public String btc_balance; - @SerializedName("eur_balance") - public String eur_balance; - @SerializedName("eth_balance") - public String eth_balance; - @SerializedName("etn_balance") - public String etn_balance; - @SerializedName("btc_reserved") - public String btc_reserved; - @SerializedName("eur_reserved") - public String eur_reserved; - @SerializedName("eth_reserved") - public String eth_reserved; - @SerializedName("etn_reserved") - public String etn_reserved; - @SerializedName("btc_available") - public String btc_available; - @SerializedName("eur_available") - public String eur_available; - @SerializedName("eth_available") - public String eth_available; - @SerializedName("etn_available") - public String etn_available; - @SerializedName("btceur_fee") - public String btceur_fee; - @SerializedName("etheur_fee") - public String etheur_fee; - @SerializedName("etnbtc_fee") - public String etnbtc_fee; - @SerializedName("fee") - public String fee; - } - - public static class CryptoWithdrawal { - @SerializedName("success") - public Boolean success; - @SerializedName("status") - public String status; - @SerializedName("reason") - public Object reason; - } - } - - public interface Api { - @Multipart - @Headers("accept: multipart/form-data") - @POST(Method.BALANCE) - Observable getBalance( - @Path("cryptoCurrency") String cryptoCurrency, @Path("fiatCurrency") String fiatCurrency, - @Part(value = "key") RequestBody key, @Part("signature") RequestBody signature, @Part("nonce") RequestBody nonce); - - @Multipart - @Headers("accept: multipart/form-data") - @POST(Method.CRYPTO_WITHDRAWAL) - Observable cryptoWithdrawal( - @Part(value = "currency") RequestBody currency, @Part("amount") RequestBody amount, @Part("address") RequestBody address, - @Part(value = "key") RequestBody key, @Part("signature") RequestBody signature, @Part("nonce") RequestBody nonce); - } - - private Api api = null; - private BalanceListener balanceListener; - private WithdrawalListener withdrawalListener; - private ErrorListener errorListener; - private Context context; - - public CryptonitOtherApi(Context context) { - this.context = context; - SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); - key = sp.getString(context.getResources().getString(R.string.key_cryptonit_key), ""); - userId = sp.getString(context.getResources().getString(R.string.key_cryptonit_user_id), ""); - secret = sp.getString(context.getResources().getString(R.string.key_cryptonit_secret), ""); - nonce = sp.getInt(context.getResources().getString(R.string.key_cryptonit_nonce), 0); - } - - public String key; - public String userId; - public String secret; - private Integer nonce; - - public String getSecretDescription() { - if (secret == null || secret.isEmpty()) return ""; - return secret.substring(0, 3) + "..." + secret.substring(secret.length() - 3, secret.length()); - } - - public Boolean havaAccountInfo() { - return (!userId.isEmpty() && !key.isEmpty() && !secret.isEmpty()); - } - - public void saveAccountInfo() { - SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); - nonce = 1; - sp.edit() - .putString(context.getResources().getString(R.string.key_cryptonit_key), key) - .putString(context.getResources().getString(R.string.key_cryptonit_user_id), userId) - .putString(context.getResources().getString(R.string.key_cryptonit_secret), secret) - .putInt(context.getResources().getString(R.string.key_cryptonit_nonce), nonce) - .apply(); - - } - - private void incNonce() { - nonce++; - SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); - sp.edit().putInt(context.getResources().getString(R.string.key_cryptonit_nonce), nonce).apply(); - } - - public interface BalanceListener { - void onBalanceData(Response.Balance response); - } - - public interface WithdrawalListener { - void onWithdrawalComplete(Response.CryptoWithdrawal response); - } - - public interface ErrorListener { - void onError(Throwable throwable); - } - - public void setBalanceListener(BalanceListener listener) { - balanceListener = listener; - } - - public void setWithdrawalListener(WithdrawalListener listener) { - withdrawalListener = listener; - } - - public void setErrorListener(ErrorListener listener) { - errorListener = listener; - } - - private String calcSignature() throws Exception { - incNonce(); - Mac sha256_HMAC = Mac.getInstance("HmacSHA256"); - SecretKeySpec secret_key = new SecretKeySpec(secret.getBytes("UTF-8"), "HmacSHA256"); - sha256_HMAC.init(secret_key); - - String data = nonce.toString() + userId + key; - return Util.bytesToHex(sha256_HMAC.doFinal(data.getBytes("UTF-8"))); - } - - @SuppressLint("CheckResult") - public void requestBalance(String cryptoCurrency, String fiatCurrency) throws Exception { - - initApi(); - - String signature = calcSignature(); - -// Log.e("CRYPTONIT", "user: " + userId); -// Log.e("CRYPTONIT", "key: " + key); -// Log.e("CRYPTONIT", "secret: " + secret); -// Log.e("CRYPTONIT", "nonce: " + nonce); -// Log.e("CRYPTONIT", "signature: " + signature); - - api.getBalance(cryptoCurrency, fiatCurrency, - RequestBody.create(MediaType.parse("text/plain"), key), - RequestBody.create(MediaType.parse("text/plain"), signature), - RequestBody.create(MediaType.parse("text/plain"), nonce.toString())) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(balanceModel -> balanceListener.onBalanceData(balanceModel), - // handle error - this::FireError - ); - } - - @SuppressLint("CheckResult") - public void requestCryptoWithdrawal(String currency, String amount, String address) throws Exception { - - initApi(); - - String signature = calcSignature(); - -// Log.e("CRYPTONIT", "user: " + userId); -// Log.e("CRYPTONIT", "key: " + key); -// Log.e("CRYPTONIT", "secret: " + secret); -// Log.e("CRYPTONIT", "nonce: " + nonce); -// Log.e("CRYPTONIT", "signature: " + signature); -// - api.cryptoWithdrawal( - RequestBody.create(MediaType.parse("text/plain"), currency), - RequestBody.create(MediaType.parse("text/plain"), amount), - RequestBody.create(MediaType.parse("text/plain"), address), - RequestBody.create(MediaType.parse("text/plain"), key), - RequestBody.create(MediaType.parse("text/plain"), signature), - RequestBody.create(MediaType.parse("text/plain"), nonce.toString())) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(response -> { - if (response.success != null && response.success) - withdrawalListener.onWithdrawalComplete(response); - else { - LinkedTreeMap reason = (LinkedTreeMap) response.reason; - errorListener.onError(new Exception(reason.entrySet().toArray()[0].toString())); - } - }, - // handle error - this::FireError - ); - } - - private void FireError(Throwable e) throws IOException { - if (e.getClass() == HttpException.class && ((HttpException) e).code() == 500) { - JsonObject jsonObject = new JsonParser().parse(((HttpException) e).response().errorBody().string()).getAsJsonObject(); - errorListener.onError(new Exception(e.getMessage() + ": " + jsonObject.get("reason").getAsString())); - if (jsonObject.get("reason").getAsString().equals("Invalid nonce")) nonce += 1000; - } else { - errorListener.onError(e); - } - } - - private void initApi() { - - if (api != null) return; - -// HttpLoggingInterceptor logging = new HttpLoggingInterceptor(); -// logging.setLevel(HttpLoggingInterceptor.Level.BODY); - - OkHttpClient httpClient = new OkHttpClient.Builder(). -// addInterceptor(logging). - build(); - - Retrofit retrofit = new Retrofit.Builder() - .baseUrl(SERVER_URL) - .addConverterFactory(GsonConverterFactory.create()) - .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) - .client(httpClient) - .build(); - - api = retrofit.create(Api.class); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/data/network/DucatusApi.java b/app/src/main/java/com/tangem/data/network/DucatusApi.java deleted file mode 100644 index 53957f70fc..0000000000 --- a/app/src/main/java/com/tangem/data/network/DucatusApi.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.tangem.data.network; - -import com.tangem.data.network.model.BitcoreBalance; -import com.tangem.data.network.model.BitcoreSendBody; -import com.tangem.data.network.model.BitcoreSendResponse; -import com.tangem.data.network.model.BitcoreUtxo; - -import java.util.List; - -import io.reactivex.Single; -import retrofit2.http.Body; -import retrofit2.http.GET; -import retrofit2.http.POST; -import retrofit2.http.Path; - -public interface DucatusApi { - @GET(Server.ApiDucatus.Method.BALANCE) - Single ducatusBalance(@Path("address") String address); - - @GET(Server.ApiDucatus.Method.UTXO) - Single> ducatusUnspents(@Path("address") String address); - - @POST(Server.ApiDucatus.Method.SEND) - Single ducatusSend(@Body BitcoreSendBody body); -} diff --git a/app/src/main/java/com/tangem/data/network/ElectrumRequest.java b/app/src/main/java/com/tangem/data/network/ElectrumRequest.java deleted file mode 100644 index 591b359973..0000000000 --- a/app/src/main/java/com/tangem/data/network/ElectrumRequest.java +++ /dev/null @@ -1,193 +0,0 @@ -package com.tangem.data.network; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -/** - * Created by dvol on 16.07.2017. - */ - -public class ElectrumRequest { - public static final String METHOD_GetBalance = "blockchain.address.get_balance"; - public static final String METHOD_ListUnspent = "blockchain.address.listunspent"; - public static final String METHOD_GetHistory = "blockchain.address.get_history"; - public static final String METHOD_GetTransaction = "blockchain.transaction.get"; - public static final String METHOD_GetHeader = "blockchain.block.get_header"; - public static final String METHOD_SendTransaction = "blockchain.transaction.broadcast"; - public static final String METHOD_GetFee = "blockchain.estimatefee"; - - private JSONObject jsRequestData; - String answerData; - private String error = null; - private String walletAddress; - public String txHash; - private String TX; - String host; - int port; - - private ElectrumRequest() { - } - - public ElectrumRequest(JSONObject jsRequest) { - try { - jsRequestData = new JSONObject(jsRequest.toString()); - } catch (JSONException e) { - e.printStackTrace(); - } - } - - public JSONObject getAnswer() { - try { - return new JSONObject(answerData); - } catch (Exception e) { - try { - return new JSONObject(String.format("[\"Error\":\"%s\"]", e.getMessage())); - } catch (JSONException e1) { - e1.printStackTrace(); - return null; - } - } - } - - public String getAsString() { - return jsRequestData.toString(); - } - - public void setID(int value) { - try { - jsRequestData.put("id", String.format("%d", value)); - } catch (JSONException e) { - e.printStackTrace(); - } - } - - public int getID() { - try { - return jsRequestData.getInt("id"); - } catch (JSONException e) { - e.printStackTrace(); - return 0; - } - } - - public static ElectrumRequest checkBalance(String wallet) { - ElectrumRequest request = new ElectrumRequest(); - try { - request.walletAddress = wallet; - request.jsRequestData = new JSONObject("{ \"method\":\"" + METHOD_GetBalance + "\", \"params\":[\"" + wallet + "\"] }"); - } catch (JSONException e) { - e.printStackTrace(); - request.error = e.toString(); - } - return request; - } - - - public static ElectrumRequest getFee() { - ElectrumRequest request = new ElectrumRequest(); - try { - request.jsRequestData = new JSONObject("{ \"method\":\"" + METHOD_GetFee + "\", \"params\":[\"" + 6 + "\"] }"); - } catch (JSONException e) { - e.printStackTrace(); - request.error = e.toString(); - } - return request; - } - - public static ElectrumRequest listUnspent(String wallet) { - ElectrumRequest request = new ElectrumRequest(); - try { - request.walletAddress = wallet; - request.jsRequestData = new JSONObject("{ \"method\":\"" + METHOD_ListUnspent + "\", \"params\":[\"" + wallet + "\"] }"); - } catch (JSONException e) { - e.printStackTrace(); - request.error = e.toString(); - } - return request; - } - - public static ElectrumRequest broadcast(String wallet, String tx) { - ElectrumRequest request = new ElectrumRequest(); - try { - request.walletAddress = wallet; - request.TX = tx; - request.jsRequestData = new JSONObject("{ \"method\":\"" + METHOD_SendTransaction + "\", \"params\":[\"" + tx + "\"] }"); - } catch (JSONException e) { - e.printStackTrace(); - request.error = e.toString(); - } - return request; - } - - public static ElectrumRequest getTransaction(String wallet, String tx_hash) { - ElectrumRequest request = new ElectrumRequest(); - try { - request.walletAddress = wallet; - request.txHash = tx_hash; - request.jsRequestData = new JSONObject("{ \"method\":\"" + METHOD_GetTransaction + "\", \"params\":[\"" + tx_hash + "\"] }"); - } catch (JSONException e) { - e.printStackTrace(); - request.error = e.toString(); - } - return request; - } - - public String getMethod() { - try { - return jsRequestData.getString("method"); - } catch (JSONException e) { - e.printStackTrace(); - } - return ""; - } - - public boolean isMethod(String methodName) { - return getMethod().equals(methodName); - } - - public JSONArray getParams() throws JSONException { - return jsRequestData.getJSONArray("params"); - } - - public JSONObject getResult() throws JSONException { - return getAnswer().getJSONObject("result"); - } - - public String getError() { - if( answerData!=null ) { - // answer received - return error from it - JSONObject answer = getAnswer(); - if (answer.has("error")) { - try { - return getAnswer().getJSONObject("error").toString(); - } catch (JSONException e) { - e.printStackTrace(); - return null; - } - } else { - return null; - } - }else{ - // no answer received - return saved error reason - return error; - } - } - - public void setError(String error) { - this.error = error; - } - - public String getResultString() throws JSONException { - if (getAnswer().has("result")) { - return getAnswer().getString("result"); - } else { - return null; - } - } - - public JSONArray getResultArray() throws JSONException { - return getAnswer().getJSONArray("result"); - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/data/network/EstimatefeeApi.java b/app/src/main/java/com/tangem/data/network/EstimatefeeApi.java deleted file mode 100644 index a004db3864..0000000000 --- a/app/src/main/java/com/tangem/data/network/EstimatefeeApi.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.tangem.data.network; - -import retrofit2.Call; -import retrofit2.http.GET; - -public interface EstimatefeeApi { - @GET(Server.ApiEstimatefee.Method.N_2) - Call getEstimateFeePriority(); - - @GET(Server.ApiEstimatefee.Method.N_3) - Call getEstimateFeeNormal(); - - @GET(Server.ApiEstimatefee.Method.N_6) - Call getEstimateFeeMinimal(); -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/data/network/InfuraApi.java b/app/src/main/java/com/tangem/data/network/InfuraApi.java deleted file mode 100644 index 2ad1802e25..0000000000 --- a/app/src/main/java/com/tangem/data/network/InfuraApi.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.tangem.data.network; - -import com.tangem.data.network.model.InfuraBody; -import com.tangem.data.network.model.InfuraResponse; - -import retrofit2.Call; -import retrofit2.http.Body; -import retrofit2.http.Headers; -import retrofit2.http.POST; - -public interface InfuraApi { - @Headers("Content-Type: application/json") - @POST(Server.ApiInfura.Method.MAIN) - Call infura(@Body InfuraBody body); -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/data/network/InsightApi.java b/app/src/main/java/com/tangem/data/network/InsightApi.java deleted file mode 100644 index 4095326dc5..0000000000 --- a/app/src/main/java/com/tangem/data/network/InsightApi.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.tangem.data.network; - -import com.tangem.data.network.model.InsightBody; -import com.tangem.data.network.model.InsightResponse; -import com.tangem.data.network.model.InsightUtxo; - -import java.util.List; - -import retrofit2.Call; -import retrofit2.http.Body; -import retrofit2.http.GET; -import retrofit2.http.Headers; -import retrofit2.http.POST; -import retrofit2.http.Path; -import retrofit2.http.Query; - -public interface InsightApi { - @GET(ServerApiInsight.INSIGHT_ADDRESS) - Call insightAddress(@Path("address") String address); - - @GET(ServerApiInsight.INSIGHT_UNSPENT_OUTPUTS) - Call> insightUnspent(@Path("address") String address); - - @GET(ServerApiInsight.INSIGHT_TRANSACTION) - Call insightTransaction(@Path("txId") String txId); - - @GET(ServerApiInsight.INSIGHT_FEE) - Call insightFee(); - - @Headers("Content-Type: application/json") - @POST(ServerApiInsight.INSIGHT_SEND) - Call insightSend(@Body InsightBody body ); -} diff --git a/app/src/main/java/com/tangem/data/network/Kraken.java b/app/src/main/java/com/tangem/data/network/Kraken.java deleted file mode 100644 index 4f7eaae457..0000000000 --- a/app/src/main/java/com/tangem/data/network/Kraken.java +++ /dev/null @@ -1,358 +0,0 @@ -package com.tangem.data.network; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.SharedPreferences; -import android.preference.PreferenceManager; - -import com.jakewharton.retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; -import com.tangem.tangem_card.util.Util; -import com.tangem.wallet.R; - -import org.spongycastle.util.encoders.Base64; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; - -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; - -import io.reactivex.Observable; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.schedulers.Schedulers; -import okhttp3.Interceptor; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.Response; -import okio.Buffer; -import retrofit2.Retrofit; -import retrofit2.converter.gson.GsonConverterFactory; -import retrofit2.http.Field; -import retrofit2.http.FormUrlEncoded; -import retrofit2.http.POST; - -//import okhttp3.logging.HttpLoggingInterceptor; - -/** - * HTTP - * Used in Kraken service - */ -public class Kraken { - private static final String SERVER_URL = "https://api.kraken.com"; - - private static class Method { - public static final String BALANCE = SERVER_URL + "/0/private/Balance"; - public static final String WITHDRAW_INFO = SERVER_URL + "/0/private/WithdrawInfo"; - public static final String WITHDRAW = SERVER_URL + "/0/private/Withdraw"; - } - - public static class Model { - - public static class Balance { - public static class Response { - public String[] error; - public Result result; - - public static class Result { - public String XXBT; - public String XETH; - public String BCH; - } - } - } - - public static class WithdrawInfo { - public static class Response { - public String[] error; - public Result result; - - public static class Result { - public String fee; - public String amount; - } - } - } - - public static class Withdraw { - public static class Response { - public String[] error; - public Result result; - - public static class Result { - public String refid; - } - } - } - } - - public interface Api { - @FormUrlEncoded - @POST(Method.BALANCE) - Observable getBalance(@Field("nonce") String nonce); - - @FormUrlEncoded - @POST(Method.WITHDRAW_INFO) - Observable WithdrawInfo(@Field("nonce") String nonce, @Field("asset") String asset, @Field("key") String key, @Field("amount") String amount); - - @FormUrlEncoded - @POST(Method.WITHDRAW) - Observable Withdraw(@Field("nonce") String nonce, @Field("asset") String asset, @Field("key") String key, @Field("amount") String amount); - } - - private Api api = null; - private BalanceListener balanceListener; - private WithdrawalListener withdrawalListener; - private WithdrawalInfoListener withdrawalInfoListener; - private ErrorListener errorListener; - private Context context; - - public Kraken(Context context) { - this.context = context; - SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); - key = sp.getString(context.getResources().getString(R.string.key_kraken_key), ""); - secret = sp.getString(context.getResources().getString(R.string.key_kraken_secret), ""); - nonce = sp.getInt(context.getResources().getString(R.string.key_kraken_nonce), 0); - } - - public String key; - public String secret; - private Integer nonce; - - public String getSecretDescription() { - if (secret == null || secret.isEmpty()) return ""; - return secret.substring(0, 3) + "..." + secret.substring(secret.length() - 3, secret.length()); - } - - public Boolean haveAccountInfo() { - return (!key.isEmpty() && !secret.isEmpty()); - } - - public void saveAccountInfo() { - SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); - sp.edit() - .putString(context.getResources().getString(R.string.key_kraken_key), key) - .putString(context.getResources().getString(R.string.key_kraken_secret), secret) - .putInt(context.getResources().getString(R.string.key_kraken_nonce), nonce) - .apply(); - - } - - private void incNonce() { - nonce++; - SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); - sp.edit().putInt(context.getResources().getString(R.string.key_kraken_nonce), nonce).apply(); - } - - public interface BalanceListener { - void onBalanceData(Model.Balance.Response response); - } - - public interface WithdrawalInfoListener { - void onWithdrawalInfoComplete(Model.WithdrawInfo.Response response); - } - - public interface WithdrawalListener { - void onWithdrawalComplete(Model.Withdraw.Response response); - } - - public interface ErrorListener { - void onError(Throwable throwable); - } - - public void setBalanceListener(BalanceListener listener) { - balanceListener = listener; - } - - public void setWithdrawalListener(WithdrawalListener listener) { - withdrawalListener = listener; - } - - public void setWithdrawalInfoListener(WithdrawalInfoListener listener) { - withdrawalInfoListener = listener; - } - - public void setErrorListener(ErrorListener listener) { - errorListener = listener; - } - - - private static final String HMAC_SHA512 = "HmacSHA512"; - - - private String bodyToString(final RequestBody request){ - try { - final RequestBody copy = request; - final Buffer buffer = new Buffer(); - if(copy != null) - copy.writeTo(buffer); - else - return ""; - return buffer.readUtf8(); - } - catch (final IOException e) { - return "did not work"; - } - } - - private String calcSignature(String url, RequestBody requestBody) throws NoSuchAlgorithmException, InvalidKeyException, IOException { - - String postData=bodyToString(requestBody); - - // create SHA-256 hash of the nonce and the POST data - - String s=nonce+postData; - byte[] sha256 = Util.calculateSHA256(s); - - // set the API method and retrieve the path - byte[] path = url.getBytes(StandardCharsets.UTF_8); - - // decode the API secret, it's the HMAC key - byte[] hmacKey = Base64.decode(secret); - - // create the HMAC message from the path and the previous hash - ByteArrayOutputStream outputStream=new ByteArrayOutputStream(); - outputStream.write(path); - outputStream.write(sha256); - byte[] hmacMessage = outputStream.toByteArray();//concatArrays(path, sha256); - - Mac mac = Mac.getInstance(HMAC_SHA512); - mac.init(new SecretKeySpec(hmacKey, HMAC_SHA512)); - byte[] hmacSignature=mac.doFinal(hmacMessage); - - byte[] b64Signature = Base64.encode(hmacSignature); - - return new String(b64Signature, StandardCharsets.UTF_8); - } - - @SuppressLint("CheckResult") - public void requestBalance() throws Exception { - - initApi(); - -// Log.e("kraken", "key: " + key); -// Log.e("kraken", "secret: " + secret); -// Log.e("kraken", "nonce: " + nonce); - - api.getBalance(nonce.toString()) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(response -> balanceListener.onBalanceData(response), - // handle error - this::FireError - ); - } - - @SuppressLint("CheckResult") - public void requestWithdrawInfo(String currency, String amount, String withdrawKey) throws Exception { - - initApi(); - -// Log.e("kraken", "key: " + key); -// Log.e("kraken", "secret: " + secret); -// Log.e("kraken", "nonce: " + nonce); - - String asset=CurrencyToAsset(currency); - -// withdrawKey = "test"; - api.WithdrawInfo(nonce.toString(), asset, withdrawKey, amount) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(response -> withdrawalInfoListener.onWithdrawalInfoComplete(response), - // handle error - this::FireError - ); - } - - private static String CurrencyToAsset(String currency) throws Exception { - switch (currency) - { - case "BTC": return "XXBT"; - case "BCH": return "BCH"; - case "ETH": return "XETH"; - default: - throw new Exception("Unsupported currency!"); - } - } - - @SuppressLint("CheckResult") - public void requestWithdraw(String currency, String amount, String withdrawKey) throws Exception { - initApi(); - -// Log.e("kraken", "key: " + key); -// Log.e("kraken", "secret: " + secret); -// Log.e("kraken", "nonce: " + nonce); - - String asset=CurrencyToAsset(currency); - -// withdrawKey = "test"; - api.Withdraw(nonce.toString(), asset, withdrawKey, amount) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(response -> withdrawalListener.onWithdrawalComplete(response), - // handle error - this::FireError - ); - } - - private void FireError(Throwable e) throws IOException { -// if (e.getClass() == HttpException.class && ((HttpException) e).code() == 500) { -// JsonObject jsonObject = new JsonParser().parse(((HttpException) e).response().errorBody().string()).getAsJsonObject(); -// errorListener.onError(new Exception(e.getMessage() + ": " + jsonObject.get("reason").getAsString())); -// if (jsonObject.get("reason").getAsString().equals("Invalid nonce")) nonce += 1000; -// } else { - errorListener.onError(e); -// } - } - - private void initApi() { - - if (api != null) return; - -// HttpLoggingInterceptor logging = new HttpLoggingInterceptor(); -// logging.setLevel(HttpLoggingInterceptor.Level.BODY); - - OkHttpClient httpClient = new OkHttpClient.Builder(). - addInterceptor(new AuthorizationInterceptor()). -// addInterceptor(logging). - build(); - - Retrofit retrofit = new Retrofit.Builder() - .baseUrl(SERVER_URL) - .addConverterFactory(GsonConverterFactory.create()) - .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) - .client(httpClient) - .build(); - - api = retrofit.create(Api.class); - } - - public class AuthorizationInterceptor implements Interceptor { - - AuthorizationInterceptor() { - } - - - @Override - public Response intercept(Chain chain) throws IOException { - Request mainRequest=chain.request(); - - try { - mainRequest = mainRequest.newBuilder(). - addHeader("API-Key", key). - addHeader("API-Sign", calcSignature(mainRequest.url().toString().substring(SERVER_URL.length()), mainRequest.body())). - build(); - incNonce(); - } catch (Exception e) { - e.printStackTrace(); - throw new IOException("Can't calculate signature: "+e.getMessage()); - } - - return chain.proceed(mainRequest); - - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/data/network/MaticApi.java b/app/src/main/java/com/tangem/data/network/MaticApi.java deleted file mode 100644 index de29ef8178..0000000000 --- a/app/src/main/java/com/tangem/data/network/MaticApi.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.tangem.data.network; - -import com.tangem.data.network.model.InfuraBody; -import com.tangem.data.network.model.InfuraResponse; - -import retrofit2.Call; -import retrofit2.http.Body; -import retrofit2.http.Headers; -import retrofit2.http.POST; - -public interface MaticApi { - @Headers("Content-Type: application/json") - @POST(Server.ApiMaticTesnet.Method.MAIN) - Call matic(@Body InfuraBody body); -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/data/network/PayIdApi.java b/app/src/main/java/com/tangem/data/network/PayIdApi.java deleted file mode 100644 index 4616c01240..0000000000 --- a/app/src/main/java/com/tangem/data/network/PayIdApi.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.tangem.data.network; - -import com.tangem.data.network.model.PayIdResponse; - -import io.reactivex.Single; -import retrofit2.http.GET; -import retrofit2.http.Header; -import retrofit2.http.Headers; -import retrofit2.http.Path; - -public interface PayIdApi { - @Headers("PayID-Version: 1.0") - @GET("{user}") - Single getAddress(@Path("user") String user, @Header("Accept") String acceptHeader); -} diff --git a/app/src/main/java/com/tangem/data/network/RippleApi.java b/app/src/main/java/com/tangem/data/network/RippleApi.java deleted file mode 100644 index 2b6ad9fe29..0000000000 --- a/app/src/main/java/com/tangem/data/network/RippleApi.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.tangem.data.network; - -import com.tangem.data.network.model.RippleBody; -import com.tangem.data.network.model.RippleResponse; - -import retrofit2.Call; -import retrofit2.http.Body; -import retrofit2.http.Headers; -import retrofit2.http.POST; - -public interface RippleApi { - @Headers("Content-Type: application/json") - @POST("./") - Call ripple(@Body RippleBody body); -} diff --git a/app/src/main/java/com/tangem/data/network/RootstockApi.java b/app/src/main/java/com/tangem/data/network/RootstockApi.java deleted file mode 100644 index 7faadfb4c0..0000000000 --- a/app/src/main/java/com/tangem/data/network/RootstockApi.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.tangem.data.network; - -import com.tangem.data.network.model.InfuraBody; -import com.tangem.data.network.model.InfuraResponse; - -import retrofit2.Call; -import retrofit2.http.Body; -import retrofit2.http.Headers; -import retrofit2.http.POST; - -public interface RootstockApi { - @Headers("Content-Type: application/json") - @POST(Server.ApiRootstock.Method.MAIN) - Call rootstock(@Body InfuraBody body); -} diff --git a/app/src/main/java/com/tangem/data/network/Server.java b/app/src/main/java/com/tangem/data/network/Server.java deleted file mode 100644 index 8ef83ebd52..0000000000 --- a/app/src/main/java/com/tangem/data/network/Server.java +++ /dev/null @@ -1,170 +0,0 @@ -package com.tangem.data.network; - -public class Server { - - public static class ApiUpdateVersion { - public static final String URL_UPDATE_VERSION = ServerURL.API_UPDATE_VERSION; - - public static class Method { - static final String LAST_VERSION = URL_UPDATE_VERSION + "TangemCash/tangem-binaries/master/apk-version.txt"; - } - } - - /** - * https://coinmarketcap.com/api/ - */ - public static class ApiCoinmarket { - public static final String URL_COINMARKET = ServerURL.API_COINMARKETCAP; - - public static class Method { - static final String PRICE_CONVERSION = URL_COINMARKET + "v1/tools/price-conversion"; - } - } - - /** - * https://infura.io/ - */ - public static class ApiInfura { - public static final String URL_INFURA = ServerURL.API_INFURA; - - public static class Method { - public static final String MAIN = "v3/613a0b14833145968b1f656240c7d245"; - } - } - - public static class ApiInfuraTestnet { - public static final String URL_INFURA_TESTNET = ServerURL.API_INFURA_TESTNET; - - public static class Method { - public static final String MAIN = "v3/613a0b14833145968b1f656240c7d245"; - } - } - - public static class ApiInfuraRopsten { - public static final String URL_INFURA_ROPSTEN = ServerURL.API_INFURA_ROPSTEN; - - public static class Method { - public static final String MAIN = "v3/613a0b14833145968b1f656240c7d245"; - } - } - - - public static class ApiSoChain { - public static final String URL = ServerURL.API_SOCHAIN_V2; - - public static class Method { - public static final String ADDRESS_BALANCE = "api/v2/get_address_balance/{network}/{address}"; - public static final String UNSPENT_TX = "api/v2/get_tx_unspent/{network}/{address}"; - public static final String GET_TX = "api/v2/get_tx/{network}/{txid}"; - public static final String SEND_TRANSACTION = "api/v2/send_tx/{network}"; - } - } - - /** - * https://public-node.rsk.co/ - */ - public static class ApiRootstock { - public static final String URL_ROOTSTOCK = ServerURL.API_ROOTSTOCK; - - public static class Method { - static final String MAIN = URL_ROOTSTOCK; - } - } - - /** - * https://testnet2.matic.network - */ - public static class ApiMaticTesnet { - public static final String URL_MATIC_TESTNET = ServerURL.API_MATIC_TESTNET ; - - public static class Method { - static final String MAIN = URL_MATIC_TESTNET; - } - } - - /** - * https://estimatefee.com/ - */ - public static class ApiEstimatefee { - public static final String URL_ESTIMATEFEE = ServerURL.API_ESTIMATEFEE; - - public static class Method { - static final String N_2 = URL_ESTIMATEFEE + "n/2"; - static final String N_3 = URL_ESTIMATEFEE + "n/3"; - static final String N_6 = URL_ESTIMATEFEE + "n/6"; - } - } - - /** - * https://dex.binance.org/ - */ - - public static class ApiBinance { - public static final String URL_BINANCE = ServerURL.API_BINANCE; - - public static class Method { - public static final String API_V1 = URL_BINANCE + "api/v1/"; - } - } - - /** - * https://testnet-dex.binance.org/ - */ - - public static class ApiBinanceTestnet { - public static final String URL_BINANCE_TESTNET = ServerURL.API_BINANCE_TESTNET; - - public static class Method { - public static final String API_V1 = URL_BINANCE_TESTNET + "api/v1/"; - } - } - - /** - * https://api.blockcypher.com/ - */ - - public static class ApiBlockcypher { - public static final String URL_BLOCKCYPHER = ServerURL.API_BLOCKCYPHER; - static final String V1_MAIN = "v1/{blockchain}/{network}"; - - public static class Method { - static final String MAIN = URL_BLOCKCYPHER + V1_MAIN; - static final String ADDRESS = MAIN + "/addrs/{address}?includeScript=true&limit=2000"; - static final String TXS = MAIN + "/txs/{txHash}?includeHex=true"; - static final String PUSH = MAIN + "/txs/push"; - } - } - - public static class ApiBlockchainInfo { - public static final String URL_BLOCKCHAININFO = ServerURL.API_BLOCKCHAIN_INFO; - - public static class Method { - static final String ADDRESS = URL_BLOCKCHAININFO + "rawaddr/{address}"; - static final String UTXO = URL_BLOCKCHAININFO + "unspent"; -// static final String TX = URL_BLOCKCHAININFO + "rawtx/{txHash}"; - static final String PUSH = URL_BLOCKCHAININFO + "pushtx"; - } - } - - public static class ApiDucatus { - public static final String URL_DUCATUS = ServerURL.API_DUCATUS + "api/DUC/mainnet/"; - - public static class Method { - static final String BALANCE = URL_DUCATUS + "address/{address}/balance"; - static final String UTXO = URL_DUCATUS + "address/{address}/?unspent=true"; - static final String SEND = URL_DUCATUS + "tx/send"; - } - } - - public static class ApiBlockchair { - public static final String URL_BLOCKCHAIR = ServerURL.API_BLOCKCHAIR + "{blockchain}/"; - private static final String API_KEY = "?key=A___0Shpsu4KagE7oSabrw20DfXAqWlT"; - - public static class Method { - static final String ADDRESS = URL_BLOCKCHAIR + "dashboards/address/{address}" + API_KEY; - static final String TRANSACTION = URL_BLOCKCHAIR + "dashboards/transaction/{transaction}" + API_KEY; - static final String STATS = URL_BLOCKCHAIR + "stats" + API_KEY; - static final String PUSH = URL_BLOCKCHAIR + "push/transaction" + API_KEY; - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/data/network/ServerApiAdalite.java b/app/src/main/java/com/tangem/data/network/ServerApiAdalite.java deleted file mode 100644 index d36bcf2caa..0000000000 --- a/app/src/main/java/com/tangem/data/network/ServerApiAdalite.java +++ /dev/null @@ -1,196 +0,0 @@ -package com.tangem.data.network; - -import android.util.Log; - -import androidx.annotation.NonNull; - -import com.tangem.data.network.model.AdaliteBody; -import com.tangem.data.network.model.AdaliteResponse; -import com.tangem.data.network.model.AdaliteResponseUtxo; - -import java.util.List; - -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; -import retrofit2.Retrofit; -import retrofit2.converter.gson.GsonConverterFactory; -import retrofit2.converter.scalars.ScalarsConverterFactory; - -public class ServerApiAdalite { - private static String TAG = ServerApiAdalite.class.getSimpleName(); - - public static final String ADALITE_ADDRESS = "/api/addresses/summary/{address}"; - public static final String ADALITE_UNSPENT_OUTPUTS = "/api/bulk/addresses/utxo"; - // public static final String ADALITE_TRANSACTION = "/api/txs/raw/{txId}}"; - public static final String ADALITE_SEND = "/api/v2/txs/signed"; - - private int requestsCount = 0; - - private final String adaliteURL1 = "https://explorer2.adalite.io"; //TODO: make random selection, add more?, move - private final String adaliteURL2 = "https://nodes.southeastasia.cloudapp.azure.com"; - - private String currentURL = adaliteURL1; - - public String getCurrentURL() { - return currentURL; - } - - public boolean isRequestsSequenceCompleted() { - Log.i(TAG, String.format("isRequestsSequenceCompleted: %s (%d requests left)", String.valueOf(requestsCount <= 0), requestsCount)); - return requestsCount <= 0; - } - - private ResponseListener responseListener; - - public interface ResponseListener { - void onSuccess(String method, AdaliteResponse adaliteResponse); - - void onSuccess(String method, AdaliteResponseUtxo adaliteResponseUtxo); - - void onSuccess(String method, String stringResponse); - - void onFail(String method, String message); - } - - public void setResponseListener(ResponseListener listener) { - responseListener = listener; - } - - public void requestData(String method, String wallet, String tx) { - requestData(method, wallet, tx, false); - } - - public void requestData(String method, String wallet, String tx, boolean isRetry) { - requestsCount++; - - Retrofit retrofitAdalite = new Retrofit.Builder() - .baseUrl(currentURL) - .addConverterFactory(ScalarsConverterFactory.create()) - .addConverterFactory(GsonConverterFactory.create()) - .build(); - - AdaliteApi adaliteApi = retrofitAdalite.create(AdaliteApi.class); - - switch (method) { - case ADALITE_ADDRESS: - - Call addressCall = adaliteApi.adaliteAddress(wallet); - addressCall.enqueue(new Callback() { - @Override - public void onResponse(@NonNull Call call, @NonNull Response response) { - requestsCount--; - - if (response.code() == 200) { - responseListener.onSuccess(method, response.body()); - Log.i(TAG, "requestData " + method + " onResponse " + response.code()); - } else { - Log.e(TAG, "requestData " + method + " onResponse " + response.code()); - - if (!isRetry) { - retryRequest(method, wallet, tx); - } else { - responseListener.onFail(method, String.valueOf(response.code())); - } - } - } - - @Override - public void onFailure(@NonNull Call call, @NonNull Throwable t) { - Log.e(TAG, "requestData " + method + " onFailure " + t.getMessage()); - requestsCount--; - - if (!isRetry) { - retryRequest(method, wallet, tx); - } else { - responseListener.onFail(method, String.valueOf(t.getMessage())); - } - } - }); - break; - - case ADALITE_UNSPENT_OUTPUTS: - Call outputsCall = adaliteApi.adaliteUnspent("[\"" + wallet + "\"]"); - outputsCall.enqueue(new Callback() { - @Override - public void onResponse(@NonNull Call call, @NonNull Response response) { - requestsCount--; - - if (response.code() == 200) { - responseListener.onSuccess(method, response.body()); - Log.i(TAG, "requestData " + method + " onResponse " + response.code()); - } else { - Log.e(TAG, "requestData " + method + " onResponse " + response.code()); - - if (!isRetry) { - retryRequest(method, wallet, tx); - } else { - responseListener.onFail(method, String.valueOf(response.code())); - } - } - } - - @Override - public void onFailure(@NonNull Call call, @NonNull Throwable t) { - Log.e(TAG, "requestData " + method + " onFailure " + t.getMessage()); - requestsCount--; - - if (!isRetry) { - retryRequest(method, wallet, tx); - } else { - responseListener.onFail(method, String.valueOf(t.getMessage())); - } - } - }); - break; - - case ADALITE_SEND: - Call sendCall = adaliteApi.adaliteSend(new AdaliteBody(tx)); - sendCall.enqueue(new Callback() { - @Override - public void onResponse(@NonNull Call call, @NonNull Response response) { - requestsCount--; - - if (response.code() == 200) { - responseListener.onSuccess(method, response.body()); - Log.i(TAG, "requestData " + method + " onResponse " + response.code()); - } else { - Log.e(TAG, "requestData " + method + " onResponse " + response.code()); - - if (!isRetry) { - retryRequest(method, wallet, tx); - } else { - responseListener.onFail(method, String.valueOf(response.code())); - } - } - } - - @Override - public void onFailure(@NonNull Call call, @NonNull Throwable t) { - Log.e(TAG, "requestData " + method + " onFailure " + t.getMessage()); - requestsCount--; - - if (!isRetry) { - retryRequest(method, wallet, tx); - } else { - responseListener.onFail(method, String.valueOf(t.getMessage())); - } - } - }); - break; - - default: - requestsCount--; - responseListener.onFail(method, "undeclared method"); - Log.e(TAG, "requestData " + method + " onFailure - undeclared method"); - break; - } - - } - - private void retryRequest(String method, String wallet, String tx) { -// currentURL = adaliteURL2; - requestData(method, wallet, tx, true); - } -} - diff --git a/app/src/main/java/com/tangem/data/network/ServerApiBinance.java b/app/src/main/java/com/tangem/data/network/ServerApiBinance.java deleted file mode 100644 index 8527a9c0d4..0000000000 --- a/app/src/main/java/com/tangem/data/network/ServerApiBinance.java +++ /dev/null @@ -1,137 +0,0 @@ -package com.tangem.data.network; - -import android.util.Log; - -import com.tangem.wallet.TangemContext; -import com.tangem.wallet.Transaction; -import com.tangem.wallet.binance.BinanceAssetData; -import com.tangem.wallet.binance.BinanceData; -import com.tangem.wallet.binance.client.BinanceDexApiRestClient; -import com.tangem.wallet.binance.client.domain.Account; -import com.tangem.wallet.binance.client.domain.Balance; -import com.tangem.wallet.binance.client.domain.TransactionMetadata; -import com.tangem.wallet.binance.client.encoding.message.TransactionRequestAssemblerExtSign; - -import java.util.ArrayList; -import java.util.List; - -import io.reactivex.Observable; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.observers.DefaultObserver; -import io.reactivex.schedulers.Schedulers; -import okhttp3.RequestBody; - -public class ServerApiBinance { - private static String TAG = ServerApiBinance.class.getSimpleName(); - - private ResponseListener responseListener; - - public interface ResponseListener { - void onSuccess(); - void onFail(); - } - - public void setResponseListener(ResponseListener listener) { - responseListener = listener; - } - - public void getBalance(TangemContext ctx, BinanceDexApiRestClient client) { - Log.i(TAG, "new getBalance request"); - - Observable balanceObservable = Observable.just(new Account()) - .map(account -> client.getAccount(ctx.getCoinData().getWallet())) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()); - - balanceObservable.subscribe(new DefaultObserver() { - @Override - public void onNext(Account account) { - Log.i(TAG, "getBalance onNext"); -// account = client.getAccount(ctx.getCoinData().getWallet()); - BinanceData binanceData = (BinanceData) ctx.getCoinData(); - - for (Balance balance : account.getBalances()) { - if (balance.getSymbol().equals("BNB")) { - binanceData.setBalanceReceived(true); - binanceData.setBalance(balance.getFree()); - break; - } - } - - if (binanceData instanceof BinanceAssetData) { - BinanceAssetData binanceAssetData = (BinanceAssetData) binanceData; - for (Balance balance : account.getBalances()) { - if (balance.getSymbol().equals(ctx.getCard().getContractAddress())) { - binanceAssetData.setAssetBalance(balance.getFree()); - break; - } - } - } - - if (!binanceData.isBalanceReceived()) { - binanceData.setBalanceReceived(true); - binanceData.setBalance("0"); - } - - binanceData.setAccountNumber(account.getAccountNumber()); - binanceData.setSequence(account.getSequence()); - } - - @Override - public void onError(Throwable e) { - Log.e(TAG, "getBalance onError" + e.getMessage()); - if (e.getMessage().contains("account not found")) { - ((BinanceData)ctx.getCoinData()).setError404(true); - responseListener.onFail(); - } else { - e.printStackTrace(); - ctx.setError(e.getMessage()); - responseListener.onFail(); - } - } - - @Override - public void onComplete() { - Log.i(TAG, "getBalance onComplete"); - responseListener.onSuccess(); - } - }); - } - - public void sendTransaction (byte[] txForSend, BinanceDexApiRestClient client) { - Log.i(TAG, "new sendTransaction request"); - - RequestBody requestBody = TransactionRequestAssemblerExtSign.createRequestBody(txForSend); - - Observable> sendObservable = Observable.just(new ArrayList<>()) - .map(metadatas -> client.broadcastNoWallet(requestBody, true)) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()); - - sendObservable.subscribe(new DefaultObserver>() { - @Override - public void onNext(List metadatas ) { -// RequestBody requestBody = TransactionRequestAssemblerExtSign.createRequestBody(txForSend); -// List metadatas = client.broadcastNoWallet(requestBody, true); - if (!metadatas.isEmpty() && metadatas.get(0).isOk()) { - responseListener.onSuccess(); - } else { - Log.e(TAG, "Transaction send error"); - responseListener.onFail(); - } - } - - @Override - public void onError(Throwable e) { - Log.e(TAG, "sendTransaction onError" + e.getMessage()); - e.printStackTrace(); - responseListener.onFail(); - } - - @Override - public void onComplete() { - Log.i(TAG, "sendTransaction onComplete"); - } - }); - } -} diff --git a/app/src/main/java/com/tangem/data/network/ServerApiBitcore.java b/app/src/main/java/com/tangem/data/network/ServerApiBitcore.java deleted file mode 100644 index de9de087eb..0000000000 --- a/app/src/main/java/com/tangem/data/network/ServerApiBitcore.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.tangem.data.network; - -import com.tangem.App; -import com.tangem.data.network.model.BitcoreBalance; -import com.tangem.data.network.model.BitcoreBalanceAndUnspents; -import com.tangem.data.network.model.BitcoreSendBody; -import com.tangem.data.network.model.BitcoreSendResponse; -import com.tangem.data.network.model.BitcoreUtxo; -import com.tangem.tangem_card.util.Log; - -import java.util.ArrayList; -import java.util.List; - -import io.reactivex.Single; -import io.reactivex.SingleObserver; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.schedulers.Schedulers; - -public class ServerApiBitcore { - private static String TAG = ServerApiBitcore.class.getSimpleName(); - - public void getBalanceAndUnspents(String wallet, SingleObserver balanceAndUnspentsObserver) { - Log.i(TAG, "new getAddressAndUnspents request"); - DucatusApi api = App.Companion.getNetworkComponent().getRetrofitDucatus().create(DucatusApi.class); - - Single balanceObservable = api.ducatusBalance(wallet); - - Single> unspentsObservable = api.ducatusUnspents(wallet) - .onErrorReturnItem(new ArrayList<>()); - - Single.zip(balanceObservable, unspentsObservable, BitcoreBalanceAndUnspents::new) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(balanceAndUnspentsObserver); - } - - public void sendTransaction(String tx, SingleObserver sendObserver) { - Log.i(TAG, "new getAddress request"); - DucatusApi api = App.Companion.getNetworkComponent().getRetrofitDucatus().create(DucatusApi.class); - - Single sendObservable = api.ducatusSend(new BitcoreSendBody(tx)) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()); - - sendObservable.subscribe(sendObserver); - } -} diff --git a/app/src/main/java/com/tangem/data/network/ServerApiBlockchainInfo.java b/app/src/main/java/com/tangem/data/network/ServerApiBlockchainInfo.java deleted file mode 100644 index a16f25ddfc..0000000000 --- a/app/src/main/java/com/tangem/data/network/ServerApiBlockchainInfo.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.tangem.data.network; - -import android.util.Log; - -import com.tangem.App; -import com.tangem.data.network.model.BlockchainInfoAddress; -import com.tangem.data.network.model.BlockchainInfoAddressAndUnspents; -import com.tangem.data.network.model.BlockchainInfoTransaction; -import com.tangem.data.network.model.BlockchainInfoUnspents; - -import java.util.ArrayList; -import java.util.List; - -import io.reactivex.Single; -import io.reactivex.SingleObserver; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.schedulers.Schedulers; -import okhttp3.ResponseBody; - -public class ServerApiBlockchainInfo { - private static String TAG = ServerApiBlockchainInfo.class.getSimpleName(); - private int page = 1; - - public void getAddressAndUnspents(String wallet, SingleObserver addressAndUnspentsObserver) { - Log.i(TAG, "new getAddressAndUnspents request"); - BlockchainInfoApi api = App.Companion.getNetworkComponent().getRetrofitBlockchainInfo().create(BlockchainInfoApi.class); - - Single addressObservable = api.blockchainInfoAddress(wallet, null); - - Single unspentsObservable = api.blockchainInfoUnspents(wallet) - .onErrorReturnItem(new BlockchainInfoUnspents(new ArrayList<>())); - - Single.zip(addressObservable, unspentsObservable, BlockchainInfoAddressAndUnspents::new) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(addressAndUnspentsObserver); - } - - public void sendTransaction(String tx, SingleObserver sendObserver) { - Log.i(TAG, "new getAddress request"); - BlockchainInfoApi api = App.Companion.getNetworkComponent().getRetrofitBlockchainInfo().create(BlockchainInfoApi.class); - - Single sendObservable = api.blockchainInfoPush(tx) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()); - - sendObservable.subscribe(sendObserver); - } - - public Single> getMoreAddressTxs(String wallet) { - Log.i(TAG, "new getAddress request"); - BlockchainInfoApi api = App.Companion.getNetworkComponent().getRetrofitBlockchainInfo().create(BlockchainInfoApi.class); - - Single> addressObservable = api.blockchainInfoAddress(wallet, page * 50) - .map(BlockchainInfoAddress::getTxs) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()); - - page++; - return addressObservable; - } -} diff --git a/app/src/main/java/com/tangem/data/network/ServerApiBlockchair.java b/app/src/main/java/com/tangem/data/network/ServerApiBlockchair.java deleted file mode 100644 index f77bb83449..0000000000 --- a/app/src/main/java/com/tangem/data/network/ServerApiBlockchair.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.tangem.data.network; - -import com.tangem.App; -import com.tangem.data.Blockchain; -import com.tangem.data.network.model.BlockchairAddressResponse; -import com.tangem.data.network.model.BlockchairSendBody; -import com.tangem.data.network.model.BlockchairStatsResponse; -import com.tangem.data.network.model.BlockchairTransactionResponse; -import com.tangem.tangem_card.util.Log; - -import io.reactivex.Completable; -import io.reactivex.CompletableObserver; -import io.reactivex.Single; -import io.reactivex.SingleObserver; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.schedulers.Schedulers; - -public class ServerApiBlockchair { - private static String TAG = ServerApiBlockchair.class.getSimpleName(); - private String blockchain; - - public ServerApiBlockchair(Blockchain blockchain) { - if (blockchain == Blockchain.BitcoinCash) this.blockchain = "bitcoin-cash"; - } - - public void getAddress(String wallet, SingleObserver addressObserver) { - Log.i(TAG, "new getAddress request"); - BlockchairApi api = App.Companion.getNetworkComponent().getRetrofitBlockchair().create(BlockchairApi.class); - - Single addressSingle = api.getAddress(blockchain, wallet) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()); - - addressSingle.subscribe(addressObserver); - } - - public void getTransaction(String transaction, SingleObserver transactionObserver) { - Log.i(TAG, "new getAddress request"); - BlockchairApi api = App.Companion.getNetworkComponent().getRetrofitBlockchair().create(BlockchairApi.class); - - Single transactionSingle = api.getTransaction(blockchain, transaction) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()); - - transactionSingle.subscribe(transactionObserver); - } - - public void getStats(SingleObserver statsObserver) { - Log.i(TAG, "new getStats request"); - BlockchairApi api = App.Companion.getNetworkComponent().getRetrofitBlockchair().create(BlockchairApi.class); - - Single statsSingle = api.getStats(blockchain) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()); - - statsSingle.subscribe(statsObserver); - } - - public void sendTransaction(String tx, CompletableObserver sendObserver) { - Log.i(TAG, "new getAddress request"); - BlockchairApi api = App.Companion.getNetworkComponent().getRetrofitDucatus().create(BlockchairApi.class); - - Completable sendCompletable = api.sendTransaction(blockchain, new BlockchairSendBody(tx)) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()); - - sendCompletable.subscribe(sendObserver); - } - - public String getUrl() { - return ServerURL.API_BLOCKCHAIR; - } -} diff --git a/app/src/main/java/com/tangem/data/network/ServerApiBlockcypher.java b/app/src/main/java/com/tangem/data/network/ServerApiBlockcypher.java deleted file mode 100644 index bccbfd5a1e..0000000000 --- a/app/src/main/java/com/tangem/data/network/ServerApiBlockcypher.java +++ /dev/null @@ -1,212 +0,0 @@ -package com.tangem.data.network; - -import android.util.Log; - -import androidx.annotation.NonNull; - -import com.tangem.App; -import com.tangem.data.Blockchain; -import com.tangem.data.network.model.BlockcypherBody; -import com.tangem.data.network.model.BlockcypherFee; -import com.tangem.data.network.model.BlockcypherResponse; -import com.tangem.data.network.model.BlockcypherTx; - -import java.util.Random; - -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; - -public class ServerApiBlockcypher { - private static String TAG = ServerApiBlockcypher.class.getSimpleName(); - - public static final String BLOCKCYPHER_ADDRESS = "blockcypher_address"; - public static final String BLOCKCYPHER_FEE = "blockcypher_fee"; - public static final String BLOCKCYPHER_TXS = "blockcypher_txs"; - public static final String BLOCKCYPHER_SEND = "blockcypher_send"; - - private int requestsCount = 0; - - public boolean isRequestsSequenceCompleted() { - Log.i(TAG, String.format("isRequestsSequenceCompleted: %s (%d requests left)", String.valueOf(requestsCount <= 0), requestsCount)); - return requestsCount <= 0; - } - - private String apiKey = null; - - private ResponseListener responseListener; - private TxResponseListener txResponseListener; - - public interface ResponseListener { - void onSuccess(String method, BlockcypherResponse blockcypherResponse); - - void onSuccess(String method, BlockcypherFee blockcypherFee); - - void onFail(String method, String message); - } - - public interface TxResponseListener { - void onSuccess(BlockcypherTx blockcypherTx); - - void onFail(String message); - } - - public void setResponseListener(ResponseListener listener) { - responseListener = listener; - } - - public void setTxResponseListener(TxResponseListener txListener) { - txResponseListener = txListener; - } - - public void requestData(String blockchainID, String method, String wallet, String tx) { - requestsCount++; - String blockchain = blockchainID.toLowerCase(); - BlockcypherApi blockcypherApi = App.Companion.getNetworkComponent().getRetrofitBlockcypher().create(BlockcypherApi.class); - - String network = "main"; - if (blockchainID.equals(Blockchain.BitcoinTestNet.getID())) { - blockchain = "btc"; - network = "test3"; - } - if (blockchainID.equals(Blockchain.Token.getID())) blockchain = "eth"; - if (blockchainID.equals(Blockchain.BitcoinDual.getID())) blockchain = "btc"; - - switch (method) { - case BLOCKCYPHER_ADDRESS: - Call addressCall = blockcypherApi.blockcypherAddress(blockchain, network, wallet, apiKey); - addressCall.enqueue(new Callback() { - @Override - public void onResponse(@NonNull Call call, @NonNull Response response) { - requestsCount--; - switch (response.code()) { - case 200: - responseListener.onSuccess(method, response.body()); - Log.i(TAG, "requestData " + method + " onResponse " + response.code()); - break; - case 429: - apiKey = getRandomApiKey(); - requestData(blockchainID, method, wallet, tx); - break; - default: - responseListener.onFail(method, String.valueOf(response.code())); - Log.e(TAG, "requestData " + method + " onResponse " + response.code()); - break; - } - } - - @Override - public void onFailure(@NonNull Call call, @NonNull Throwable t) { - requestsCount--; - responseListener.onFail(method, String.valueOf(t.getMessage())); - Log.e(TAG, "requestData " + method + " onFailure " + t.getMessage()); - } - }); - break; - - case BLOCKCYPHER_FEE: - Call feeCall = blockcypherApi.blockcypherMain(blockchain, network, apiKey); - feeCall.enqueue(new Callback() { - @Override - public void onResponse(@NonNull Call call, @NonNull Response response) { - requestsCount--; - switch (response.code()) { - case 200: - responseListener.onSuccess(method, response.body()); - Log.i(TAG, "requestData " + method + " onResponse " + response.code()); - break; - case 429: - apiKey = getRandomApiKey(); - requestData(blockchainID, method, wallet, tx); - break; - default: - responseListener.onFail(method, String.valueOf(response.code())); - Log.e(TAG, "requestData " + method + " onResponse " + response.code()); - break; - } - } - - @Override - public void onFailure(@NonNull Call call, @NonNull Throwable t) { - requestsCount--; - responseListener.onFail(method, String.valueOf(t.getMessage())); - Log.e(TAG, "requestData " + method + " onFailure " + t.getMessage()); - } - }); - break; - - case BLOCKCYPHER_TXS: - Call txsCall = blockcypherApi.blockcypherTxs(blockchain, network, tx, apiKey); - txsCall.enqueue(new Callback() { - @Override - public void onResponse(@NonNull Call call,@NonNull Response response) { - requestsCount--; - switch (response.code()) { - case 200: - txResponseListener.onSuccess(response.body()); - Log.i(TAG, "requestData " + method + " onResponse " + response.code()); - break; - case 429: - apiKey = getRandomApiKey(); - requestData(blockchainID, method, wallet, tx); - break; - default: - txResponseListener.onFail(String.valueOf(response.code())); - Log.e(TAG, "requestData " + method + " onResponse " + response.code()); - break; - } - } - - @Override - public void onFailure(@NonNull Call call, @NonNull Throwable t) { - requestsCount--; - txResponseListener.onFail(String.valueOf(t.getMessage())); - Log.e(TAG, "requestData " + method + " onFailure " + t.getMessage()); - } - }); - break; - - case BLOCKCYPHER_SEND: - Call sendCall = blockcypherApi.blockcypherPush(blockchain, network, new BlockcypherBody(tx), apiKey); - sendCall.enqueue(new Callback() { - @Override - public void onResponse(@NonNull Call call, @NonNull Response response) { - requestsCount--; - switch (response.code()) { - case 201: - responseListener.onSuccess(method, response.body()); - Log.i(TAG, "requestData " + method + " onResponse " + response.code()); - break; - case 429: - apiKey = getRandomApiKey(); - requestData(blockchainID, method, wallet, tx); - break; - default: - responseListener.onFail(method, String.valueOf(response.code())); - Log.e(TAG, "requestData " + method + " onResponse " + response.code()); - break; - } - } - - @Override - public void onFailure(@NonNull Call call, @NonNull Throwable t) { - requestsCount--; - responseListener.onFail(method, String.valueOf(t.getMessage())); - Log.e(TAG, "requestData " + method + " onFailure " + t.getMessage()); - } - }); - break; - - default: - requestsCount--; - responseListener.onFail(method, "undeclared method"); - Log.e(TAG, "requestData " + method + " onFailure - undeclared method"); - break; - } - } - - private String getRandomApiKey() { - return BlockcypherToken - .values()[new Random().nextInt(BlockcypherToken.values().length)].getToken(); - } -} diff --git a/app/src/main/java/com/tangem/data/network/ServerApiCommon.java b/app/src/main/java/com/tangem/data/network/ServerApiCommon.java deleted file mode 100644 index d6c7b315f0..0000000000 --- a/app/src/main/java/com/tangem/data/network/ServerApiCommon.java +++ /dev/null @@ -1,172 +0,0 @@ -package com.tangem.data.network; - -import android.annotation.SuppressLint; -import androidx.annotation.NonNull; -import android.util.Log; - -import com.tangem.App; -import com.tangem.data.network.model.RateInfoResponse; - -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.schedulers.Schedulers; -import okhttp3.ResponseBody; -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; - -public class ServerApiCommon { - private static String TAG = ServerApiCommon.class.getSimpleName(); - - /** - * HTTP - * Estimate fee - */ - public static final int ESTIMATE_FEE_PRIORITY = 2; - public static final int ESTIMATE_FEE_NORMAL = 3; - public static final int ESTIMATE_FEE_MINIMAL = 6; - private EstimatedFeeListener estimatedFeeListener; - - public interface EstimatedFeeListener { - void onSuccess(int blockCount, String estimateFeeResponse); - void onFail(int blockCount, String message); - } - - public void setBtcEstimatedFeeListener(EstimatedFeeListener listener) { - estimatedFeeListener = listener; - } - - public void requestBtcEstimatedFee(int blockCount) { - EstimatefeeApi estimatefeeApi = App.Companion.getNetworkComponent().getRetrofitEstimatefee().create(EstimatefeeApi.class); - - Call call; - switch (blockCount) { - case ESTIMATE_FEE_PRIORITY: - call = estimatefeeApi.getEstimateFeePriority(); - break; - - case ESTIMATE_FEE_NORMAL: - call = estimatefeeApi.getEstimateFeeNormal(); - break; - - case ESTIMATE_FEE_MINIMAL: - call = estimatefeeApi.getEstimateFeeMinimal(); - break; - - default: - call = estimatefeeApi.getEstimateFeeNormal(); - } - - call.enqueue(new Callback() { - @Override - public void onResponse(@NonNull Call call, @NonNull Response response) { - if (response.code() == 200) { - estimatedFeeListener.onSuccess(blockCount, response.body()); - Log.i(TAG, "requestBtcEstimatedFee onResponse " + response.code() + " " + response.body()); - } else - estimatedFeeListener.onFail(blockCount, response.body()); - Log.e(TAG, "requestBtcEstimatedFee onResponse " + response.code()); - } - - @Override - public void onFailure(@NonNull Call call, @NonNull Throwable t) { - estimatedFeeListener.onFail(blockCount, t.getMessage()); - Log.e(TAG, "requestBtcEstimatedFee onFailure " + t.getMessage()); - } - }); - } - - /** - * HTTP - * Used in Crypto-currency course - */ - private RateInfoListener rateInfoListener; - - public interface RateInfoListener { - void onSuccess(RateInfoResponse rateInfoResponse); - - void onFail(String message); - } - - public void setRateInfoListener(RateInfoListener listener) { - rateInfoListener = listener; - } - - public void requestRateInfo(String cryptoId) { - CoinmarketApi coinmarketApi = App.Companion.getNetworkComponent().getRetrofitCoinmarketcap().create(CoinmarketApi.class); - -// Call call = coinmarketApi.getRateInfo(1, cryptoId); -// -// call.enqueue(new Callback() { -// @Override -// public void onResponse(@NonNull Call call, @NonNull Response response) { -// if (response.code() == 200) { -// rateInfoListener.onSuccess(response.body()); -// Log.i(TAG, "coinmarketcap onResponse " + response.code()); -// } else { -// rateInfoListener.onFail("Rate info error:" + String.valueOf(response.code())); -// Log.e(TAG, "coinmarketcap onResponse " + response.code()); -// } -// } -// -// @Override -// public void onFailure(@NonNull Call call, @NonNull Throwable t) { -// rateInfoListener.onFail(String.valueOf(t.getMessage())); -// Log.e(TAG, "coinmarketcap onFailure " + t.getMessage()); -// } -// }); - coinmarketApi.getRateInfo(1, cryptoId) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(rateInfoResponse -> { - if (rateInfoResponse.getData().getQuote().getUsd().getPrice() != null) { - rateInfoListener.onSuccess(rateInfoResponse); - Log.i(TAG, "coinmarketcap onResponse " + 200); - } else { - rateInfoListener.onFail("Rate info error: wrong response"); - } - }, - // handle error - Throwable::printStackTrace); - } - - /** - * HTTP - * Last version request from GitHub - */ - private LastVersionListener lastVersionListener; - - public interface LastVersionListener { - void onSuccess(String lastVersion); - } - - public void setLastVersionListener(LastVersionListener listener) { - lastVersionListener = listener; - } - - public void requestLastVersion() { - UpdateVersionApi updateVersionApi = App.Companion.getNetworkComponent().getRetrofitGitHubUserContent().create(UpdateVersionApi.class); - - Call call = updateVersionApi.getLastVersion(); - call.enqueue(new Callback() { - @Override - public void onResponse(@NonNull Call call, @NonNull Response response) { - Log.i(TAG, "requestLastVersion onResponse " + response.code()); - if (response.code() == 200) { - String stringResponse; - try { - stringResponse = response.body() != null ? response.body().string() : null; - lastVersionListener.onSuccess(stringResponse); - } catch (Exception e) { - e.printStackTrace(); - } - } - } - - @Override - public void onFailure(@NonNull Call call, @NonNull Throwable t) { - Log.e(TAG, "lastVersion onFailure " + t.getMessage()); - } - }); - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/data/network/ServerApiElectrum.java b/app/src/main/java/com/tangem/data/network/ServerApiElectrum.java deleted file mode 100644 index b2e8f91540..0000000000 --- a/app/src/main/java/com/tangem/data/network/ServerApiElectrum.java +++ /dev/null @@ -1,386 +0,0 @@ -package com.tangem.data.network; - -import android.util.Log; - -import com.tangem.App; -import com.tangem.data.Blockchain; -import com.tangem.wallet.R; -import com.tangem.wallet.TangemContext; -import com.tangem.wallet.bch.BitcoinCashNode; -import com.tangem.wallet.btc.BitcoinNode; -import com.tangem.wallet.btc.BitcoinNodeTestNet; -import com.tangem.wallet.ltc.LitecoinNode; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.net.ConnectException; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.Socket; -import java.security.KeyManagementException; -import java.security.NoSuchAlgorithmException; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Random; - -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLSocketFactory; -import javax.net.ssl.TrustManager; -import javax.net.ssl.X509TrustManager; - -import io.reactivex.Observable; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.observers.DefaultObserver; -import io.reactivex.schedulers.Schedulers; - -/** - * Request processor for Electrum Api - * Every request live cycle: - * 1. In application create request and call {@link ServerApiElectrum}.requestData(..) - * 2. Try send every request for max 4 times, - * 3. If all 4 times fail call DefaultObserver.onError (defined in .requestData(..)) and than - * {@link ResponseListener}.onFail(...) callback - * Error can be acquired with {@link ElectrumRequest}.getError() method - * 4. If request network communication finished successfully then call DefaultObserver.onComplete (defined in .requestData) and than - * {@link ResponseListener}.onSuccess(...) callback - */ -public class ServerApiElectrum { - public static final String ERROR_STARTS_WITH_CODE_32601 = "{\"code\":-32601,"; - private static String TAG = ServerApiElectrum.class.getSimpleName(); - - /** - * TCP, SSL - * Used in BTC, BCH - */ - private ResponseListener responseListener; - private String host; - private int port; - - private int requestsCount=0; - - public boolean isRequestsSequenceCompleted() { - Log.i(TAG, String.format("isRequestsSequenceCompleted: %s (%d requests left)", String.valueOf(requestsCount <= 0), requestsCount)); - return requestsCount <= 0; - } - - /** - * Interface for notification every request result - */ - public interface ResponseListener { - - /** - * Notify that request processing was successful - * @param electrumRequest - processed request containing received answer {@see electrumRequest.getAnswer() method} - */ - void onSuccess(ElectrumRequest electrumRequest); - /** - * Notify that request processing was successful - * @param electrumRequest - processed request containing occurred error {@see electrumRequest.getError() method} - */ - void onFail(ElectrumRequest electrumRequest); - } - - /** - * Set notificaion listener - * @param listener - */ - public void setResponseListener(ResponseListener listener) { - responseListener = listener; - } - - - /** - * Start process request - * @param ctx - * @param electrumRequest - */ - public void requestData(TangemContext ctx, ElectrumRequest electrumRequest) { - requestsCount++; - Log.i(TAG, String.format("New request[%d]: %s", requestsCount,electrumRequest.getMethod())); - Observable checkElectrumDataObserver = Observable.just(electrumRequest) - .doOnNext(electrumRequest1 -> doElectrumRequest(ctx, electrumRequest)) - - .flatMap(electrumRequest1 -> { - if (electrumRequest1.answerData == null) { - Log.e(TAG, "NullPointerException " + electrumRequest.getMethod()); - return Observable.error(new NullPointerException()); - } else - return Observable.just(electrumRequest1); - }) - - .retryWhen(errors -> errors - .filter(throwable -> throwable instanceof NullPointerException) - .zipWith(Observable.range(1, 4), (n, i) -> i)) - - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()); - checkElectrumDataObserver.subscribe(new DefaultObserver() { - //TODO remove onNext - @Override - public void onNext(ElectrumRequest v) { - if (electrumRequest.answerData != null) { - Log.i(TAG, "requestData " + electrumRequest.getMethod() + " onNext != null"); - } else { - Log.e(TAG, "requestData " + electrumRequest.getMethod() + " onNext == null"); - } - } - - @Override - public void onError(Throwable e) { - requestsCount--; - Log.e(TAG, "requestData " + electrumRequest.getMethod() + " onError " + e.getMessage()); - Log.e(TAG, String.format("%d requests left in processing",requestsCount)); - electrumRequest.setError(ctx.getString(R.string.loaded_wallet_error_obtaining_blockchain_data)); - //setErrorOccurred(e.getMessage());//; - responseListener.onFail(electrumRequest); - } - - /** - * Called after completion request processing - */ - @Override - public void onComplete() { - requestsCount--; - if (electrumRequest.answerData != null) { - Log.i(TAG, "requestData " + electrumRequest.getMethod() + " onComplete, answerData!=null"); - } else { - Log.e(TAG, "requestData " + electrumRequest.getMethod() + " onComplete, answerData==null"); - } - Log.e(TAG, String.format("%d requests left in processing",requestsCount)); - if (electrumRequest.answerData != null && electrumRequest.getError()==null) { - responseListener.onSuccess(electrumRequest); - } else { -// if( error==null || error.isEmpty() ) setErrorOccurred(ctx.getString(R.string.cannot_obtain_data_from_blockchain)); - responseListener.onFail(electrumRequest); - } - } - - }); - } - - private void doElectrumRequest(TangemContext ctx, ElectrumRequest electrumRequest) { - String host; - int port; - String proto; - electrumRequest.setError(null); - // todo - get available URL list from coinEngine, remove if( ctx.getBlockchain()==...) - if (ctx.getBlockchain() == Blockchain.BitcoinTestNet) { - BitcoinNodeTestNet bitcoinNodeTestNet = BitcoinNodeTestNet.values()[new Random().nextInt(BitcoinNodeTestNet.values().length)]; - host = bitcoinNodeTestNet.getHost(); - port = bitcoinNodeTestNet.getPort(); - - this.host = host; - this.port = port; - - doElectrumRequestTcp(electrumRequest, host, port); - } else if (ctx.getBlockchain() == Blockchain.BitcoinCash) { - BitcoinCashNode bitcoinCashNode = BitcoinCashNode.values()[new Random().nextInt(BitcoinCashNode.values().length)]; - host = bitcoinCashNode.getHost(); - port = bitcoinCashNode.getPort(); - proto = bitcoinCashNode.getProto(); - - this.host = host; - this.port = port; - - if (proto.equals("tcp")) { - doElectrumRequestTcp(electrumRequest, host, port); - } else { - doElectrumRequestSsl(electrumRequest, host, port); - } - - } else if (ctx.getBlockchain() == Blockchain.Bitcoin) { - BitcoinNode bitcoinNode = BitcoinNode.values()[new Random().nextInt(BitcoinNode.values().length)]; - host = bitcoinNode.getHost(); - port = bitcoinNode.getPort(); - proto = bitcoinNode.getProto(); - - this.host = host; - this.port = port; - - if (proto.equals("tcp")) { - doElectrumRequestTcp(electrumRequest, host, port); - } else { - doElectrumRequestSsl(electrumRequest, host, port); - } - } else if (ctx.getBlockchain() == Blockchain.Litecoin) { - LitecoinNode litecoinNode = LitecoinNode.values()[new Random().nextInt(LitecoinNode.values().length)]; - host = litecoinNode.getHost(); - port = litecoinNode.getPort(); - proto = litecoinNode.getProto(); - - this.host = host; - this.port = port; - - if (proto.equals("tcp")) { - doElectrumRequestTcp(electrumRequest, host, port); - } else { - doElectrumRequestSsl(electrumRequest, host, port); - } - } - } - - private void doElectrumRequestTcp(ElectrumRequest electrumRequest, String host, int port) { - try { - Socket socket = App.Companion.getNetworkComponent().getSocket(); - socket.setSoTimeout(3000); - Log.i(TAG, "Start process "+electrumRequest.getMethod()+" @ "+host + ":" + port); - socket.connect(new InetSocketAddress(InetAddress.getByName(host), port)); - try { - OutputStream os = socket.getOutputStream(); - OutputStreamWriter out = new OutputStreamWriter(os, "UTF-8"); - InputStream is = socket.getInputStream(); - BufferedReader in = new BufferedReader(new InputStreamReader(is)); - electrumRequest.setID(1); - - out.write(electrumRequest.getAsString() + "\n"); - out.flush(); - - electrumRequest.answerData = in.readLine(); - electrumRequest.host = host; - electrumRequest.port = port; - if (electrumRequest.answerData != null) { - Log.i(TAG, ">> " + electrumRequest.answerData); - if( (electrumRequest.getError()!=null && electrumRequest.getError().startsWith(ERROR_STARTS_WITH_CODE_32601)) ) - { - // method unknown error??? - electrumRequest.setError(App.Companion.getInstance().getString(R.string.loaded_wallet_error_obtaining_blockchain_data)); - electrumRequest.answerData=null; - } - } else { - electrumRequest.setError(App.Companion.getInstance().getString(R.string.loaded_wallet_error_blockchain_empty_answer)); - Log.i(TAG, ">> "); - } - - } catch (ConnectException e) { - //e.printStackTrace(); - //responseListener.onFail(e.getMessage()); - electrumRequest.setError(App.Companion.getInstance().getString(R.string.loaded_wallet_error_blockchain_connection_refused)); - Log.e(TAG, "doElectrumRequestTcp " + electrumRequest.getMethod() + " ConnectException " + e.getMessage()); - } finally { - Log.i(TAG, "doElectrumRequestTcp " + electrumRequest.getMethod() + " socket.close"); - try { - if( socket.isConnected() ) socket.close(); - } - catch (Exception e) - { - e.printStackTrace(); - Log.e(TAG,"Can't close socket"); - electrumRequest.setError(App.Companion.getInstance().getString(R.string.loaded_wallet_error_blockchain_communication_error)); - } - } - } catch (IOException e) { - //e.printStackTrace(); - //responseListener.onFail(e.getMessage()); - electrumRequest.setError(App.Companion.getInstance().getString(R.string.loaded_wallet_error_blockchain_communication_error)); - Log.e(TAG, "doElectrumRequestTcp " + electrumRequest.getMethod() + " IOException " + e.getMessage()); - } - } - - private void doElectrumRequestSsl(ElectrumRequest electrumRequest, String host, int port) { - try { - // create a trust manager that does not validate certificate chains - TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() { - @Override - public void checkClientTrusted(X509Certificate[] chain, String authType) { - - } - - @Override - public void checkServerTrusted(X509Certificate[] chain, String authType) { - - } - - @Override - public X509Certificate[] getAcceptedIssuers() { - return new X509Certificate[0]; - } - }}; - - // install the all-trusting trust manager - SSLContext sc = SSLContext.getInstance("SSL"); - sc.init(null, trustAllCerts, new java.security.SecureRandom()); - SSLSocketFactory sf = sc.getSocketFactory(); - HttpsURLConnection.setDefaultSSLSocketFactory(sf); - - // create all-trusting host name verifier - HostnameVerifier allHostsValid = (hostname, session) -> true; - - // install the all-trusting host verifier - HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid); - - Socket sslSocket = new Socket(); - - List result = new ArrayList<>(); - Collections.addAll(result, electrumRequest); - - try { - Log.i(TAG, host + " " + port); - sslSocket.connect(new InetSocketAddress(host,port), 3000); - sslSocket.setSoTimeout(3000); - sslSocket = sf.createSocket(sslSocket, host, port, true); - try { - OutputStream os = sslSocket.getOutputStream(); - OutputStreamWriter out = new OutputStreamWriter(os, "UTF-8"); - InputStream is = sslSocket.getInputStream(); - BufferedReader in = new BufferedReader(new InputStreamReader(is)); - electrumRequest.setID(1); - - out.write(electrumRequest.getAsString() + "\n"); - out.flush(); - - electrumRequest.answerData = in.readLine(); - if (electrumRequest.answerData != null) { - Log.i(TAG, ">> " + electrumRequest.answerData); - if( (electrumRequest.getError()!=null && electrumRequest.getError().startsWith(ERROR_STARTS_WITH_CODE_32601)) ) - { - // method unknown error??? - electrumRequest.setError(App.Companion.getInstance().getString(R.string.loaded_wallet_error_obtaining_blockchain_data)); - electrumRequest.answerData=null; - } - } else { - electrumRequest.setError(App.Companion.getInstance().getString(R.string.loaded_wallet_error_blockchain_empty_answer)); - Log.i(TAG, ">> "); - } - - } catch (ConnectException e) { - e.printStackTrace(); - electrumRequest.setError(App.Companion.getInstance().getString(R.string.loaded_wallet_error_blockchain_connection_refused)); - Log.e(TAG, "doElectrumRequestSsl " + electrumRequest.getMethod() + " ConnectException " + e.getMessage()); - } finally { - Log.i(TAG, "doElectrumRequestSsl " + electrumRequest.getMethod() + " socket.close"); - try { - if( sslSocket.isConnected() ) sslSocket.close(); - } - catch (Exception e) - { - electrumRequest.setError(App.Companion.getInstance().getString(R.string.loaded_wallet_error_blockchain_communication_error)); - e.printStackTrace(); - Log.e(TAG, "Can't close ssl socket"); - } - } - - } catch (IOException e) { - e.printStackTrace(); - electrumRequest.setError(App.Companion.getInstance().getString(R.string.loaded_wallet_error_blockchain_communication_error)); - Log.e(TAG, "doElectrumRequestSsl " + electrumRequest.getMethod() + " IOException " + e.getMessage()); - } - } catch (NoSuchAlgorithmException | KeyManagementException e) { - electrumRequest.setError(App.Companion.getInstance().getString(R.string.loaded_wallet_error_obtaining_blockchain_data)); - Log.e(TAG, e.getMessage()); - } - } - - public String getValidationNodeDescription() { - return "Electrum, " + host + ":" + String.valueOf(port); - } - - -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/data/network/ServerApiEos.java b/app/src/main/java/com/tangem/data/network/ServerApiEos.java deleted file mode 100644 index 99bc68b9cb..0000000000 --- a/app/src/main/java/com/tangem/data/network/ServerApiEos.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.tangem.data.network; - -import android.util.Log; - -import com.tangem.wallet.eos.EosApiPush; -import com.tangem.wallet.eos.EosPushTransactionRequest; - -import io.jafka.jeos.EosApi; -import io.jafka.jeos.EosApiFactory; -import io.jafka.jeos.core.response.chain.account.Account; -import io.jafka.jeos.core.response.chain.transaction.PushedTransaction; -import io.jafka.jeos.impl.EosApiServiceGenerator; -import io.reactivex.Observable; -import io.reactivex.Observer; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.schedulers.Schedulers; - -public class ServerApiEos { - private static String TAG = ServerApiEos.class.getSimpleName(); - - public static void getBalance(String wallet, Observer accountObserver) { - Log.i(TAG, "new getBalance request"); - EosApi eosApi = EosApiFactory.create("https://api.eosdetroit.io:443"); //TODO: add random server request - - Observable accountObservable = Observable.just(new Account()) - .map(account -> eosApi.getAccount(wallet)) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()); - - accountObservable.subscribe(accountObserver); - } - - public static void sendTransaction(EosPushTransactionRequest req, Observer sendObserver) { - Log.i(TAG, "new getBalance request"); -// EosApi eosApi = EosApiFactory.create("https://api.eosdetroit.io:443"); //TODO: add random server request - EosApiPush eosApiPush = EosApiServiceGenerator.createService(EosApiPush.class, "https://api.eosdetroit.io:443"); //TODO: add random server request - - Observable sendObservable = Observable.just(new PushedTransaction()) - .map(pushedTransaction -> EosApiServiceGenerator.executeSync(eosApiPush.pushTransaction(req))) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()); - - sendObservable.subscribe(sendObserver); - } - -} diff --git a/app/src/main/java/com/tangem/data/network/ServerApiInfura.java b/app/src/main/java/com/tangem/data/network/ServerApiInfura.java deleted file mode 100644 index 29ded17af1..0000000000 --- a/app/src/main/java/com/tangem/data/network/ServerApiInfura.java +++ /dev/null @@ -1,121 +0,0 @@ -package com.tangem.data.network; - -import android.util.Log; - -import androidx.annotation.NonNull; - -import com.tangem.App; -import com.tangem.data.Blockchain; -import com.tangem.data.network.model.InfuraBody; -import com.tangem.data.network.model.InfuraResponse; - -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; - -public class ServerApiInfura { - private static String TAG = ServerApiInfura.class.getSimpleName(); - - /** - * HTTP - * Infura - *

- * eth_getBalance - * eth_getTransactionCount - * eth_call - * eth_sendRawTransaction - * eth_gasPrice - */ - public static final String INFURA_ETH_GET_BALANCE = "eth_getBalance"; - public static final String INFURA_ETH_GET_TRANSACTION_COUNT = "eth_getTransactionCount"; - public static final String INFURA_ETH_GET_PENDING_COUNT = "eth_getPendingCount"; - public static final String INFURA_ETH_CALL = "eth_call"; - public static final String INFURA_ETH_SEND_RAW_TRANSACTION = "eth_sendRawTransaction"; - public static final String INFURA_ETH_GAS_PRICE = "eth_gasPrice"; - - private int requestsCount=0; - - private InfuraApi infuraApi = App.Companion.getNetworkComponent().getRetrofitInfura().create(InfuraApi.class); - - public ServerApiInfura() {} - - public ServerApiInfura(Blockchain blockchain) { - if (blockchain == Blockchain.EthereumTestNet) { - infuraApi = App.Companion.getNetworkComponent().getRetrofitInfuraTestnet().create(InfuraApi.class); - } else if (blockchain == Blockchain.TokenEmv) { - infuraApi = App.Companion.getNetworkComponent().getRetrofitInfuraRopsten().create(InfuraApi.class); - } - } - - public boolean isRequestsSequenceCompleted() { - Log.i(TAG, String.format("isRequestsSequenceCompleted: %s (%d requests left)", String.valueOf(requestsCount <= 0), requestsCount)); - return requestsCount <= 0; - } - - private ResponseListener responseListener; - - public interface ResponseListener { - void onSuccess(String method, InfuraResponse infuraResponse); - - void onFail(String method, String message); - } - - public void setResponseListener(ResponseListener listener) { - responseListener = listener; - } - - public void requestData(String method, int id, String wallet, String contract, String tx) { - requestsCount++; - - - InfuraBody infuraBody; - switch (method) { - case INFURA_ETH_GET_BALANCE: - case INFURA_ETH_GET_TRANSACTION_COUNT: - infuraBody = new InfuraBody(method, new String[]{wallet, "latest"}, id); - break; - case INFURA_ETH_GET_PENDING_COUNT: - infuraBody = new InfuraBody(INFURA_ETH_GET_TRANSACTION_COUNT, new String[]{wallet, "pending"}, id); - break; - case INFURA_ETH_CALL: - String address = wallet.substring(2); - infuraBody = new InfuraBody(method, new Object[]{new InfuraBody.EthCallParams("0x70a08231000000000000000000000000" + address, contract), "latest"}, id); - break; - - case INFURA_ETH_SEND_RAW_TRANSACTION: - infuraBody = new InfuraBody(method, new String[]{tx}, id); - break; - - case INFURA_ETH_GAS_PRICE: - infuraBody = new InfuraBody(method, id); - break; - - default: - infuraBody = new InfuraBody(); - } - - Call call = infuraApi.infura(infuraBody); - call.enqueue(new Callback() { - @Override - public void onResponse(@NonNull Call call, @NonNull Response response) { - requestsCount--; - - if (response.code() == 200) { - responseListener.onSuccess(method, response.body()); - Log.i(TAG, "requestData " + method + " onResponse " + response.code()); - } else { - responseListener.onFail(method, String.valueOf(response.code())); - Log.e(TAG, "requestData " + method + " onResponse " + response.code()); - } - } - - @Override - public void onFailure(@NonNull Call call, @NonNull Throwable t) { - requestsCount--; - responseListener.onFail(method, String.valueOf(t.getMessage())); - Log.e(TAG, "requestData " + method + " onFailure " + t.getMessage()); - } - }); - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/data/network/ServerApiInsight.java b/app/src/main/java/com/tangem/data/network/ServerApiInsight.java deleted file mode 100644 index 5a768471e6..0000000000 --- a/app/src/main/java/com/tangem/data/network/ServerApiInsight.java +++ /dev/null @@ -1,131 +0,0 @@ -package com.tangem.data.network; - -import android.util.Log; - -import androidx.annotation.NonNull; - -import com.tangem.data.network.model.InsightBody; -import com.tangem.data.network.model.InsightResponse; -import com.tangem.data.network.model.InsightUtxo; - -import java.util.List; - -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; -import retrofit2.Retrofit; -import retrofit2.converter.gson.GsonConverterFactory; - -public class ServerApiInsight { - private static String TAG = ServerApiInsight.class.getSimpleName(); - - public static final String INSIGHT_ADDRESS = "addr/{address}"; - public static final String INSIGHT_UNSPENT_OUTPUTS = "addr/{address}/utxo"; - public static final String INSIGHT_TRANSACTION = "rawtx/{txId}"; - public static final String INSIGHT_FEE = "utils/estimatefee?nbBlocks=2,3,6"; - public static final String INSIGHT_SEND = "tx/send"; - - private int requestsCount = 0; - - public static String lastNode; - - public boolean isRequestsSequenceCompleted() { - Log.i(TAG, String.format("isRequestsSequenceCompleted: %s (%d requests left)", String.valueOf(requestsCount <= 0), requestsCount)); - return requestsCount <= 0; - } - - private ResponseListener responseListener; - - public interface ResponseListener { - void onSuccess(String method, InsightResponse insightResponse); - - void onSuccess(String method, List utxoList); - - void onFail(String method, String message); - } - - public void setResponseListener(ResponseListener listener) { - responseListener = listener; - } - - public void requestData(String method, String wallet, String tx) { - requestsCount++; - String insightURL = "https://insight.ducatus.io/insight-lite-api/"; //TODO: make random selection - this.lastNode = insightURL; //TODO: show node instead of URL - - Retrofit retrofitInsight = new Retrofit.Builder() - .baseUrl(insightURL) - .addConverterFactory(GsonConverterFactory.create()) - .build(); - -// InsightApi insightApi = App.getNetworkComponent().getRetrofitInsight(insightURL).create(InsightApi.class); - InsightApi insightApi = retrofitInsight.create(InsightApi.class); - - if (method.equals(INSIGHT_UNSPENT_OUTPUTS)) { - Call> call = insightApi.insightUnspent(wallet); - call.enqueue(new Callback>() { - @Override - public void onResponse(@NonNull Call> call, @NonNull Response> response) { - requestsCount--; - - if (response.code() == 200) { - responseListener.onSuccess(method, response.body()); - Log.i(TAG, "requestData " + method + " onResponse " + response.code()); - } else { - responseListener.onFail(method, String.valueOf(response.code())); - Log.e(TAG, "requestData " + method + " onResponse " + response.code()); - } - } - - @Override - public void onFailure(@NonNull Call> call, @NonNull Throwable t) { - requestsCount--; - responseListener.onFail(method, String.valueOf(t.getMessage())); - Log.e(TAG, "requestData " + method + " onFailure " + t.getMessage()); - } - }); - - } else { - Call call; - - switch (method) { - case INSIGHT_ADDRESS: - call = insightApi.insightAddress(wallet); - break; - -// case INSIGHT_FEE: -// call = insightApi.insightFee(); -// break; - - case INSIGHT_SEND: - call = insightApi.insightSend(new InsightBody(tx)); - break; - - default: - call = insightApi.insightAddress(wallet); - break; - } - call.enqueue(new Callback() { - @Override - public void onResponse(@NonNull Call call, @NonNull Response response) { - requestsCount--; - - if (response.code() == 200) { - responseListener.onSuccess(method, response.body()); - Log.i(TAG, "requestData " + method + " onResponse " + response.code()); - } else { - responseListener.onFail(method, String.valueOf(response.code())); - Log.e(TAG, "requestData " + method + " onResponse " + response.code()); - } - } - - @Override - public void onFailure(@NonNull Call call, @NonNull Throwable t) { - requestsCount--; - responseListener.onFail(method, String.valueOf(t.getMessage())); - Log.e(TAG, "requestData " + method + " onFailure " + t.getMessage()); - } - }); - } - } -} diff --git a/app/src/main/java/com/tangem/data/network/ServerApiMatic.java b/app/src/main/java/com/tangem/data/network/ServerApiMatic.java deleted file mode 100644 index 448fa02fc6..0000000000 --- a/app/src/main/java/com/tangem/data/network/ServerApiMatic.java +++ /dev/null @@ -1,108 +0,0 @@ -package com.tangem.data.network; - -import android.util.Log; - -import androidx.annotation.NonNull; - -import com.tangem.App; -import com.tangem.data.network.model.InfuraBody; -import com.tangem.data.network.model.InfuraResponse; - -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; - -public class ServerApiMatic { - private static String TAG = ServerApiMatic.class.getSimpleName(); - - /** - * HTTP - * Infura - *

- * eth_getBalance - * eth_getTransactionCount - * eth_call - * eth_sendRawTransaction - * eth_gasPrice - */ - public static final String MATIC_ETH_GET_BALANCE = "eth_getBalance"; - public static final String MATIC_ETH_GET_TRANSACTION_COUNT = "eth_getTransactionCount"; - public static final String MATIC_ETH_GET_PENDING_COUNT = "eth_getPendingCount"; - public static final String MATIC_ETH_CALL = "eth_call"; - public static final String MATIC_ETH_SEND_RAW_TRANSACTION = "eth_sendRawTransaction"; - public static final String MATIC_ETH_GAS_PRICE = "eth_gasPrice"; - - private int requestsCount=0; - - public boolean isRequestsSequenceCompleted() { - Log.i(TAG, String.format("isRequestsSequenceCompleted: %s (%d requests left)", String.valueOf(requestsCount <= 0), requestsCount)); - return requestsCount <= 0; - } - - private ResponseListener responseListener; - - public interface ResponseListener { - void onSuccess(String method, InfuraResponse infuraResponse); - - void onFail(String method, String message); - } - - public void setResponseListener(ResponseListener listener) { - responseListener = listener; - } - - public void requestData(String method, int id, String wallet, String contract, String tx) { - requestsCount++; - MaticApi maticApi = App.Companion.getNetworkComponent().getRetrofitMaticTesnet().create(MaticApi.class); - - InfuraBody infuraBody; - switch (method) { - case MATIC_ETH_GET_BALANCE: - case MATIC_ETH_GET_TRANSACTION_COUNT: - infuraBody = new InfuraBody(method, new String[]{wallet, "latest"}, id); - break; - case MATIC_ETH_GET_PENDING_COUNT: - infuraBody = new InfuraBody(MATIC_ETH_GET_TRANSACTION_COUNT, new String[]{wallet, "pending"}, id); - break; - case MATIC_ETH_CALL: - String address = wallet.substring(2); - infuraBody = new InfuraBody(method, new Object[]{new InfuraBody.EthCallParams("0x70a08231000000000000000000000000" + address, contract), "latest"}, id); - break; - - case MATIC_ETH_SEND_RAW_TRANSACTION: - infuraBody = new InfuraBody(method, new String[]{tx}, id); - break; - - case MATIC_ETH_GAS_PRICE: - infuraBody = new InfuraBody(method, id); - break; - - default: - infuraBody = new InfuraBody(); - } - - Call call = maticApi.matic(infuraBody); - call.enqueue(new Callback() { - @Override - public void onResponse(@NonNull Call call, @NonNull Response response) { - requestsCount--; - - if (response.code() == 200) { - responseListener.onSuccess(method, response.body()); - Log.i(TAG, "requestData " + method + " onResponse " + response.code()); - } else { - responseListener.onFail(method, String.valueOf(response.code())); - Log.e(TAG, "requestData " + method + " onResponse " + response.code()); - } - } - - @Override - public void onFailure(@NonNull Call call, @NonNull Throwable t) { - requestsCount--; - responseListener.onFail(method, String.valueOf(t.getMessage())); - Log.e(TAG, "requestData " + method + " onFailure " + t.getMessage()); - } - }); - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/data/network/ServerApiPayId.java b/app/src/main/java/com/tangem/data/network/ServerApiPayId.java deleted file mode 100644 index 5bf8857769..0000000000 --- a/app/src/main/java/com/tangem/data/network/ServerApiPayId.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.tangem.data.network; - -import com.jakewharton.retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; -import com.tangem.data.Blockchain; -import com.tangem.data.network.model.PayIdResponse; -import com.tangem.tangem_card.util.Log; - -import java.security.InvalidParameterException; - -import io.reactivex.Single; -import io.reactivex.SingleObserver; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.schedulers.Schedulers; -import retrofit2.Retrofit; -import retrofit2.converter.gson.GsonConverterFactory; - -public class ServerApiPayId { - private static String TAG = ServerApiPayId.class.getSimpleName(); - - private int requestsCount = 0; - - public String getAcceptHeader(Blockchain blockchain) throws InvalidParameterException { - switch (blockchain) { - case Ripple: return "application/xrpl-mainnet+json"; - case Bitcoin: return "application/btc-mainnet+json"; - case Litecoin: return "application/ltc-mainnet+json"; - case Cardano: - case CardanoShelley: - return "application/ada-mainnet+json"; - case Ducatus: return "application/duc-mainnet+json"; - case BitcoinCash: return "application/bch-mainnet+json"; - case Ethereum: - case Token: - return "application/eth-mainnet+json"; - case Stellar: - case StellarAsset: - return "application/xlm-mainnet+json"; - case Binance: - case BinanceAsset: - return "application/bnb-mainnet+json"; - case Rootstock: - case RootstockToken: - return "application/rsk-mainnet+json"; - default: throw new InvalidParameterException("PayID is not supported for " + blockchain.getOfficialName()); - } - } - - public boolean isRequestsSequenceCompleted() { - Log.i(TAG, String.format("isRequestsSequenceCompleted: %s (%d requests left)", String.valueOf(requestsCount <= 0), requestsCount)); - return requestsCount <= 0; - } - - public void getAddress(String payID, Blockchain blockchain, SingleObserver addressObserver) throws InvalidParameterException { - requestsCount++; - Log.i(TAG, "new getAddress request"); - - String[] addressParts = payID.split("\\$"); - String user = addressParts[0]; - String domain = addressParts[1]; - - Retrofit retrofit = new Retrofit.Builder() - .baseUrl("https://" + domain + "/") - .addConverterFactory(GsonConverterFactory.create()) - .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) - .build(); - - PayIdApi api = retrofit.create(PayIdApi.class); - - Single addressSingle = api.getAddress(user, getAcceptHeader(blockchain)) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .doOnEvent((object, throwable) -> requestsCount--); - - addressSingle.subscribe(addressObserver); - } -} diff --git a/app/src/main/java/com/tangem/data/network/ServerApiRipple.java b/app/src/main/java/com/tangem/data/network/ServerApiRipple.java deleted file mode 100644 index 85563c0df4..0000000000 --- a/app/src/main/java/com/tangem/data/network/ServerApiRipple.java +++ /dev/null @@ -1,157 +0,0 @@ -package com.tangem.data.network; - -import android.util.Log; - -import androidx.annotation.NonNull; - -import com.tangem.data.network.model.RippleBody; -import com.tangem.data.network.model.RippleResponse; - -import java.util.HashMap; - -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; -import retrofit2.Retrofit; -import retrofit2.converter.gson.GsonConverterFactory; - -public class ServerApiRipple { - private static String TAG = ServerApiRipple.class.getSimpleName(); - - public static final String RIPPLE_ACCOUNT_INFO = "account_info"; - public static final String RIPPLE_ACCOUNT_UNCONFIRMED = "account_unconfirmed"; - public static final String RIPPLE_SUBMIT = "submit"; - public static final String RIPPLE_FEE = "fee"; - public static final String RIPPLE_SERVER_STATE = "server_state"; - - private int requestsCount = 0; - - private final String rippleURL1 = "https://s1.ripple.com:51234"; //TODO: make random selection, add more?, move - private final String rippleURL2 = "https://s2.ripple.com:51234"; - - private String currentURL = rippleURL1; - - public String getCurrentURL() { - return currentURL; - } - - public boolean isRequestsSequenceCompleted() { - Log.i(TAG, String.format("isRequestsSequenceCompleted: %s (%d requests left)", String.valueOf(requestsCount <= 0), requestsCount)); - return requestsCount <= 0; - } - - private ResponseListener responseListener; - - public interface ResponseListener { - void onSuccess(String method, RippleResponse rippleResponse); - - void onFail(String method, String message); - } - - public void setResponseListener(ResponseListener listener) { - responseListener = listener; - } - - public void requestData(String method, String wallet, String tx) { - requestsCount++; - - Retrofit retrofitRipple = new Retrofit.Builder() - .baseUrl(currentURL) - .addConverterFactory(GsonConverterFactory.create()) - .build(); - - RippleApi rippleApi = retrofitRipple.create(RippleApi.class); - - RippleBody rippleBody; - HashMap paramsMap; - - switch (method) { - case RIPPLE_ACCOUNT_INFO: - paramsMap = new HashMap<>(); - paramsMap.put("account", wallet); - paramsMap.put("ledger_index", "validated"); - rippleBody = new RippleBody(method, paramsMap); - break; - - case RIPPLE_ACCOUNT_UNCONFIRMED: - paramsMap = new HashMap<>(); - paramsMap.put("account", wallet); - paramsMap.put("ledger_index", "current"); -// paramsMap.put("queue", "true"); TODO: make queue check if needed - rippleBody = new RippleBody(RIPPLE_ACCOUNT_INFO, paramsMap); - break; - - case RIPPLE_SERVER_STATE: - rippleBody = new RippleBody(method, new HashMap<>()); - break; - - case RIPPLE_FEE: - rippleBody = new RippleBody(method, new HashMap<>()); - break; - - case RIPPLE_SUBMIT: - paramsMap = new HashMap<>(); - paramsMap.put("tx_blob", tx); - rippleBody = new RippleBody(method, paramsMap); - break; - - default: - rippleBody = new RippleBody(); - } - - Call call = rippleApi.ripple(rippleBody); - call.enqueue(new Callback() { - @Override - public void onResponse(@NonNull Call call, @NonNull Response response) { - if (response.code() == 200) { - requestsCount--; - responseListener.onSuccess(method, response.body()); - Log.i(TAG, "requestData " + method + " onResponse " + response.code()); - } else { - retryRequest(method, rippleBody); - Log.e(TAG, "requestData " + method + " onResponse " + response.code()); - } - } - - @Override - public void onFailure(@NonNull Call call, @NonNull Throwable t) { - retryRequest(method, rippleBody); - Log.e(TAG, "requestData " + method + " onFailure " + t.getMessage()); - } - }); - } - - private void retryRequest(String method, RippleBody rippleBody) { - currentURL = rippleURL2; - - Retrofit retrofitRipple = new Retrofit.Builder() - .baseUrl(currentURL) - .addConverterFactory(GsonConverterFactory.create()) - .build(); - - RippleApi rippleApi = retrofitRipple.create(RippleApi.class); - - Call call = rippleApi.ripple(rippleBody); - call.enqueue(new Callback() { - @Override - public void onResponse(@NonNull Call call, @NonNull Response response) { - requestsCount--; - - if (response.code() == 200) { - responseListener.onSuccess(method, response.body()); - Log.i(TAG, "requestData " + method + " onResponse " + response.code()); - } else { - responseListener.onFail(method, String.valueOf(response.code())); - Log.e(TAG, "requestData " + method + " onResponse " + response.code()); - } - } - - @Override - public void onFailure(@NonNull Call call, @NonNull Throwable t) { - requestsCount--; - responseListener.onFail(method, String.valueOf(t.getMessage())); - Log.e(TAG, "requestData " + method + " onFailure " + t.getMessage()); - } - }); - } -} diff --git a/app/src/main/java/com/tangem/data/network/ServerApiRootstock.java b/app/src/main/java/com/tangem/data/network/ServerApiRootstock.java deleted file mode 100644 index ce1c152a39..0000000000 --- a/app/src/main/java/com/tangem/data/network/ServerApiRootstock.java +++ /dev/null @@ -1,108 +0,0 @@ -package com.tangem.data.network; - -import android.util.Log; - -import androidx.annotation.NonNull; - -import com.tangem.App; -import com.tangem.data.network.model.InfuraBody; -import com.tangem.data.network.model.InfuraResponse; - -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; - -public class ServerApiRootstock { - private static String TAG = ServerApiRootstock.class.getSimpleName(); - - /** - * HTTP - * Rootstock - *

- * eth_getBalance - * eth_getTransactionCount - * eth_call - * eth_sendRawTransaction - * eth_gasPrice - */ - public static final String ROOTSTOCK_ETH_GET_BALANCE = "eth_getBalance"; - public static final String ROOTSTOCK_ETH_GET_TRANSACTION_COUNT = "eth_getTransactionCount"; - public static final String ROOTSTOCK_ETH_GET_PENDING_COUNT = "eth_getPendingCount"; - public static final String ROOTSTOCK_ETH_CALL = "eth_call"; - public static final String ROOTSTOCK_ETH_SEND_RAW_TRANSACTION = "eth_sendRawTransaction"; - public static final String ROOTSTOCK_ETH_GAS_PRICE = "eth_gasPrice"; - - private int requestsCount=0; - - public boolean isRequestsSequenceCompleted() { - Log.i(TAG, String.format("isRequestsSequenceCompleted: %s (%d requests left)", String.valueOf(requestsCount <= 0), requestsCount)); - return requestsCount <= 0; - } - - private ResponseListener responseListener; - - public interface ResponseListener { - void onSuccess(String method, InfuraResponse infuraResponse); - - void onFail(String method, String message); - } - - public void setResponseListener(ResponseListener listener) { - responseListener = listener; - } - - public void requestData(String method, int id, String wallet, String contract, String tx) { - requestsCount++; - RootstockApi rootstockApi = App.Companion.getNetworkComponent().getRetrofitRootstock().create(RootstockApi.class); - - InfuraBody infuraBody; - switch (method) { - case ROOTSTOCK_ETH_GET_BALANCE: - case ROOTSTOCK_ETH_GET_TRANSACTION_COUNT: - infuraBody = new InfuraBody(method, new String[]{wallet, "latest"}, id); - break; - case ROOTSTOCK_ETH_GET_PENDING_COUNT: - infuraBody = new InfuraBody(ROOTSTOCK_ETH_GET_TRANSACTION_COUNT, new String[]{wallet, "pending"}, id); - break; - case ROOTSTOCK_ETH_CALL: - String address = wallet.substring(2); - infuraBody = new InfuraBody(method, new Object[]{new InfuraBody.EthCallParams("0x70a08231000000000000000000000000" + address, contract), "latest"}, id); - break; - - case ROOTSTOCK_ETH_SEND_RAW_TRANSACTION: - infuraBody = new InfuraBody(method, new String[]{tx}, id); - break; - - case ROOTSTOCK_ETH_GAS_PRICE: - infuraBody = new InfuraBody(method, id); - break; - - default: - infuraBody = new InfuraBody(); - } - - Call call = rootstockApi.rootstock(infuraBody); - call.enqueue(new Callback() { - @Override - public void onResponse(@NonNull Call call, @NonNull Response response) { - requestsCount--; - - if (response.code() == 200) { - responseListener.onSuccess(method, response.body()); - Log.i(TAG, "requestData " + method + " onResponse " + response.code()); - } else { - responseListener.onFail(method, String.valueOf(response.code())); - Log.e(TAG, "requestData " + method + " onResponse " + response.code()); - } - } - - @Override - public void onFailure(@NonNull Call call, @NonNull Throwable t) { - requestsCount--; - responseListener.onFail(method, String.valueOf(t.getMessage())); - Log.e(TAG, "requestData " + method + " onFailure " + t.getMessage()); - } - }); - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/data/network/ServerApiSoChain.java b/app/src/main/java/com/tangem/data/network/ServerApiSoChain.java deleted file mode 100644 index ceb90697d5..0000000000 --- a/app/src/main/java/com/tangem/data/network/ServerApiSoChain.java +++ /dev/null @@ -1,186 +0,0 @@ -package com.tangem.data.network; - -import android.util.Log; - -import androidx.annotation.NonNull; - -import com.tangem.App; -import com.tangem.data.Blockchain; -import com.tangem.data.network.model.SoChain; - -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; - -public class ServerApiSoChain { - - private static String TAG = ServerApiSoChain.class.getSimpleName(); - - private int requestsCount = 0; - - public boolean isRequestsSequenceCompleted() { - Log.i(TAG, String.format("isRequestsSequenceCompleted: %s (%d requests left)", String.valueOf(requestsCount <= 0), requestsCount)); - return requestsCount <= 0; - } - - - public interface AddressInfoListener { - void onSuccess(SoChain.Response.AddressBalance response); - - void onSuccess(SoChain.Response.TxUnspent response); - - void onFail(String message); - } - - private AddressInfoListener addressInfoListener; - - public void setAddressInfoListener(AddressInfoListener listener) { - addressInfoListener = listener; - } - - public interface SendTxListener { - void onSuccess(SoChain.Response.SendTx response); - - void onFail(String message); - } - - private SendTxListener sendTxListener; - - public void setSendTxListener(SendTxListener listener) { - sendTxListener = listener; - } - - - public interface TransactionInfoListener { - void onSuccess(SoChain.Response.GetTx response); - - void onFail(String message); - } - - private TransactionInfoListener txInfoListener; - - public void setTransactionInfoListener(TransactionInfoListener listener) { - txInfoListener=listener; - } - - - private String getNetwork(Blockchain blockchain) throws Exception { - switch (blockchain) { - case Bitcoin: - return "BTC"; - case BitcoinTestNet: - return "BTCTEST"; - case Litecoin: - return "LTC"; - default: - throw new Exception("SoChainAPI don't support blockchain " + blockchain.getID()); - } - } - - public void requestAddressBalance(Blockchain blockchain, String wallet) throws Exception { - requestsCount++; - SoChainApi api = App.Companion.getNetworkComponent().getRetrofitSoChain().create(SoChainApi.class); - - Call call = api.getAddressBalance(getNetwork(blockchain), wallet); - call.enqueue(new Callback() { - @Override - public void onResponse(@NonNull Call call, @NonNull Response response) { - requestsCount--; - Log.i(TAG, "requestAddressBalance onResponse " + response.code()); - if (response.code() == 200) { - addressInfoListener.onSuccess(response.body()); - } else { - addressInfoListener.onFail(String.valueOf(response.code())); - } - } - - @Override - public void onFailure(@NonNull Call call, @NonNull Throwable t) { - requestsCount--; - Log.e(TAG, "requestAddressBalance onFailure " + t.getMessage()); - addressInfoListener.onFail(String.valueOf(t.getMessage())); - } - }); - } - - public void requestUnspentTx(Blockchain blockchain, String wallet) throws Exception { - requestsCount++; - SoChainApi api = App.Companion.getNetworkComponent().getRetrofitSoChain().create(SoChainApi.class); - - Call call = api.getUnspentTx(getNetwork(blockchain), wallet); - call.enqueue(new Callback() { - @Override - public void onResponse(@NonNull Call call, @NonNull Response response) { - requestsCount--; - Log.i(TAG, "requestAddressBalance onResponse " + response.code()); - if (response.code() == 200) { - addressInfoListener.onSuccess(response.body()); - } else { - addressInfoListener.onFail(String.valueOf(response.code())); - } - } - - @Override - public void onFailure(@NonNull Call call, @NonNull Throwable t) { - requestsCount--; - Log.e(TAG, "requestAddressBalance onFailure " + t.getMessage()); - addressInfoListener.onFail(String.valueOf(t.getMessage())); - } - }); - } - - public void requestSendTransaction(Blockchain blockchain, String txHEX) throws Exception { - requestsCount++; - SoChainApi api = App.Companion.getNetworkComponent().getRetrofitSoChain().create(SoChainApi.class); - - SoChain.Request.SendTx tx=new SoChain.Request.SendTx(); - tx.setTx_hex(txHEX); - Call call = api.sendTransaction(getNetwork(blockchain), tx); - call.enqueue(new Callback() { - @Override - public void onResponse(@NonNull Call call, @NonNull Response response) { - requestsCount--; - Log.i(TAG, "requestAddressBalance onResponse " + response.code()); - if (response.code() == 200) { - sendTxListener.onSuccess(response.body()); - } else { - sendTxListener.onFail(String.valueOf(response.code())); - } - } - - @Override - public void onFailure(@NonNull Call call, @NonNull Throwable t) { - requestsCount--; - Log.e(TAG, "requestAddressBalance onFailure " + t.getMessage()); - sendTxListener.onFail(String.valueOf(t.getMessage())); - } - }); - } - - public void requestTransactionInfo(Blockchain blockchain, String txId) throws Exception { - requestsCount++; - SoChainApi api = App.Companion.getNetworkComponent().getRetrofitSoChain().create(SoChainApi.class); - - Call call = api.getTx(getNetwork(blockchain), txId); - call.enqueue(new Callback() { - @Override - public void onResponse(@NonNull Call call, @NonNull Response response) { - requestsCount--; - Log.i(TAG, "requestAddressBalance onResponse " + response.code()); - if (response.code() == 200) { - txInfoListener.onSuccess(response.body()); - } else { - txInfoListener.onFail(String.valueOf(response.code())); - } - } - - @Override - public void onFailure(@NonNull Call call, @NonNull Throwable t) { - requestsCount--; - Log.e(TAG, "requestAddressBalance onFailure " + t.getMessage()); - txInfoListener.onFail(String.valueOf(t.getMessage())); - } - }); - } - -} diff --git a/app/src/main/java/com/tangem/data/network/ServerApiStellar.java b/app/src/main/java/com/tangem/data/network/ServerApiStellar.java deleted file mode 100644 index 77427e3fc4..0000000000 --- a/app/src/main/java/com/tangem/data/network/ServerApiStellar.java +++ /dev/null @@ -1,206 +0,0 @@ -package com.tangem.data.network; - -import com.tangem.App; -import com.tangem.data.Blockchain; -import com.tangem.util.LOG; -import com.tangem.wallet.R; -import com.tangem.wallet.TangemContext; - -import org.stellar.sdk.Network; -import org.stellar.sdk.Server; -import org.stellar.sdk.requests.ErrorResponse; - -import java.io.IOException; - -import io.reactivex.Observable; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.observers.DefaultObserver; -import io.reactivex.schedulers.Schedulers; - -/** - * Created by dvol on 7.01.2019. - *

- * Request processor for Stellar Horizon Rest Api - * Every request live cycle: - * 1. In application create request and call {@link ServerApiStellar}.requestData(..) - * 2. Try send every request for max 4 times, - * 3. If all 4 times fail call DefaultObserver.onError (defined in .requestData(..)) and than - * {@link Listener}.onFail(...) callback - * Error can be acquired with {@link StellarRequest}.getError() method - * 4. If request network communication finished successfully then call DefaultObserver.onComplete (defined in .requestData) and than - * {@link Listener}.onSuccess(...) callback - */ -public class ServerApiStellar { - - public ServerApiStellar(Blockchain blockchain) { - if (blockchain == Blockchain.Stellar || blockchain == Blockchain.StellarAsset || blockchain == Blockchain.StellarTag) { - currentURL = ServerURL.API_STELLAR; - } else { - currentURL = ServerURL.API_STELLAR_TESTNET; - } - } - - private static String TAG = ServerApiStellar.class.getSimpleName(); - - /** - * TCP, SSL - * Used in BTC, BCH - */ - private Listener listener; - - private int requestsCount = 0; - - private String currentURL; - - public String getCurrentURL() { - return currentURL; - } - - public boolean isRequestsSequenceCompleted() { - LOG.i(TAG, String.format("isRequestsSequenceCompleted: %s (%d requests left)", String.valueOf(requestsCount <= 0), requestsCount)); - return requestsCount <= 0; - } - - /** - * Interface for notification every request result - */ - public interface Listener { - - /** - * Notify that request processing was successful - * - * @param stellarRequest - processed request containing received answer {@see stellarRequest.getAnswer() method} - */ - void onSuccess(StellarRequest.Base stellarRequest); - - /** - * Notify that request processing was successful - * - * @param stellarRequest - processed request containing occurred error {@see stellarRequest.getError() method} - */ - void onFail(StellarRequest.Base stellarRequest); - } - - /** - * Set notificaion listener - * - * @param listener - */ - public void setListener(Listener listener) { - this.listener = listener; - } - - - /** - * Start process request - * - * @param ctx - * @param stellarRequest - */ - - public void requestData(TangemContext ctx, StellarRequest.Base stellarRequest) { - requestData(ctx, stellarRequest, false); - } - - public void requestData(TangemContext ctx, StellarRequest.Base stellarRequest, boolean isRetry) { - requestsCount++; - LOG.i(TAG, String.format("New request[%d]: %s", requestsCount, stellarRequest.getClass().getSimpleName())); - - Observable stellarObserver = Observable.just(stellarRequest) - .doOnEach(stellarRequest1 -> doStellarRequest(ctx, stellarRequest)) - - .flatMap(stellarRequest1 -> { - if (stellarRequest1.errorResponse != null) { - LOG.e(TAG, "Error response on " + stellarRequest.getClass().getSimpleName()); - return Observable.error(stellarRequest.errorResponse); - } else - return Observable.just(stellarRequest1); - } - ) -// .retryWhen(errors -> errors -// .filter(throwable -> (throwable instanceof IOException) || (throwable instanceof ErrorResponse)) -// .zipWith(Observable.range(1, 4), (n, i) -> i)) - - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()); - stellarObserver.subscribe(new DefaultObserver() { - - @Override - public void onNext(StellarRequest.Base stellarRequest) { - LOG.e(TAG, "requestData " + stellarRequest.getClass().getSimpleName() + " onNext "); - } - - @Override - public void onError(Throwable e) { - requestsCount--; - LOG.e(TAG, "requestData " + stellarRequest.getClass().getSimpleName() + " onError " + e.getMessage()); - LOG.e(TAG, String.format("%d requests left in processing", requestsCount)); - - if (isRetry || (stellarRequest.errorResponse != null && stellarRequest.errorResponse.getCode() == 404)) { - stellarRequest.setError(e.getMessage()); - //setErrorOccurred(e.getMessage());//; - listener.onFail(stellarRequest); - } else { - retryRequest(ctx, stellarRequest); - } - } - - /** - * Called after completion request processing - */ - @Override - public void onComplete() { - requestsCount--; - LOG.e(TAG, String.format("%d requests left in processing", requestsCount)); - if (stellarRequest.getError() != null) { - LOG.i(TAG, "requestData " + stellarRequest.getClass().getSimpleName() + " onComplete, error!=null"); - - if (isRetry || (stellarRequest.errorResponse != null && stellarRequest.errorResponse.getCode() == 404)) { - listener.onFail(stellarRequest); - } else { - retryRequest(ctx, stellarRequest); - } - } else { - LOG.e(TAG, "requestData " + stellarRequest.getClass().getSimpleName() + " onComplete, error==null"); - listener.onSuccess(stellarRequest); - } - } - - }); - } - - public void doStellarRequest(TangemContext ctx, StellarRequest.Base stellarRequest) throws IOException { - stellarRequest.setError(null); - try { - Server server; - Blockchain blockchain = ctx.getBlockchain(); - if (blockchain == Blockchain.Stellar || blockchain == Blockchain.StellarAsset || blockchain == Blockchain.StellarTag) { - Network.usePublicNetwork(); - server = new Server(currentURL); - } else if (blockchain == Blockchain.StellarTestNet) { - Network.useTestNetwork(); - server = new Server(currentURL); - } else { - throw new IOException("Wrong blockchain for ServerApiStellar"); - } - try { - LOG.e(TAG, "--- request " + stellarRequest.getClass().getSimpleName()); - stellarRequest.process(server); - } catch (ErrorResponse errorResponse) { - LOG.e(TAG, "--- error response: " + errorResponse.getMessage()); - stellarRequest.errorResponse = errorResponse; - stellarRequest.setError(errorResponse.getMessage()); - } - } catch (Exception e) { - e.printStackTrace(); - stellarRequest.setError(App.Companion.getInstance().getString(R.string.loaded_wallet_error_blockchain_communication_error)); - throw e; - } - } - - public void retryRequest (TangemContext ctx, StellarRequest.Base stellarRequest) { - currentURL = ServerURL.API_STELLAR_RESERVE; - requestData(ctx, stellarRequest, true); - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/data/network/ServerApiTezos.java b/app/src/main/java/com/tangem/data/network/ServerApiTezos.java deleted file mode 100644 index b5300912ca..0000000000 --- a/app/src/main/java/com/tangem/data/network/ServerApiTezos.java +++ /dev/null @@ -1,139 +0,0 @@ -package com.tangem.data.network; - -import com.jakewharton.retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; -import com.tangem.data.network.model.TezosAccountResponse; -import com.tangem.data.network.model.TezosForgeBody; -import com.tangem.data.network.model.TezosHeaderResponse; -import com.tangem.data.network.model.TezosPreapplyBody; -import com.tangem.tangem_card.util.Log; - -import java.util.ArrayList; -import java.util.List; - -import io.reactivex.Single; -import io.reactivex.SingleObserver; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.schedulers.Schedulers; -import okhttp3.OkHttpClient; -import okhttp3.logging.HttpLoggingInterceptor; -import retrofit2.Response; -import retrofit2.Retrofit; -import retrofit2.converter.gson.GsonConverterFactory; -import retrofit2.converter.scalars.ScalarsConverterFactory; - -public class ServerApiTezos { - private static String TAG = ServerApiTezos.class.getSimpleName(); - - private final String letzbakeURI = "https://teznode.letzbake.com"; - private final String tezrpcURI = "https://mainnet.tezrpc.me"; - - static final String TEZOS_ADDRESS = "chains/main/blocks/head/context/contracts/{address}"; - static final String TEZOS_HEADER = "chains/main/blocks/head/header"; - static final String TEZOS_MANAGER_KEY = "chains/main/blocks/head/context/contracts/{address}/manager_key"; - static final String TEZOS_FORGE_OPERATIONS = "chains/main/blocks/head/helpers/forge/operations"; - static final String TEZOS_PREAPPLY_OPERATIONS = "chains/main/blocks/head/helpers/preapply/operations"; - static final String TEZOS_RUN_OPERATION = "chains/main/blocks/head/helpers/scripts/run_operation"; - static final String TEZOS_INJECT_OPERATIONS = "injection/operation"; - - private Retrofit retrofitTezos = new Retrofit.Builder() - .baseUrl(letzbakeURI) - .addConverterFactory(GsonConverterFactory.create()) - .addConverterFactory(ScalarsConverterFactory.create()) - .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) - //logging for testing - .client(new OkHttpClient.Builder().addInterceptor( - new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY) - ).build()) - - .build(); - - private TezosApi tezosApi = retrofitTezos.create(TezosApi.class); - - private int requestsCount = 0; - - public boolean isRequestsSequenceCompleted() { - Log.i(TAG, String.format("isRequestsSequenceCompleted: %s (%d requests left)", String.valueOf(requestsCount <= 0), requestsCount)); - return requestsCount <= 0; - } - - public void getAddress(String wallet, SingleObserver accountObserver) { - requestsCount++; - Log.i(TAG, "new getAddress request"); - - Single accountSingle = tezosApi.getAccount(wallet) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .doOnEvent((object, throwable) -> requestsCount--); - - accountSingle.subscribe(accountObserver); - } - - public void getMangerKey(String wallet, SingleObserver accountObserver) { - requestsCount++; - Log.i(TAG, "new getManagerKey request"); - - Single managerKeySingle = tezosApi.getManagerKey(wallet) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .doOnEvent((object, throwable) -> requestsCount--); - - managerKeySingle.subscribe(accountObserver); - } - - public TezosHeaderResponse getHeader() throws Exception { // TODO? not async - requestsCount++; - Log.i(TAG, "new getHeader request"); - - Response headerResponse = tezosApi.getHeader().execute(); - - requestsCount--; - if (headerResponse.code() == 200) { - return headerResponse.body(); - } else { - throw new Exception("Wrong header response, code: " + headerResponse.code()); - } - } - - public String forgeOperations(TezosForgeBody tezosForgeBody) throws Exception { // TODO? not async - requestsCount++; - Log.i(TAG, "new forgeOperations request"); - - Response forgeResponse = tezosApi.forgeOperations(tezosForgeBody).execute(); - - requestsCount--; - if (forgeResponse.code() == 200) { - return forgeResponse.body(); - } else { - throw new Exception("Wrong forge response, code: " + forgeResponse.code()); - } - } - - public void peapplyOperations(TezosPreapplyBody tezosPreapplyBody) throws Exception { - Log.i(TAG, "new peapplyOperations request"); - - List tezosPreapplyBodyList = new ArrayList<>(); - tezosPreapplyBodyList.add(tezosPreapplyBody); - Response preapplyResponse = tezosApi.preapplyOperations(tezosPreapplyBodyList).execute(); - - if (preapplyResponse.code() != 200) { - String error = "Preapply error: unknown error"; - if (preapplyResponse.errorBody() != null) { - error = "Preapply error: " + preapplyResponse.errorBody().string(); - } - Log.e(TAG, error); - throw new Exception(error); - } - } - - public void injectOperations(String txForSend, SingleObserver injectObserver) { - requestsCount++; - Log.i(TAG, "new injectOperations request"); - - Single injectSingle = tezosApi.injectOperations(txForSend) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .doOnEvent((object, throwable) -> requestsCount--); - - injectSingle.subscribe(injectObserver); - } -} diff --git a/app/src/main/java/com/tangem/data/network/ServerApiTokenEmv.kt b/app/src/main/java/com/tangem/data/network/ServerApiTokenEmv.kt deleted file mode 100644 index d3fdba5f87..0000000000 --- a/app/src/main/java/com/tangem/data/network/ServerApiTokenEmv.kt +++ /dev/null @@ -1,53 +0,0 @@ -package com.tangem.data.network - -import com.jakewharton.retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory -import com.tangem.data.network.model.TokenEmvGetTransferFeeAnswer -import com.tangem.data.network.model.TokenEmvGetTransferFeeBody -import com.tangem.data.network.model.TokenEmvTransferAnswer -import com.tangem.data.network.model.TokenEmvTransferBody -import com.tangem.tangem_card.util.Log -import io.reactivex.SingleObserver -import io.reactivex.android.schedulers.AndroidSchedulers -import io.reactivex.schedulers.Schedulers -import okhttp3.OkHttpClient -import okhttp3.logging.HttpLoggingInterceptor -import retrofit2.Retrofit -import retrofit2.converter.gson.GsonConverterFactory -import retrofit2.converter.scalars.ScalarsConverterFactory -import java.util.concurrent.TimeUnit - -class ServerApiTokenEmv { - private val TAG = ServerApiTokenEmv::class.java.simpleName - - private val tangemServer = "https://emvsupport.appspot.com/" - - private val tokenEmvApi = Retrofit.Builder() - .baseUrl(tangemServer) - .addConverterFactory(GsonConverterFactory.create()) - .addConverterFactory(ScalarsConverterFactory.create()) - .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) //logging for testing - .client(OkHttpClient.Builder().addInterceptor( - HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY) - ).build()) - .build() - .create(TokenEmvApi::class.java) - - fun transfer(tokenEmvTransferBody: TokenEmvTransferBody, transferObserver: SingleObserver) { - Log.i(TAG, "new transfer request") - - tokenEmvApi.transfer(tokenEmvTransferBody) - .timeout(30, TimeUnit.SECONDS) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(transferObserver) - } - - fun getTransferFee(tokenEmvGetTransferFeeBody: TokenEmvGetTransferFeeBody, transferObserver: SingleObserver) { - Log.i(TAG, "new get transfer fee request") - - tokenEmvApi.getTransferFee(tokenEmvGetTransferFeeBody) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(transferObserver) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/data/network/ServerURL.java b/app/src/main/java/com/tangem/data/network/ServerURL.java deleted file mode 100644 index dedda6858d..0000000000 --- a/app/src/main/java/com/tangem/data/network/ServerURL.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.tangem.data.network; - -class ServerURL { - static final String API_TANGEM = "https://verify.tangem.com/"; - static final String API_COINMARKETCAP = "https://pro-api.coinmarketcap.com/"; - static final String API_INFURA = "https://mainnet.infura.io/"; - static final String API_INFURA_TESTNET = "https://rinkeby.infura.io/"; - static final String API_INFURA_ROPSTEN = "https://ropsten.infura.io/"; - static final String API_SOCHAIN_V2 = "https://chain.so/"; - static final String API_ESTIMATEFEE = "https://estimatefee.com/"; - static final String API_UPDATE_VERSION = "https://raw.githubusercontent.com/"; - static final String API_ROOTSTOCK = "https://public-node.rsk.co/"; - static final String API_BLOCKCYPHER = "https://api.blockcypher.com/"; - static final String API_BINANCE = "https://dex.binance.org/"; - static final String API_BINANCE_TESTNET = "https://testnet-dex.binance.org/"; - static final String API_MATIC_TESTNET = "https://testnet2.matic.network/"; - static final String API_STELLAR = "https://horizon.stellar.org/"; - static final String API_STELLAR_RESERVE = "https://horizon.sui.li/"; - static final String API_STELLAR_TESTNET = "https://horizon-testnet.stellar.org/"; - static final String API_BLOCKCHAIN_INFO = "https://blockchain.info/"; - static final String API_DUCATUS = "https://ducapi.rocknblock.io/"; - static final String API_BLOCKCHAIR = "https://api.blockchair.com/"; -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/data/network/SoChainApi.java b/app/src/main/java/com/tangem/data/network/SoChainApi.java deleted file mode 100644 index ea1f3fb89d..0000000000 --- a/app/src/main/java/com/tangem/data/network/SoChainApi.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.tangem.data.network; - - -import com.tangem.data.network.model.SoChain; - -import retrofit2.Call; -import retrofit2.http.Body; -import retrofit2.http.GET; -import retrofit2.http.Headers; -import retrofit2.http.POST; -import retrofit2.http.Path; - -public interface SoChainApi { - @GET(Server.ApiSoChain.Method.ADDRESS_BALANCE) - Call getAddressBalance(@Path("network") String network, @Path("address") String address); - - @GET(Server.ApiSoChain.Method.UNSPENT_TX) - Call getUnspentTx(@Path("network") String network, @Path("address") String address); - - @GET(Server.ApiSoChain.Method.GET_TX) - Call getTx(@Path("network") String network, @Path("txid") String txId); - - @Headers("Content-Type: application/json") - @POST(Server.ApiSoChain.Method.SEND_TRANSACTION) - Call sendTransaction(@Path("network") String network, @Body SoChain.Request.SendTx body); -} diff --git a/app/src/main/java/com/tangem/data/network/StellarRequest.java b/app/src/main/java/com/tangem/data/network/StellarRequest.java deleted file mode 100644 index fe4f27230c..0000000000 --- a/app/src/main/java/com/tangem/data/network/StellarRequest.java +++ /dev/null @@ -1,113 +0,0 @@ -package com.tangem.data.network; - -import org.stellar.sdk.KeyPair; -import org.stellar.sdk.Server; -import org.stellar.sdk.Transaction; -import org.stellar.sdk.requests.ErrorResponse; -import org.stellar.sdk.requests.RequestBuilder; -import org.stellar.sdk.responses.AccountResponse; -import org.stellar.sdk.responses.LedgerResponse; -import org.stellar.sdk.responses.Page; -import org.stellar.sdk.responses.SubmitTransactionResponse; -import org.stellar.sdk.responses.operations.OperationResponse; - -import java.io.IOException; -import java.net.URISyntaxException; -import java.util.List; - -/** - * Created by dvol on 7.01.2019. - */ - -public class StellarRequest { - - public static abstract class Base { - public ErrorResponse errorResponse; - private String error = null; - - public String getError() { - return error; - } - - public void setError(String error) { - this.error = error; - } - - public abstract void process(Server server) throws IOException; - } - - public static class Balance extends Base { - KeyPair accountKeyPair; - public AccountResponse accountResponse; - - public Balance(String walletAddress) { - accountKeyPair = KeyPair.fromAccountId(walletAddress); - } - - @Override - public void process(Server server) throws IOException { - accountResponse = server.accounts().account(accountKeyPair); - } - } - - public static class SubmitTransaction extends Base { - public Transaction transaction; - public SubmitTransactionResponse response; - - public SubmitTransaction(Transaction transaction) { - this.transaction = transaction; - } - - @Override - public void process(Server server) throws IOException { -// // First, check to make sure that the destination account exists. -// // You could skip this, but if the account does not exist, you will be charged -// // the transaction fee when the transaction fails. -// // It will throw HttpResponseException if account does not exist or there was another error. -// server.accounts().account(targetAccount); -// -// // If there was no error, load up-to-date information on your account. -// AccountResponse sourceAccount = server.accounts().account(sourceAccount); - - // And finally, send it off to Stellar! - response = server.submitTransaction(transaction); - } - } - - public static class Ledgers extends Base { - public LedgerResponse ledgerResponse; - - public Ledgers() { - } - - @Override - public void process(Server server) throws IOException { - int latestLedger = server.root().getHistoryLatestLedger(); - ledgerResponse = server.ledgers().ledger(latestLedger); - } - } - - public static class Operations extends Base { - KeyPair accountKeyPair; - public List operationsList; - int limit = 200; - - public Operations(String walletAddress) { - accountKeyPair = KeyPair.fromAccountId(walletAddress); - } - - @Override - public void process(Server server) throws IOException { - Page operationsResponse = server.operations().forAccount(accountKeyPair).limit(limit).order(RequestBuilder.Order.DESC).execute(); - operationsList = operationsResponse.getRecords(); - while (operationsResponse.getRecords().size() == limit) { - try { - operationsResponse = operationsResponse.getNextPage(server.getHttpClient()); - operationsList.addAll(operationsResponse.getRecords()); - } catch (URISyntaxException e) { - break; - } - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/data/network/TezosApi.java b/app/src/main/java/com/tangem/data/network/TezosApi.java deleted file mode 100644 index e61809b3d7..0000000000 --- a/app/src/main/java/com/tangem/data/network/TezosApi.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.tangem.data.network; - -import com.tangem.data.network.model.TezosAccountResponse; -import com.tangem.data.network.model.TezosForgeBody; -import com.tangem.data.network.model.TezosHeaderResponse; -import com.tangem.data.network.model.TezosPreapplyBody; - -import java.util.List; - -import io.reactivex.Single; -import retrofit2.Call; -import retrofit2.http.Body; -import retrofit2.http.GET; -import retrofit2.http.POST; -import retrofit2.http.Path; - -public interface TezosApi { - @GET(ServerApiTezos.TEZOS_ADDRESS) - Single getAccount(@Path("address") String address); - - @GET(ServerApiTezos.TEZOS_HEADER) - Call getHeader(); - - @GET(ServerApiTezos.TEZOS_MANAGER_KEY) - Single getManagerKey(@Path("address") String address); - - @POST(ServerApiTezos.TEZOS_FORGE_OPERATIONS) - Call forgeOperations(@Body TezosForgeBody tezosForgeBody); - - @POST(ServerApiTezos.TEZOS_PREAPPLY_OPERATIONS) - Call preapplyOperations(@Body List tezosPreapplyBodyList); - - @POST(ServerApiTezos.TEZOS_INJECT_OPERATIONS) - Single injectOperations(@Body String txForSend); -} diff --git a/app/src/main/java/com/tangem/data/network/TokenEmvApi.kt b/app/src/main/java/com/tangem/data/network/TokenEmvApi.kt deleted file mode 100644 index 43e09897c0..0000000000 --- a/app/src/main/java/com/tangem/data/network/TokenEmvApi.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.tangem.data.network - -import com.tangem.data.network.model.TokenEmvGetTransferFeeAnswer -import com.tangem.data.network.model.TokenEmvGetTransferFeeBody -import com.tangem.data.network.model.TokenEmvTransferAnswer -import com.tangem.data.network.model.TokenEmvTransferBody -import io.reactivex.Completable -import io.reactivex.Single -import retrofit2.http.Body -import retrofit2.http.POST - -interface TokenEmvApi { - @POST("./card/transfer") - fun transfer(@Body tokenEmvTransferBody: TokenEmvTransferBody): Single - @POST("./card/transfer/fee") - fun getTransferFee(@Body tokenEmvGetTransferFeeBody: TokenEmvGetTransferFeeBody): Single -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/data/network/UpdateVersionApi.java b/app/src/main/java/com/tangem/data/network/UpdateVersionApi.java deleted file mode 100644 index ab443b7815..0000000000 --- a/app/src/main/java/com/tangem/data/network/UpdateVersionApi.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.tangem.data.network; - -import okhttp3.ResponseBody; -import retrofit2.Call; -import retrofit2.http.GET; - -public interface UpdateVersionApi { - @GET(Server.ApiUpdateVersion.Method.LAST_VERSION) - Call getLastVersion(); -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/data/network/exception/Failure.kt b/app/src/main/java/com/tangem/data/network/exception/Failure.kt deleted file mode 100644 index f2fbb19c5b..0000000000 --- a/app/src/main/java/com/tangem/data/network/exception/Failure.kt +++ /dev/null @@ -1,31 +0,0 @@ -/** - - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.tangem.data.network.exception - -/** - * Base Class for handling errors/failures/exceptions. - * Every feature specific failure should extend [FeatureFailure] class. - */ -sealed class Failure { - class ApplicationError(val message: String) : Failure() - class NotFound : FeatureFailure() - class NetworkConnection : Failure() - class ServerError : Failure() - class Unauthorized : Failure() // 401 - - /** * Extend this class for feature specific failures.*/ - abstract class FeatureFailure : Failure() -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/data/network/model/AdaliteBody.java b/app/src/main/java/com/tangem/data/network/model/AdaliteBody.java deleted file mode 100644 index a8208d01cf..0000000000 --- a/app/src/main/java/com/tangem/data/network/model/AdaliteBody.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.tangem.data.network.model; - -public class AdaliteBody { - private String signedTx; - - public AdaliteBody(String signedTx) { - this.signedTx = signedTx; - } -} diff --git a/app/src/main/java/com/tangem/data/network/model/AdaliteResponse.kt b/app/src/main/java/com/tangem/data/network/model/AdaliteResponse.kt deleted file mode 100644 index d560d5fefc..0000000000 --- a/app/src/main/java/com/tangem/data/network/model/AdaliteResponse.kt +++ /dev/null @@ -1,45 +0,0 @@ -package com.tangem.data.network.model - -import com.google.gson.annotations.SerializedName - -data class AdaliteResponse( - @SerializedName("Right") - var right: AdaliteAddressData? = null -) - -data class AdaliteResponseUtxo( - @SerializedName("Right") - var right: List -) - -data class AdaliteAddressData( - @SerializedName("caAddress") - var caAddress: String? = null, - - @SerializedName("caBalance") - var caBalance: AdaliteCoins? = null, - - @SerializedName("caTxList") - var caTxList: List -) - -data class AdaliteCoins( - @SerializedName("getCoin") - var getCoin: Long? = null -) - -data class AdaliteUtxoData( - @SerializedName("cuId") - var cuId: String? = null, - - @SerializedName("cuOutIndex") - var cuOutIndex: Int? = null, - - @SerializedName("cuCoins") - var cuCoins: AdaliteCoins? = null -) - -data class AdaliteTxData( - @SerializedName("ctbId") - var ctbId: String? = null -) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/data/network/model/BinanceFee.kt b/app/src/main/java/com/tangem/data/network/model/BinanceFee.kt deleted file mode 100644 index 061b9dbb27..0000000000 --- a/app/src/main/java/com/tangem/data/network/model/BinanceFee.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.tangem.data.network.model - -import com.google.gson.annotations.SerializedName - -data class BinanceFees( - @SerializedName("fixed_fee_params") - var fixed_fee_params: BinanceFixedFee? = null -) - -data class BinanceFixedFee( - @SerializedName("msg_type") - var msg_type: String? = null, - - @SerializedName("fee") - var fee: Int? = null -) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/data/network/model/BitcoreResponse.kt b/app/src/main/java/com/tangem/data/network/model/BitcoreResponse.kt deleted file mode 100644 index 119c1fb3ef..0000000000 --- a/app/src/main/java/com/tangem/data/network/model/BitcoreResponse.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.tangem.data.network.model - -import com.google.gson.annotations.SerializedName - -data class BitcoreBalance( - @SerializedName("confirmed") - var confirmed: Long? = null, - - @SerializedName("unconfirmed") - var unconfirmed: Long? = null -) - -data class BitcoreUtxo( - @SerializedName("mintTxid") - var mintTxid: String? = null, - - @SerializedName("mintIndex") - var mintIndex: Int? = null, - - @SerializedName("value") - var value: Long? = null, - - @SerializedName("script") - var script: String? = null -) - -data class BitcoreBalanceAndUnspents( - var balance: BitcoreBalance, - var unspents: List -) - -data class BitcoreSendResponse( - var txid: String? = null -) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/data/network/model/BitcoreSendBody.java b/app/src/main/java/com/tangem/data/network/model/BitcoreSendBody.java deleted file mode 100644 index 019b4c4e80..0000000000 --- a/app/src/main/java/com/tangem/data/network/model/BitcoreSendBody.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.tangem.data.network.model; - -import java.util.ArrayList; -import java.util.List; - -public class BitcoreSendBody { - private List rawTx; - - public BitcoreSendBody(String tx) { - List txList = new ArrayList<>(); - txList.add(tx); - rawTx = txList; - } -} diff --git a/app/src/main/java/com/tangem/data/network/model/BlockchainInfoResponse.kt b/app/src/main/java/com/tangem/data/network/model/BlockchainInfoResponse.kt deleted file mode 100644 index 8133dbf180..0000000000 --- a/app/src/main/java/com/tangem/data/network/model/BlockchainInfoResponse.kt +++ /dev/null @@ -1,56 +0,0 @@ -package com.tangem.data.network.model - -import com.google.gson.annotations.SerializedName - -data class BlockchainInfoAddress( - @SerializedName("final_balance") - var final_balance: Long? = null, - - @SerializedName("txs") - var txs: List? = null -) - -data class BlockchainInfoTransaction( - @SerializedName("hash") - var hash: String? = null, - - @SerializedName("block_height") - var block_height: Long? = null, - - @SerializedName("inputs") - var inputs: List -) - -data class BlockchainInfoUnspents( - @SerializedName("unspent_outputs") - var unspent_outputs: List -) - -data class BlockchainInfoUtxo( - @SerializedName("tx_hash_big_endian") - var tx_hash_big_endian: String? = null, - - @SerializedName("tx_output_n") - var tx_output_n: Int? = null, - - @SerializedName("value") - var value: Long? = null, - - @SerializedName("script") - var script: String? = null -) - -data class BlockchainInfoInput( - @SerializedName("prev_out") - var prev_out: BlockchainInfoOutput -) - -data class BlockchainInfoOutput( - @SerializedName("addr") - var addr: String? = null -) - -data class BlockchainInfoAddressAndUnspents( - var address: BlockchainInfoAddress, - var unspents: BlockchainInfoUnspents -) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/data/network/model/BlockchairResponse.kt b/app/src/main/java/com/tangem/data/network/model/BlockchairResponse.kt deleted file mode 100644 index 8e8f65aae1..0000000000 --- a/app/src/main/java/com/tangem/data/network/model/BlockchairResponse.kt +++ /dev/null @@ -1,69 +0,0 @@ -package com.tangem.data.network.model - -import com.google.gson.annotations.SerializedName - -data class BlockchairAddressResponse( - @SerializedName("data") - val data: Map? = null -) - -data class BlockchairAddressData( - @SerializedName("address") - val address: BlockchairAddressInfo? = null, - - @SerializedName("utxo") - val unspentOutputs: List? = null, - - @SerializedName("transactions") - val transactions: List -) - -data class BlockchairAddressInfo( - @SerializedName("balance") - val balance: Long? = null, - - @SerializedName("output_count") - val outputCount: Int? = null, - - @SerializedName("unspent_output_count") - val unspentOutputCount: Int? = null -) - -data class BlockchairUnspentOutput( - @SerializedName("block_id") - val block: Int? = null, - - @SerializedName("transaction_hash") - val transactionHash: String? = null, - - @SerializedName("index") - val index: Int? = null, - - @SerializedName("value") - val amount: Long? = null -) - -data class BlockchairTransactionResponse( - @SerializedName("data") - val data: Map? = null -) - -data class BlockchairTransactionData( - @SerializedName("transaction") - val transaction: BlockchairTransactionInfo? = null -) - -data class BlockchairTransactionInfo( - @SerializedName("block_id") - val block: Int? = null -) - -data class BlockchairStatsResponse( - @SerializedName("data") - val data: BlockchairStatsData? = null -) - -data class BlockchairStatsData( - @SerializedName("suggested_transaction_fee_per_byte_sat") - val feePerByte: Int? = null -) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/data/network/model/BlockchairSendBody.java b/app/src/main/java/com/tangem/data/network/model/BlockchairSendBody.java deleted file mode 100644 index 3e6458c090..0000000000 --- a/app/src/main/java/com/tangem/data/network/model/BlockchairSendBody.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.tangem.data.network.model; - -public class BlockchairSendBody { - private String data; - - public BlockchairSendBody(String data) { - this.data = data; - } -} diff --git a/app/src/main/java/com/tangem/data/network/model/BlockcypherBody.java b/app/src/main/java/com/tangem/data/network/model/BlockcypherBody.java deleted file mode 100644 index 065b899dcf..0000000000 --- a/app/src/main/java/com/tangem/data/network/model/BlockcypherBody.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.tangem.data.network.model; - -public class BlockcypherBody { - private String tx; - - public BlockcypherBody(String tx) { - this.tx = tx; - } -} diff --git a/app/src/main/java/com/tangem/data/network/model/BlockcypherResponse.kt b/app/src/main/java/com/tangem/data/network/model/BlockcypherResponse.kt deleted file mode 100644 index f3c2e951cd..0000000000 --- a/app/src/main/java/com/tangem/data/network/model/BlockcypherResponse.kt +++ /dev/null @@ -1,65 +0,0 @@ -package com.tangem.data.network.model - -import com.google.gson.annotations.SerializedName - -data class BlockcypherResponse( - @SerializedName("address") - var address: String? = null, - - @SerializedName("balance") - var balance: Long? = null, - - @SerializedName("unconfirmed_balance") - var unconfirmed_balance: Long? = null, - - @SerializedName("txrefs") - var txrefs: List? = null, - - @SerializedName("unconfirmed_txrefs") - var unconfirmed_txrefs: List? = null, - - @SerializedName("hasMore") - var hasMore: Boolean? = null -) - -data class BlockcypherTxref( - @SerializedName("tx_hash") - var tx_hash: String? = null, - - @SerializedName("tx_input_n") - var tx_input_n: Int? = null, - - @SerializedName("tx_output_n") - var tx_output_n: Int? = null, - - @SerializedName("value") - var value: Long? = null, - - @SerializedName("confirmations") - var confirmations: Long? = null, - - @SerializedName("script") - var script: String? = null, - - @SerializedName("spent") - var spent: Boolean? = null -) - -data class BlockcypherTx( - @SerializedName("hex") - var hex: String? = null, - - @SerializedName("addresses") - var addesses: List? = null -) - -data class BlockcypherFee( - @SerializedName("low_fee_per_kb") - var low_fee_per_kb: Long? = null, - - @SerializedName("medium_fee_per_kb") - var medium_fee_per_kb: Long? = null, - - @SerializedName("high_fee_per_kb") - var high_fee_per_kb: Long? = null -) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/data/network/model/InfuraBody.java b/app/src/main/java/com/tangem/data/network/model/InfuraBody.java deleted file mode 100644 index 7f104e419e..0000000000 --- a/app/src/main/java/com/tangem/data/network/model/InfuraBody.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.tangem.data.network.model; - -public class InfuraBody { - private String method; - private Object[] params; - private int id; - private String jsonrpc = "2.0"; - - public InfuraBody() { - } - - // body for eth gasPrice - public InfuraBody(String method, int id) { - this.method = method; - this.id = id; - } - - // body for eth getBalance, eth getTransactionCount, eth sendRawTransaction - public InfuraBody(String method, String[] params, int id) { - this.method = method; - this.params = params; - this.id = id; - } - - // body for eth call - public InfuraBody(String method, Object[] params, int id) { - this.method = method; - this.params = params; - this.id = id; - } - - public static class EthCallParams { - private String data; - private String to; - - public EthCallParams(String data, String to) { - this.data = data; - this.to = to; - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/data/network/model/InfuraResponse.kt b/app/src/main/java/com/tangem/data/network/model/InfuraResponse.kt deleted file mode 100644 index d565ef79c8..0000000000 --- a/app/src/main/java/com/tangem/data/network/model/InfuraResponse.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.tangem.data.network.model - -import com.google.gson.annotations.SerializedName - -data class InfuraResponse( - @SerializedName("jsonrpc") - var jsonrpc: String = "", - - @SerializedName("id") - var id: Int? = null, - - @SerializedName("result") - var result: String? = null, - - @SerializedName("error") - var error: Object -) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/data/network/model/InsightBody.java b/app/src/main/java/com/tangem/data/network/model/InsightBody.java deleted file mode 100644 index 4d8951adfa..0000000000 --- a/app/src/main/java/com/tangem/data/network/model/InsightBody.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.tangem.data.network.model; - -public class InsightBody { - private String rawtx; - - public InsightBody(String rawtx){ - this.rawtx = rawtx; - } -} diff --git a/app/src/main/java/com/tangem/data/network/model/InsightResponse.kt b/app/src/main/java/com/tangem/data/network/model/InsightResponse.kt deleted file mode 100644 index a16c535649..0000000000 --- a/app/src/main/java/com/tangem/data/network/model/InsightResponse.kt +++ /dev/null @@ -1,40 +0,0 @@ -package com.tangem.data.network.model - -import com.google.gson.annotations.SerializedName - -data class InsightResponse( - @SerializedName("balanceSat") - var balanceSat: Long? = null, - - @SerializedName("unconfirmedBalanceSat") - var unconfirmedBalanceSat: Long? = null, - - @SerializedName("addrStr") - var addrStr: String = "", - -// @SerializedName("2") -// var fee2: String = "", -// -// @SerializedName("3") -// var fee3: String = "", -// -// @SerializedName("6") -// var fee6: String = "", - - @SerializedName("error") - var error: String = "" -) - -data class InsightUtxo( - @SerializedName("txid") - var txid: String = "", - - @SerializedName("satoshis") - var satoshis: Long? = null, - - @SerializedName("vout") - var vout: Int? = null, - - @SerializedName("scriptPubKey") - var scriptPubKey: String? = null -) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/data/network/model/PayIdResponse.kt b/app/src/main/java/com/tangem/data/network/model/PayIdResponse.kt deleted file mode 100644 index dfdf6220b3..0000000000 --- a/app/src/main/java/com/tangem/data/network/model/PayIdResponse.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.tangem.data.network.model - -import com.google.gson.annotations.SerializedName - -data class PayIdResponse( - @SerializedName("addresses") - var addresses: List? = null -) - -data class PayIdAddress( - @SerializedName("paymentNetwork") - var paymentNetwork: String? = null, - - @SerializedName("environment") - var environment: String? = null, - - @SerializedName("addressDetails") - var addressDetails: PayIdAddressDetails? = null -) - -data class PayIdAddressDetails( - @SerializedName("address") - var address: String? = null, - - @SerializedName("tag") - var tag: String? = null -) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/data/network/model/RateInfoResponse.kt b/app/src/main/java/com/tangem/data/network/model/RateInfoResponse.kt deleted file mode 100644 index 6fd76e8581..0000000000 --- a/app/src/main/java/com/tangem/data/network/model/RateInfoResponse.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.tangem.data.network.model - -import com.google.gson.annotations.SerializedName - -data class RateInfoResponse( - @SerializedName("data") - var data: RateData? = null -) - -data class RateData( - @SerializedName("quote") - var quote: Quote? = null -) - -data class Quote( - @SerializedName("USD") - var usd: CurrencyRate? = null -) - -data class CurrencyRate( - @SerializedName("price") - var price: Float? = null -) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/data/network/model/RippleBody.java b/app/src/main/java/com/tangem/data/network/model/RippleBody.java deleted file mode 100644 index 0423f357a1..0000000000 --- a/app/src/main/java/com/tangem/data/network/model/RippleBody.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.tangem.data.network.model; - -import java.util.ArrayList; -import java.util.HashMap; - -public class RippleBody { - private String method; - private ArrayList> params; - - public RippleBody() { - } - - //for RIPPLE_FEE - public RippleBody(String method) { - this.method = method; - } - - public RippleBody(String method, HashMap paramsMap) { - this.method = method; - ArrayList> paramsList = new ArrayList<>(); - paramsList.add(paramsMap); - this.params = paramsList; - } -} diff --git a/app/src/main/java/com/tangem/data/network/model/RippleResponse.kt b/app/src/main/java/com/tangem/data/network/model/RippleResponse.kt deleted file mode 100644 index 4a865aca67..0000000000 --- a/app/src/main/java/com/tangem/data/network/model/RippleResponse.kt +++ /dev/null @@ -1,78 +0,0 @@ -package com.tangem.data.network.model - -import com.google.gson.annotations.SerializedName - -data class RippleResponse( - @SerializedName("result") - var result: RippleResult? = null -) - -data class RippleResult( - @SerializedName("account_data") - var account_data: RippleAccountData? = null, - - @SerializedName("validated") - var validated: Boolean? = null, - - //for RIPPLE_FEE - @SerializedName("drops") - var drops: RippleFeeDrops? = null, - - //for RIPPLE_SUBMIT - @SerializedName("engine_result_code") - var engine_result_code: Int? = null, - - //for RIPPLE_SUBMIT - @SerializedName ("engine_result_message") - var engine_result_message: String? = null, - - //for RIPPLE_SUBMIT - @SerializedName("error") - var error: String? = null, - - //for RIPPLE_SUBMIT - @SerializedName("error_exception") - var error_exception: String? = null, - - //for RIPPLE_SERVER_STATE - @SerializedName("state") - var state: RippleState? = null, - - //for "Account not found error" - @SerializedName("error_code") - var error_code: Int? = null -) - -data class RippleAccountData( - @SerializedName("Account") - var account: String? = null, - - @SerializedName("Balance") - var balance: String? = null, - - @SerializedName("Sequence") - var sequence: Long? = null -) - -data class RippleFeeDrops( - //enough to put tx to queue - @SerializedName("minimum_fee") - var minimum_fee: String? = null, - - //enough to put tx to current ledger - @SerializedName("open_ledger_fee") - var open_ledger_fee: String? = null, - - @SerializedName("median_fee") - var median_fee: String? = null -) - -data class RippleState( - @SerializedName("validated_ledger") - var validated_ledger: RippleLedger? = null -) - -data class RippleLedger( - @SerializedName("reserve_base") - var reserve_base: Long? = null -) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/data/network/model/SoChain.kt b/app/src/main/java/com/tangem/data/network/model/SoChain.kt deleted file mode 100644 index 65a7103689..0000000000 --- a/app/src/main/java/com/tangem/data/network/model/SoChain.kt +++ /dev/null @@ -1,70 +0,0 @@ -package com.tangem.data.network.model - -class SoChain { - class Request { - class SendTx{ - var tx_hex: String? = null - } - } - - class Response { - class AddressBalance { - class Data { - var network: String? = null - var address: String? = null - var confirmed_balance: String? = null - var unconfirmed_balance: String? = null - var confirmations: String? = null - } - - var status: String? = null - var data: Data? = null - } - - class TxUnspent { - class Data { - class Tx { - var txid: String? = null //"9b5c8fbeb1e42bb2a6da40e2eab49c368d1a205707a1ec88aa13f0f2ecdfe944", - var output_no: Int? = null // 0, - var script_asm: String? = null // "OP_DUP OP_HASH160 8541eb0593bb19c3755198e7d2a71e134da21a97 OP_EQUALVERIFY OP_CHECKSIG", - var script_hex: String? = null // "76a9148541eb0593bb19c3755198e7d2a71e134da21a9788ac", - var value: String? = null // "11.38404832", - var confirmations: Long? = null - var time: Long? = null// : 1555509495 - } - - var network: String? = null - var address: String? = null - var txs: Array? = null - } - - var status: String? = null - var data: Data? = null - - } - - class SendTx { - class Data { - var network: String? = null - var txid: String? = null - var tx_hex: String? = null - } - var status: String? = null - var data: Data? = null - } - - class GetTx { - class Data { - // restricted data - var network: String? = null - var address: String? = null - var txid: String? = null - var tx_hex: String? = null - } - - var status: String? = null - var data: Data? = null - } - - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/data/network/model/TezosBody.kt b/app/src/main/java/com/tangem/data/network/model/TezosBody.kt deleted file mode 100644 index 16bf34b145..0000000000 --- a/app/src/main/java/com/tangem/data/network/model/TezosBody.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.tangem.data.network.model - -data class TezosForgeBody( - val branch: String, - val contents: List -) - -data class TezosOperationContent( - val kind: String, - val source: String, - val fee: String, - val counter: String, - val gas_limit: String, - val storage_limit: String, - val public_key: String? = null, - val destination: String? = null, - val amount: String? = null -) - -data class TezosPreapplyBody( - val protocol: String, - val branch: String, - val contents: List, - val signature: String -) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/data/network/model/TezosResponse.kt b/app/src/main/java/com/tangem/data/network/model/TezosResponse.kt deleted file mode 100644 index db3bece398..0000000000 --- a/app/src/main/java/com/tangem/data/network/model/TezosResponse.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.tangem.data.network.model - -import com.google.gson.annotations.SerializedName - -data class TezosAccountResponse( - @SerializedName("balance") - var balance: Long? = null, - - @SerializedName("counter") - var counter: Long? = null -) - -data class TezosHeaderResponse( - @SerializedName("protocol") - var protocol: String? = null, - - @SerializedName("hash") - var hash: String? = null -) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/data/network/model/TokenEmvBody.kt b/app/src/main/java/com/tangem/data/network/model/TokenEmvBody.kt deleted file mode 100644 index ea89dd88d9..0000000000 --- a/app/src/main/java/com/tangem/data/network/model/TokenEmvBody.kt +++ /dev/null @@ -1,36 +0,0 @@ -package com.tangem.data.network.model - -import com.google.gson.annotations.SerializedName - -data class TokenEmvTransferBody( - val CID: String, - val publicKey: String, - val amount: String, - val currency: String, - val recipient: String, - @SerializedName("fee_limit") - val feeLimit: String, - val sequence: Int, - val signature: String -) - -data class TokenEmvTransferAnswer( - val error: String?, - val errorCode: Int?, - val success: Boolean?, - val tx_id: String?, - val blockchain_tx_id: String? -) - -data class TokenEmvGetTransferFeeBody( - val CID: String, - val publicKey: String -) - -data class TokenEmvGetTransferFeeAnswer( - val error: String?, - val errorCode: Int?, - val success: Boolean?, - val fee: String?, - val currency: String? -) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/di/AppModule.kt b/app/src/main/java/com/tangem/di/AppModule.kt deleted file mode 100644 index 6fa481881b..0000000000 --- a/app/src/main/java/com/tangem/di/AppModule.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.tangem.di - -import android.app.Application -import android.content.Context -import dagger.Module -import javax.inject.Singleton -import dagger.Provides - -@Module -class AppModule { - - @Provides - @Singleton - fun provideContext(application: Application): Context { - return application - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/di/NavigatorModule.kt b/app/src/main/java/com/tangem/di/NavigatorModule.kt deleted file mode 100644 index b0bfb00afc..0000000000 --- a/app/src/main/java/com/tangem/di/NavigatorModule.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.tangem.di - -import com.tangem.ui.dialog.WaitSecurityDelayDialogNew -import dagger.Module -import dagger.Provides -import javax.inject.Singleton - -@Module -internal class NavigatorModule { - - @Singleton - @Provides - fun provideToastHelper(): ToastHelper { - return ToastHelper() - } - - @Singleton - @Provides - fun provideWaitSecurityDelayDialogNew(): WaitSecurityDelayDialogNew { - return WaitSecurityDelayDialogNew() - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/di/NetworkComponent.kt b/app/src/main/java/com/tangem/di/NetworkComponent.kt deleted file mode 100644 index 3d100a99e6..0000000000 --- a/app/src/main/java/com/tangem/di/NetworkComponent.kt +++ /dev/null @@ -1,55 +0,0 @@ -package com.tangem.di - -import com.tangem.data.network.Server -import dagger.Component -import retrofit2.Retrofit -import java.net.Socket -import javax.inject.Named -import javax.inject.Singleton - -@Singleton -@Component(modules = [NetworkModule::class]) -interface NetworkComponent { - - @get:Named(Server.ApiInfura.URL_INFURA) - val retrofitInfura: Retrofit - - @get:Named(Server.ApiInfuraTestnet.URL_INFURA_TESTNET) - val retrofitInfuraTestnet: Retrofit - - @get:Named(Server.ApiInfuraRopsten.URL_INFURA_ROPSTEN) - val retrofitInfuraRopsten: Retrofit - - @get:Named(Server.ApiMaticTesnet.URL_MATIC_TESTNET) - val retrofitMaticTesnet: Retrofit - - @get:Named(Server.ApiEstimatefee.URL_ESTIMATEFEE) - val retrofitEstimatefee: Retrofit - - @get:Named(Server.ApiCoinmarket.URL_COINMARKET) - val retrofitCoinmarketcap: Retrofit - - @get:Named(Server.ApiUpdateVersion.URL_UPDATE_VERSION) - val retrofitGitHubUserContent: Retrofit - - @get:Named(Server.ApiRootstock.URL_ROOTSTOCK) - val retrofitRootstock: Retrofit - - @get:Named(Server.ApiBlockcypher.URL_BLOCKCYPHER) - val retrofitBlockcypher: Retrofit - - @get:Named(Server.ApiSoChain.URL) - val retrofitSoChain: Retrofit - - @get:Named(Server.ApiBlockchainInfo.URL_BLOCKCHAININFO) - val retrofitBlockchainInfo: Retrofit - - @get:Named(Server.ApiDucatus.URL_DUCATUS) - val retrofitDucatus: Retrofit - - @get:Named(Server.ApiBlockchair.URL_BLOCKCHAIR) - val retrofitBlockchair: Retrofit - - @get:Named("socket") - val socket: Socket -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/di/NetworkModule.kt b/app/src/main/java/com/tangem/di/NetworkModule.kt deleted file mode 100644 index 339a0ca5d8..0000000000 --- a/app/src/main/java/com/tangem/di/NetworkModule.kt +++ /dev/null @@ -1,216 +0,0 @@ -package com.tangem.di - -import com.jakewharton.retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory -import com.tangem.data.network.Server -import com.tangem.wallet.BuildConfig -import dagger.Module -import dagger.Provides -import okhttp3.OkHttpClient -import okhttp3.logging.HttpLoggingInterceptor -import retrofit2.Retrofit -import retrofit2.converter.gson.GsonConverterFactory -import retrofit2.converter.scalars.ScalarsConverterFactory -import java.io.IOException -import java.net.InetSocketAddress -import java.net.Socket -import java.net.SocketException -import javax.inject.Named -import javax.inject.Singleton - -@Module -internal class NetworkModule { - - @Singleton - @Provides - @Named(Server.ApiInfura.URL_INFURA) - fun provideRetrofitInfura(): Retrofit { - val builder = Retrofit.Builder() - .baseUrl(Server.ApiInfura.URL_INFURA) - .addConverterFactory(GsonConverterFactory.create()) - if (BuildConfig.DEBUG) - builder.client(createOkHttpClient()) - return builder.build() - } - - @Singleton - @Provides - @Named(Server.ApiInfuraTestnet.URL_INFURA_TESTNET) - fun provideRetrofitInfuraTestnet(): Retrofit { - val builder = Retrofit.Builder() - .baseUrl(Server.ApiInfuraTestnet.URL_INFURA_TESTNET) - .addConverterFactory(GsonConverterFactory.create()) - if (BuildConfig.DEBUG) - builder.client(createOkHttpClient()) - return builder.build() - } - - @Singleton - @Provides - @Named(Server.ApiInfuraRopsten.URL_INFURA_ROPSTEN) - fun provideRetrofitInfuraRopsten(): Retrofit { - val builder = Retrofit.Builder() - .baseUrl(Server.ApiInfuraRopsten.URL_INFURA_ROPSTEN) - .addConverterFactory(GsonConverterFactory.create()) - if (BuildConfig.DEBUG) - builder.client(createOkHttpClient()) - return builder.build() - } - - @Singleton - @Provides - @Named(Server.ApiRootstock.URL_ROOTSTOCK) - fun provideRetrofitRootstock(): Retrofit { - val builder = Retrofit.Builder() - .baseUrl(Server.ApiRootstock.URL_ROOTSTOCK) - .addConverterFactory(GsonConverterFactory.create()) - if (BuildConfig.DEBUG) - builder.client(createOkHttpClient()) - return builder.build() - } - - @Singleton - @Provides - @Named(Server.ApiMaticTesnet.URL_MATIC_TESTNET) - fun provideRetrofitMaticTestnet(): Retrofit { - val builder = Retrofit.Builder() - .baseUrl(Server.ApiMaticTesnet.URL_MATIC_TESTNET) - .addConverterFactory(GsonConverterFactory.create()) - if (BuildConfig.DEBUG) - builder.client(createOkHttpClient()) - return builder.build() - } - - @Singleton - @Provides - @Named(Server.ApiEstimatefee.URL_ESTIMATEFEE) - fun provideRetrofitEstimatefee(): Retrofit { - val builder = Retrofit.Builder() - .baseUrl(Server.ApiEstimatefee.URL_ESTIMATEFEE) - .addConverterFactory(GsonConverterFactory.create()) - if (BuildConfig.DEBUG) - builder.client(createOkHttpClient()) - return builder.build() - } - - @Singleton - @Provides - @Named(Server.ApiUpdateVersion.URL_UPDATE_VERSION) - fun provideGithubusercontent(): Retrofit { - val builder = Retrofit.Builder() - .baseUrl(Server.ApiUpdateVersion.URL_UPDATE_VERSION) - .addConverterFactory(GsonConverterFactory.create()) - .client(createOkHttpClient()) - if (BuildConfig.DEBUG) - builder.client(createOkHttpClient()) - return builder.build() - } - - @Singleton - @Provides - @Named(Server.ApiCoinmarket.URL_COINMARKET) - fun provideRetrofitCoinmarketcap(): Retrofit { - val builder = Retrofit.Builder() - .baseUrl(Server.ApiCoinmarket.URL_COINMARKET) - .addConverterFactory(GsonConverterFactory.create()) - .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) -// if (BuildConfig.DEBUG) -// builder.client(createOkHttpClient()) - return builder.build() - } - - @Singleton - @Provides - @Named(Server.ApiBlockcypher.URL_BLOCKCYPHER) - fun provideRetrofitBlockcypher(): Retrofit { - val builder = Retrofit.Builder() - .baseUrl(Server.ApiBlockcypher.URL_BLOCKCYPHER) - .addConverterFactory(GsonConverterFactory.create()) - .client(createOkHttpClient()) - if (BuildConfig.DEBUG) - builder.client(createOkHttpClient()) - return builder.build() - } - - @Singleton - @Provides - @Named(Server.ApiSoChain.URL) - fun provideRetrofitSoChain(): Retrofit { - val builder = Retrofit.Builder() - .baseUrl(Server.ApiSoChain.URL) - .addConverterFactory(GsonConverterFactory.create()) - if (BuildConfig.DEBUG) - builder.client(createOkHttpClient()) - return builder.build() - } - - @Singleton - @Provides - @Named(Server.ApiBlockchainInfo.URL_BLOCKCHAININFO) - fun provideRetrofitBlockchainInfo(): Retrofit { - val builder = Retrofit.Builder() - .baseUrl(Server.ApiBlockchainInfo.URL_BLOCKCHAININFO) - .addConverterFactory(GsonConverterFactory.create()) - .addConverterFactory(ScalarsConverterFactory.create()) - .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) - if (BuildConfig.DEBUG) - builder.client(createOkHttpClient()) - return builder.build() - } - - @Singleton - @Provides - @Named(Server.ApiDucatus.URL_DUCATUS) - fun provideRetrofitDucatus(): Retrofit { - val builder = Retrofit.Builder() - .baseUrl(Server.ApiDucatus.URL_DUCATUS) - .addConverterFactory(GsonConverterFactory.create()) - .addConverterFactory(ScalarsConverterFactory.create()) - .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) - if (BuildConfig.DEBUG) - builder.client(createOkHttpClient()) - return builder.build() - } - - @Singleton - @Provides - @Named(Server.ApiBlockchair.URL_BLOCKCHAIR) - fun provideRetrofitBlockchair(): Retrofit { - val builder = Retrofit.Builder() - .baseUrl(Server.ApiBlockchair.URL_BLOCKCHAIR) - .addConverterFactory(GsonConverterFactory.create()) - .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) - if (BuildConfig.DEBUG) - builder.client(createOkHttpClient()) - return builder.build() - } - - private fun createOkHttpClient(): OkHttpClient { - return OkHttpClient.Builder().addInterceptor(createHttpLoggingInterceptor()).build() - } - - private fun createHttpLoggingInterceptor(): HttpLoggingInterceptor { - val logging = HttpLoggingInterceptor() - logging.level = HttpLoggingInterceptor.Level.BODY - return logging - } - - @Provides - @Named("socket") - fun provideSocket(): Socket { - val socket = Socket() - try { - socket.soTimeout = 2000 - try { - socket.bind(InetSocketAddress(0)) - } catch (e: IOException) { - e.printStackTrace() - } - - } catch (e: SocketException) { - e.printStackTrace() - } - - return socket - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/di/ToastHelper.kt b/app/src/main/java/com/tangem/di/ToastHelper.kt deleted file mode 100644 index e18d8f2734..0000000000 --- a/app/src/main/java/com/tangem/di/ToastHelper.kt +++ /dev/null @@ -1,70 +0,0 @@ -package com.tangem.di - -import android.content.ActivityNotFoundException -import android.content.Context -import android.content.Intent -import android.net.Uri -import android.view.ViewGroup -import android.widget.Toast -import com.google.android.material.snackbar.Snackbar -import com.tangem.Constant -import com.tangem.wallet.R -import java.util.* - -class ToastHelper { - fun showSnackbarUpdateVersion(context: Context, vg: ViewGroup, versionName: String) { - Snackbar.make(vg, String.format(context.getString(R.string.main_screen_new_version_toast), versionName), Snackbar.LENGTH_INDEFINITE) - .setAction(R.string.main_screen_btn_update) { - try { - val intent = Intent(Intent.ACTION_VIEW) - intent.data = Uri.parse(Constant.URL_TANGEM) - context.startActivity(intent) - } catch (e: ActivityNotFoundException) { - e.printStackTrace() - } - }.show() - } - - fun showSnackbarSuccess(context: Context, vg: ViewGroup, message: String) { - val snackbar = Snackbar.make(vg, message, Snackbar.LENGTH_INDEFINITE) - val snackView = snackbar.view - snackView.setBackgroundColor(context.resources.getColor(R.color.msg_okay)) - snackbar.setAction(R.string.general_ok) { - snackbar.dismiss() - } - snackbar.show() - } - - fun showSnackbarError(context: Context, vg: ViewGroup, message: String) { - val snackbar = Snackbar.make(vg, message, Snackbar.LENGTH_INDEFINITE) - val snackView = snackbar.view - snackView.setBackgroundColor(context.resources.getColor(R.color.msg_err)) - snackbar.setAction(R.string.general_ok) { - snackbar.dismiss() - } - snackbar.show() - } - - fun showSnackbarWarning(context: Context, vg: ViewGroup, message: String) { - val snackbar = Snackbar.make(vg, message, Snackbar.LENGTH_LONG) - val snackView = snackbar.view - snackView.setBackgroundColor(context.resources.getColor(R.color.msg_err)) - snackbar.show() - } - - private var singleToast: Toast? = null - private var showTime: Date = Date() - - fun showSingleToast(context: Context?, text: String) { - if (singleToast == null || !singleToast!!.view.isShown || showTime.time + 2000 < Date().time) { - if (singleToast != null) - singleToast!!.cancel() - if (context != null) { - singleToast = Toast.makeText(context, text, Toast.LENGTH_LONG) - singleToast!!.show() - showTime = Date() - } - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/di/ToastHelperComponent.kt b/app/src/main/java/com/tangem/di/ToastHelperComponent.kt deleted file mode 100644 index b98d944333..0000000000 --- a/app/src/main/java/com/tangem/di/ToastHelperComponent.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.tangem.di - -import com.tangem.ui.activity.MainActivity -import dagger.Component -import javax.inject.Singleton - -@Singleton -@Component(modules = [NavigatorModule::class]) -interface ToastHelperComponent { - - fun inject(activity: MainActivity) - -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt new file mode 100644 index 0000000000..ffa1f2aa54 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -0,0 +1,105 @@ +package com.tangem.tap + +import android.content.Intent +import android.content.pm.ActivityInfo +import android.nfc.NfcAdapter +import android.nfc.Tag +import android.os.Bundle +import android.os.Handler +import androidx.appcompat.app.AppCompatActivity +import com.tangem.CardFilter +import com.tangem.Config +import com.tangem.Log +import com.tangem.TangemSdk +import com.tangem.common.extensions.CardType +import com.tangem.tangem_sdk_new.extensions.init +import com.tangem.tap.common.redux.NotificationsHandler +import com.tangem.tap.common.redux.navigation.AppScreen +import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.domain.TangemSdkManager +import com.tangem.tap.features.home.redux.HomeAction +import com.tangem.wallet.R +import kotlinx.android.synthetic.main.activity_main.* +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import java.io.PrintWriter +import java.io.StringWriter +import java.lang.ref.WeakReference +import java.util.* +import kotlin.coroutines.CoroutineContext + +lateinit var tangemSdk: TangemSdk +lateinit var tangemSdkManager: TangemSdkManager +var notificationsHandler: NotificationsHandler? = null + +private val coroutineContext: CoroutineContext + get() = Job() + Dispatchers.IO + initCoroutineExceptionHandler() +val scope = CoroutineScope(coroutineContext) + +private val mainCoroutineContext: CoroutineContext + get() = Job() + Dispatchers.Main +val mainScope = CoroutineScope(mainCoroutineContext) + +private fun initCoroutineExceptionHandler(): CoroutineExceptionHandler { + return CoroutineExceptionHandler { _, throwable -> + val sw = StringWriter() + throwable.printStackTrace(PrintWriter(sw)) + val exceptionAsString: String = sw.toString() + Log.e("TangemSdk", exceptionAsString) + throw throwable + } +} + +class MainActivity : AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; + store.dispatch(NavigationAction.ActivityCreated(WeakReference(this))) + + tangemSdk = TangemSdk.init( + this, Config(cardFilter = CardFilter(EnumSet.allOf(CardType::class.java))) + ) + tangemSdkManager = TangemSdkManager(this) + } + + override fun onResume() { + super.onResume() + notificationsHandler = NotificationsHandler(fragment_container) + if (supportFragmentManager.backStackEntryCount == 0 || + store.state.globalState.scanNoteResponse == null) { + store.dispatch(HomeAction.CheckIfFirstLaunch) + store.dispatch(NavigationAction.NavigateTo(AppScreen.Home)) + } + handleBackgroundScan(intent) + } + + override fun onNewIntent(intent: Intent?) { + super.onNewIntent(intent) + Handler(mainLooper).postDelayed( { handleBackgroundScan(intent) } , 200) + } + + private fun handleBackgroundScan(intent: Intent?) { + if (intent != null && (NfcAdapter.ACTION_TECH_DISCOVERED == intent.action || + NfcAdapter.ACTION_NDEF_DISCOVERED == intent.action + )) { + val tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG) + if (tag != null) { + intent.action = null + store.dispatch(HomeAction.ReadCard) + } + } + } + + override fun onStop() { + notificationsHandler = null + super.onStop() + } + + override fun onDestroy() { + store.dispatch(NavigationAction.ActivityDestroyed) + super.onDestroy() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt new file mode 100644 index 0000000000..b19bfc730e --- /dev/null +++ b/app/src/main/java/com/tangem/tap/TapApplication.kt @@ -0,0 +1,30 @@ +package com.tangem.tap + +import android.app.Application +import com.tangem.tap.common.images.PicassoHelper +import com.tangem.tap.common.redux.AppState +import com.tangem.tap.common.redux.appReducer +import com.tangem.tap.network.NetworkConnectivity +import com.tangem.tap.persistence.PreferencesStorage +import com.tangem.wallet.BuildConfig +import org.rekotlin.Store +import timber.log.Timber + +val store = Store( + reducer = ::appReducer, + middleware = AppState.getMiddleware(), + state = AppState() +) +lateinit var preferencesStorage: PreferencesStorage + +class TapApplication : Application() { + override fun onCreate() { + super.onCreate() + if (BuildConfig.DEBUG) { + Timber.plant(Timber.DebugTree()) + } + NetworkConnectivity.createInstance(store, this) + preferencesStorage = PreferencesStorage(this) + PicassoHelper.initPicassoWithCaching(this) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/TapConfig.kt b/app/src/main/java/com/tangem/tap/TapConfig.kt new file mode 100644 index 0000000000..483f9e8306 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/TapConfig.kt @@ -0,0 +1,6 @@ +package com.tangem.tap + +object TapConfig { + const val usePayId: Boolean = true + const val coinMarketCapKey = "f6622117-c043-47a0-8975-9d673ce484de" +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/CurrencyConverter.kt b/app/src/main/java/com/tangem/tap/common/CurrencyConverter.kt new file mode 100644 index 0000000000..0ca9b929bd --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/CurrencyConverter.kt @@ -0,0 +1,36 @@ +package com.tangem.tap.common + +import com.tangem.common.extensions.isZero +import com.tangem.tap.common.extensions.scaleToFiat +import java.math.BigDecimal +import java.math.RoundingMode + +/** +[REDACTED_AUTHOR] + */ +class CurrencyConverter( + private val rateValue: BigDecimal, + private val decimals: Int +) { + private val roundingMode = RoundingMode.DOWN + + fun toFiat(crypto: BigDecimal, fiatDecimals: Int = 2): BigDecimal { + return toFiatUnscaled(crypto).setScale(fiatDecimals, roundingMode) + } + + fun toFiatUnscaled(crypto: BigDecimal): BigDecimal { + return rateValue.multiply(crypto).setScale(decimals, roundingMode) + } + + fun toFiatWithPrecision(crypto: BigDecimal): BigDecimal { + return toFiatUnscaled(crypto).scaleToFiat(true) + } + + fun toCrypto(fiat: BigDecimal): BigDecimal { + if (fiat.isZero()) return fiat + + val scaledRateValue = rateValue.setScale(decimals, roundingMode) + val scaledFiat = fiat.setScale(decimals, roundingMode) + return scaledFiat.divide(scaledRateValue, RoundingMode.UP) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/KeyboardObserver.kt b/app/src/main/java/com/tangem/tap/common/KeyboardObserver.kt new file mode 100644 index 0000000000..69581a7a34 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/KeyboardObserver.kt @@ -0,0 +1,61 @@ +package com.tangem.tap.common + +import android.app.Activity +import android.graphics.Rect +import android.util.DisplayMetrics +import android.view.ViewTreeObserver.OnGlobalLayoutListener +import kotlin.math.absoluteValue + +class KeyboardObserver(activity: Activity) { + + private val decorView = activity.window.decorView + private val windowManager = activity.windowManager + private val originalWindowHeight: Int = getWindowHeight() + private val onGlobalLayoutListener: OnGlobalLayoutListener = OnGlobalLayoutListener { onGlobalLayout() } + + private var onKeyboardListener: ((Boolean) -> Unit)? = null + private var lastIsShow = false + private var lastWindowHeight = getWindowHeight() + + fun registerListener(listener: (Boolean) -> Unit) { + decorView.viewTreeObserver.addOnGlobalLayoutListener(onGlobalLayoutListener) + onKeyboardListener = listener + } + + fun unregisterListener() { + decorView.viewTreeObserver.removeOnGlobalLayoutListener(onGlobalLayoutListener) + onKeyboardListener = null + } + + private fun getWindowHeight() = Rect().apply { decorView.getWindowVisibleDisplayFrame(this) }.bottom + + private fun onGlobalLayout() { + val currentWindowHeight = getWindowHeight() + if (isSoftKeyChanged()) { + lastWindowHeight = currentWindowHeight + return + } + + lastWindowHeight = currentWindowHeight + val isShow = originalWindowHeight != currentWindowHeight + if (lastIsShow == isShow) return + + lastIsShow = isShow + onKeyboardListener?.invoke(isShow) + } + + private fun isSoftKeyChanged() = ((lastWindowHeight - getWindowHeight()).absoluteValue) == getSoftKeyButtonHeight() + + private fun getSoftKeyButtonHeight(): Int { + val applicationDisplayHeight = DisplayMetrics().apply { + windowManager.defaultDisplay.getMetrics(this) + }.heightPixels + + val realDisplayHeight = DisplayMetrics().apply { + windowManager.defaultDisplay.getRealMetrics(this) + }.heightPixels + + return realDisplayHeight - applicationDisplayHeight + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/entities/Button.kt b/app/src/main/java/com/tangem/tap/common/entities/Button.kt new file mode 100644 index 0000000000..986a170564 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/entities/Button.kt @@ -0,0 +1,3 @@ +package com.tangem.tap.common.entities + +open class Button(val enabled: Boolean) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/entities/TapCurrency.kt b/app/src/main/java/com/tangem/tap/common/entities/TapCurrency.kt new file mode 100644 index 0000000000..1aa3f5c351 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/entities/TapCurrency.kt @@ -0,0 +1,10 @@ +package com.tangem.tap.common.entities + +/** +[REDACTED_AUTHOR] + */ +class TapCurrency { + companion object{ + const val DEFAULT_FIAT_CURRENCY = "USD" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Bitmap.kt b/app/src/main/java/com/tangem/tap/common/extensions/Bitmap.kt new file mode 100644 index 0000000000..e8b3891ce9 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/extensions/Bitmap.kt @@ -0,0 +1,15 @@ +package com.tangem.tap.common.extensions + +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import java.io.ByteArrayOutputStream + +fun Bitmap.toByteArray(): ByteArray { + val stream = ByteArrayOutputStream() + this.compress(Bitmap.CompressFormat.JPEG, 20, stream) + return stream.toByteArray() +} + +fun ByteArray.toBitmap(): Bitmap { + return BitmapFactory.decodeByteArray(this, 0, this.size) +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Int.kt b/app/src/main/java/com/tangem/tap/common/extensions/Int.kt new file mode 100644 index 0000000000..555dc252bd --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/extensions/Int.kt @@ -0,0 +1,3 @@ +package com.tangem.tap.common.extensions + +fun Int.isEven() = this and 1 == 0 \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt new file mode 100644 index 0000000000..03246d0461 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt @@ -0,0 +1,46 @@ +package com.tangem.tap.common.extensions + +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentActivity +import androidx.fragment.app.FragmentManager +import com.tangem.tap.common.redux.navigation.AppScreen +import com.tangem.tap.features.details.ui.DetailsConfirmFragment +import com.tangem.tap.features.details.ui.DetailsFragment +import com.tangem.tap.features.details.ui.DetailsSecurityFragment +import com.tangem.tap.features.home.HomeFragment +import com.tangem.tap.features.send.ui.SendFragment +import com.tangem.tap.features.wallet.ui.WalletFragment +import com.tangem.wallet.R + +fun FragmentActivity.openFragment(screen: AppScreen, addToBackStack: Boolean = true) { + val transaction = this.supportFragmentManager.beginTransaction() + .replace( + R.id.fragment_container, + fragmentFactory(screen), + screen.name + ) + if (addToBackStack && screen != AppScreen.Home) transaction.addToBackStack(null) + transaction.commit(); +} + +fun FragmentActivity.popBackTo(screen: AppScreen?, inclusive: Boolean = false) { + val inclusiveFlag = if (inclusive) FragmentManager.POP_BACK_STACK_INCLUSIVE else 0 + this.supportFragmentManager.popBackStack(screen?.name, inclusiveFlag) +} + +fun FragmentActivity.getPreviousScreen(): AppScreen? { + val indexOfLastFragment = this.supportFragmentManager.backStackEntryCount - 1 + val tag = this.supportFragmentManager.getBackStackEntryAt(indexOfLastFragment).name + return tag?.let { AppScreen.valueOf(tag) } +} + +private fun fragmentFactory(screen: AppScreen): Fragment { + return when (screen) { + AppScreen.Home -> HomeFragment() + AppScreen.Wallet -> WalletFragment() + AppScreen.Send -> SendFragment() + AppScreen.Details -> DetailsFragment() + AppScreen.DetailsConfirm -> DetailsConfirmFragment() + AppScreen.DetailsSecurity -> DetailsSecurityFragment() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Specific.kt b/app/src/main/java/com/tangem/tap/common/extensions/Specific.kt new file mode 100644 index 0000000000..925969d32f --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/extensions/Specific.kt @@ -0,0 +1,97 @@ +package com.tangem.tap.common.extensions + +import android.graphics.Bitmap +import android.graphics.Color +import com.google.zxing.BarcodeFormat +import com.google.zxing.EncodeHintType +import com.google.zxing.qrcode.QRCodeWriter +import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel +import com.tangem.common.extensions.isZero +import com.tangem.tap.common.redux.global.FiatCurrencyName +import com.tangem.tap.network.coinmarketcap.FiatCurrency +import java.math.BigDecimal +import java.math.RoundingMode +import java.text.DecimalFormat +import java.text.DecimalFormatSymbols +import java.util.* + + +fun String.toQrCode(): Bitmap { + val hintMap = Hashtable() + hintMap[EncodeHintType.ERROR_CORRECTION] = ErrorCorrectionLevel.M // H = 30% damage + hintMap[EncodeHintType.MARGIN] = 2 + + val qrCodeWriter = QRCodeWriter() + + val size = 256 + + val bitMatrix = qrCodeWriter.encode(this, BarcodeFormat.QR_CODE, size, size, hintMap) + val width = bitMatrix.width + val bmp = Bitmap.createBitmap(width, width, Bitmap.Config.RGB_565) + for (x in 0 until width) { + for (y in 0 until width) { + bmp.setPixel(y, x, if (bitMatrix.get(x, y)) Color.BLACK else Color.WHITE) + } + } + return bmp +} + +fun BigDecimal.toFormattedString(decimals: Int): String { + val symbols = DecimalFormatSymbols(Locale.US) + symbols.decimalSeparator = '.' + val df = DecimalFormat() + df.decimalFormatSymbols = symbols + df.maximumFractionDigits = decimals + df.minimumFractionDigits = 0 + df.isGroupingUsed = false + val bd = BigDecimal(unscaledValue(), scale()) + bd.setScale(decimals, RoundingMode.DOWN) + return df.format(bd) +} + +fun BigDecimal.toFormattedCurrencyString(decimals: Int, currency: String): String { + return "${this.toFormattedString(decimals)} $currency" +} + +fun BigDecimal.toFiatString(rateValue: BigDecimal, fiatCurrencyName: FiatCurrencyName): String? { + var fiatValue = rateValue.multiply(this) + fiatValue = fiatValue.setScale(2, RoundingMode.DOWN) + return "≈ ${fiatCurrencyName}  $fiatValue" +} + +fun FiatCurrency.toFormattedString(): String = "${this.name} (${this.symbol}) - ${this.sign}" + +fun BigDecimal.stripZeroPlainString(): String = this.stripTrailingZeros().toPlainString() + +// 0.00 -> 0.00 +// 0.00002345 -> 0.00002 +// 1.00002345 -> 1.00 +// 1.45002345 -> 1.45 +fun BigDecimal.scaleToFiat(applyPrecision: Boolean = false): BigDecimal { + if (this.isZero()) return this + + val scaledFiat = this.setScale(2, RoundingMode.DOWN) + return if (scaledFiat.isZero() && applyPrecision) this.setPrecision(1) + else scaledFiat + +} + +fun BigDecimal.setPrecision(precision: Int, roundingMode: RoundingMode = RoundingMode.DOWN): BigDecimal { + if (precision == precision() || scale() <= precision) return this + return this.setScale(scale() - precision() + precision, roundingMode) +} + +fun BigDecimal.isPositive(): Boolean = this.compareTo(BigDecimal.ZERO) == 1 +fun BigDecimal.isNegative(): Boolean = this.compareTo(BigDecimal.ZERO) == -1 +fun BigDecimal.isGreaterThan(value: BigDecimal): Boolean = this.compareTo(value) == 1 +fun BigDecimal.isLessThan(value: BigDecimal): Boolean = this.compareTo(value) == -1 + +fun BigDecimal.isGreaterThanOrEqual(value: BigDecimal): Boolean { + val compareResult = this.compareTo(value) + return compareResult == 1 || compareResult == 0 +} + +fun BigDecimal.isLessThanOrEqual(value: BigDecimal): Boolean { + val compareResult = this.compareTo(value) + return compareResult == -1 || compareResult == 0 +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/extensions/TextView.kt b/app/src/main/java/com/tangem/tap/common/extensions/TextView.kt new file mode 100644 index 0000000000..5774e94b61 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/extensions/TextView.kt @@ -0,0 +1,59 @@ +package com.tangem.tap.common.extensions + +import android.view.View +import android.widget.EditText +import android.widget.TextSwitcher +import android.widget.TextView +import com.google.android.material.textfield.TextInputLayout + +/** +[REDACTED_AUTHOR] + */ +fun TextView.update(text: String?) { + if (this.text?.toString() != text) this.text = text +} + +fun EditText.update(text: String?) { + if (this.text?.toString() == text) return + + val textLength = text?.length ?: 0 + + //prevent cursor jumping while editing a text + val cursorPosition = if (selectionEnd > textLength) textLength else selectionEnd + this.setText(text) + if (!isFocused || textLength == 0) return + + if (cursorPosition == 0) setSelection(textLength) + else setSelection(cursorPosition) +} + +fun EditText.setOnImeActionListener(action: Int, handler: (EditText) -> Unit) { + this.setOnEditorActionListener { view, actionId, event -> + if (actionId == action) { + handler.invoke(this) + return@setOnEditorActionListener true + } + return@setOnEditorActionListener false + } +} + +fun TextSwitcher.update(text: CharSequence?) { + val textView = this.currentView as? TextView ?: return + + if (textView.text?.toString() != text?.toString()) this.setText(text) +} + +// By default the TextInputLayout didn't activates the error state if the message is empty or null +fun TextInputLayout.enableError(enable: Boolean, errorMessage: String? = null) { + if (enable) { + if (errorMessage == null || errorMessage.isEmpty()) { + error = "Any message" + if (childCount == 2) getChildAt(1).visibility = View.GONE + } else { + error = errorMessage + } + } else { + error = null + isErrorEnabled = false + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/extensions/UI.kt b/app/src/main/java/com/tangem/tap/common/extensions/UI.kt new file mode 100644 index 0000000000..11d5aa6ca4 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/extensions/UI.kt @@ -0,0 +1,144 @@ +package com.tangem.tap.common.extensions + +import android.app.Activity +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Context +import android.content.Intent +import android.graphics.drawable.Drawable +import android.os.Build +import android.text.Spannable +import android.text.style.ForegroundColorSpan +import android.util.TypedValue +import android.view.View +import android.view.ViewGroup +import android.view.inputmethod.InputMethodManager +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes +import androidx.core.content.ContextCompat +import androidx.core.text.toSpannable +import androidx.fragment.app.Fragment +import com.google.android.material.card.MaterialCardView + +fun Fragment.getDrawable(@DrawableRes drawableResId: Int): Drawable? { + return ContextCompat.getDrawable(requireContext(), drawableResId) +} + +fun Context.getDrawableCompat(@DrawableRes drawableResId: Int): Drawable? { + return ContextCompat.getDrawable(this, drawableResId) +} + +fun View.getString(@StringRes id: Int): String { + return context.getString(id) +} + +fun View.show(show: Boolean, invokeBeforeStateChanged: (() -> Unit)? = null) { + return if (show) this.show(invokeBeforeStateChanged) + else this.hide(invokeBeforeStateChanged) +} + +fun View.show(invokeBeforeStateChanged: (() -> Unit)? = null) { + if (this.visibility == View.VISIBLE) return + + invokeBeforeStateChanged?.invoke() + this.visibility = View.VISIBLE +} + +fun View.hide(invokeBeforeStateChanged: (() -> Unit)? = null) { + if (this.visibility == View.GONE) return + + invokeBeforeStateChanged?.invoke() + this.visibility = View.GONE +} + +fun View.makeInvisible() { + if (this.visibility == View.INVISIBLE) return + this.visibility = View.INVISIBLE +} + +fun Context.dpToPixels(dp: Int): Int = + TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, dp.toFloat(), this.resources.displayMetrics + ).toInt() + +fun MaterialCardView.setMargins( + marginLeftDp: Int = 16, + marginTopDp: Int = 8, + marginRightDp: Int = 16, + marginBottomDp: Int = 8 +) { + val params = this.layoutParams + (params as ViewGroup.MarginLayoutParams).setMargins( + context.dpToPixels(marginLeftDp), + context.dpToPixels(marginTopDp), + context.dpToPixels(marginRightDp), + context.dpToPixels(marginBottomDp) + ) + this.layoutParams = params +} + +fun Activity.setSystemBarTextColor(setTextDark: Boolean) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + val flags = this.window.decorView.systemUiVisibility + // Update the SystemUiVisibility dependening on whether we want a Light or Dark theme. + this.window.decorView.systemUiVisibility = + if (setTextDark) { + flags and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv() + } else { + flags or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR + } + } +} + + + +fun String.colorSegment( + context: Context, + color: Int, + startIndex: Int = 0, + endIndex: Int = this.length +): Spannable { + return this.toSpannable() + .also { spannable -> + spannable.setSpan( + ForegroundColorSpan(ContextCompat.getColor(context, color)), + startIndex, + endIndex, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE + ) + } +} + +fun View.hideKeyboard() { + val inputMethodManager = context.getSystemService(android.content.Context.INPUT_METHOD_SERVICE) as? InputMethodManager + inputMethodManager?.hideSoftInputFromWindow(this.windowToken, 0) +} + +fun Context.copyToClipboard(value: Any, label: String = "") { + val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager ?: return + + val clip: ClipData = ClipData.newPlainText(label, value.toString()) + clipboard.setPrimaryClip(clip) +} + +fun Context.getFromClipboard(default: CharSequence? = null): CharSequence? { + val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager ?: return default + val clipData = clipboard.primaryClip ?: return default + if (clipData.itemCount == 0) return default + + return clipData.getItemAt(0).text +} + +fun Context.shareText(text: String) { + val sendIntent: Intent = Intent().apply { + action = Intent.ACTION_SEND + putExtra(Intent.EXTRA_TEXT, text) + type = "text/plain" + } + val shareIntent = Intent.createChooser(sendIntent, null) + startActivity(shareIntent) +} + +fun Fragment.shareText(text: String) { + requireContext().shareText(text) +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/extensions/ViewGroup.kt b/app/src/main/java/com/tangem/tap/common/extensions/ViewGroup.kt new file mode 100644 index 0000000000..54d98f110d --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/extensions/ViewGroup.kt @@ -0,0 +1,28 @@ +package com.tangem.tap.common.extensions + +import android.view.LayoutInflater +import android.view.ViewGroup +import android.view.ViewParent +import androidx.transition.AutoTransition +import androidx.transition.Transition +import androidx.transition.TransitionManager +import timber.log.Timber + +/** +[REDACTED_AUTHOR] + */ +fun ViewGroup.inflate(viewToInflate: Int, rootView: ViewGroup?, parent: ViewGroup) { + if (rootView == null) { + val inflatedView = LayoutInflater.from(context).inflate(viewToInflate, rootView) + parent.addView(inflatedView) + } +} + +fun ViewParent?.beginDelayedTransition(transition: Transition = AutoTransition()) { + if (this == null) Timber.e("Can't invoke beginDelayedTransition, because parent is NULL") + (this as? ViewGroup)?.beginDelayedTransition(transition) +} + +fun ViewGroup.beginDelayedTransition(transition: Transition = AutoTransition()) { + TransitionManager.beginDelayedTransition(this, transition) +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/images/Picasso.kt b/app/src/main/java/com/tangem/tap/common/images/Picasso.kt new file mode 100644 index 0000000000..c3b1d139a3 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/images/Picasso.kt @@ -0,0 +1,39 @@ +package com.tangem.tap.common.images + +import android.app.Application +import com.squareup.picasso.OkHttp3Downloader +import com.squareup.picasso.Picasso +import okhttp3.Cache +import okhttp3.CacheControl +import okhttp3.OkHttpClient +import java.io.File +import java.util.concurrent.TimeUnit + +class PicassoHelper { + companion object { + private const val KEEP_CACHE_MAX_DAYS = 7 + + fun initPicassoWithCaching(application: Application) { + val picasso = Picasso.Builder(application) + .downloader(OkHttp3Downloader(getOkHttpForPicasso(application))) + .build() + Picasso.setSingletonInstance(picasso) + } + + private fun getOkHttpForPicasso(application: Application): OkHttpClient { + val okHttpBuilder = OkHttpClient.Builder() + okHttpBuilder.cache(Cache(File(application.filesDir, "artworks"), Long.MAX_VALUE)) + okHttpBuilder.addInterceptor { chain -> + val cacheControl = CacheControl.Builder() + .maxStale(KEEP_CACHE_MAX_DAYS, TimeUnit.DAYS) + .build() + val origRequest = chain.request() + val neverExpireRequest = origRequest.newBuilder() + .cacheControl(cacheControl) + .build() + chain.proceed(neverExpireRequest) + } + return okHttpBuilder.build() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/qrCodeScan/ScanQrCodeActivity.kt b/app/src/main/java/com/tangem/tap/common/qrCodeScan/ScanQrCodeActivity.kt new file mode 100644 index 0000000000..da69eed9ae --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/qrCodeScan/ScanQrCodeActivity.kt @@ -0,0 +1,69 @@ +package com.tangem.tap.common.qrCodeScan + +import android.Manifest +import android.content.Intent +import android.content.pm.PackageManager +import android.os.Build +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat +import com.google.zxing.Result +import com.otaliastudios.cameraview.CameraView.PERMISSION_REQUEST_CODE +import me.dm7.barcodescanner.zxing.ZXingScannerView + +/** +[REDACTED_AUTHOR] + */ +class ScanQrCodeActivity : AppCompatActivity(), ZXingScannerView.ResultHandler { + companion object { + val SCAN_QR_REQUEST_CODE = 1001 + val SCAN_RESULT = "scanResult" + } + + private lateinit var mScannerView: ZXingScannerView + + override fun onCreate(state: Bundle?) { + super.onCreate(state) + mScannerView = ZXingScannerView(this) + setContentView(mScannerView) + + if (!permissionIsGranted()) requestPermission() + + } + + override fun onResume() { + super.onResume() + mScannerView.setResultHandler(this) + mScannerView.startCamera() + } + + override fun onPause() { + super.onPause() + mScannerView.stopCamera() + } + + override fun handleResult(result: Result) { + setResult(SCAN_QR_REQUEST_CODE, Intent().apply { putExtra(SCAN_RESULT, result.text) }) + finish() + } + + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { + if (requestCode != PERMISSION_REQUEST_CODE) return + + if (grantResults.isEmpty() || grantResults[0] != PackageManager.PERMISSION_GRANTED) { + finish() + } + } + + private fun permissionIsGranted(): Boolean { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + val cameraPermission = ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) + cameraPermission == PackageManager.PERMISSION_GRANTED + } else true + } + + private fun requestPermission() { + ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA), PERMISSION_REQUEST_CODE) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt new file mode 100644 index 0000000000..3420ef1255 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt @@ -0,0 +1,27 @@ +package com.tangem.tap.common.redux + +import com.tangem.tap.common.redux.global.globalReducer +import com.tangem.tap.common.redux.navigation.NavigationReducer +import com.tangem.tap.features.details.redux.DetailsReducer +import com.tangem.tap.features.home.redux.HomeReducer +import com.tangem.tap.features.send.redux.reducers.SendScreenReducer +import com.tangem.tap.features.wallet.redux.WalletReducer +import org.rekotlin.Action + +fun appReducer(action: Action, state: AppState?): AppState { + requireNotNull(state) + if (action is AppAction.RestoreState) return action.state + + return AppState( + navigationState = NavigationReducer.reduce(action, state), + globalState = globalReducer(action, state), + homeState = HomeReducer.reduce(action, state), + walletState = WalletReducer.reduce(action, state), + sendState = SendScreenReducer.reduce(action, state.sendState), + detailsState = DetailsReducer.reduce(action, state) + ) +} + +sealed class AppAction : Action { + data class RestoreState(val state: AppState) : AppAction() +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/AppState.kt b/app/src/main/java/com/tangem/tap/common/redux/AppState.kt new file mode 100644 index 0000000000..8f5f980c69 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/redux/AppState.kt @@ -0,0 +1,37 @@ +package com.tangem.tap.common.redux + +import com.tangem.tap.common.redux.global.GlobalState +import com.tangem.tap.common.redux.global.globalMiddleware +import com.tangem.tap.common.redux.navigation.NavigationState +import com.tangem.tap.common.redux.navigation.navigationMiddleware +import com.tangem.tap.features.details.redux.DetailsMiddleware +import com.tangem.tap.features.details.redux.DetailsState +import com.tangem.tap.features.home.redux.HomeState +import com.tangem.tap.features.home.redux.homeMiddleware +import com.tangem.tap.features.send.redux.middlewares.sendMiddleware +import com.tangem.tap.features.send.redux.states.SendState +import com.tangem.tap.features.wallet.redux.WalletState +import com.tangem.tap.features.wallet.redux.walletMiddleware +import org.rekotlin.Middleware +import org.rekotlin.StateType + +data class AppState( + val navigationState: NavigationState = NavigationState(), + val globalState: GlobalState = GlobalState(), + val homeState: HomeState = HomeState(), + val walletState: WalletState = WalletState(), + val sendState: SendState = SendState(), + val detailsState: DetailsState = DetailsState() +) : StateType { + + companion object { + fun getMiddleware(): List> { + return listOf( + logMiddleware, navigationMiddleware, notificationsMiddleware, globalMiddleware, + homeMiddleware, walletMiddleware, sendMiddleware, + DetailsMiddleware().detailsMiddleware + ) + } + } +} + diff --git a/app/src/main/java/com/tangem/tap/common/redux/LogMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/LogMiddleware.kt new file mode 100644 index 0000000000..15ea778fed --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/redux/LogMiddleware.kt @@ -0,0 +1,16 @@ +package com.tangem.tap.common.redux + +import org.rekotlin.Middleware +import timber.log.Timber + +/** +[REDACTED_AUTHOR] + */ +val logMiddleware: Middleware = { dispatch, appState -> + { nextDispatch -> + { action -> + Timber.d("Dispatch action: $action") + nextDispatch(action) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/NotificationsMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/NotificationsMiddleware.kt new file mode 100644 index 0000000000..8a55bcfafe --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/redux/NotificationsMiddleware.kt @@ -0,0 +1,97 @@ +package com.tangem.tap.common.redux + +import android.content.Context +import android.widget.Toast +import androidx.coordinatorlayout.widget.CoordinatorLayout +import com.google.android.material.snackbar.Snackbar +import com.tangem.tap.domain.ArgError +import com.tangem.tap.domain.MultiMessageError +import com.tangem.tap.domain.TapError +import com.tangem.tap.domain.assembleErrors +import com.tangem.tap.notificationsHandler +import org.rekotlin.Action +import org.rekotlin.Middleware +import java.lang.ref.WeakReference + +class NotificationsHandler(coordinatorLayout: CoordinatorLayout) { + private val basicCoordinatorLayout = WeakReference(coordinatorLayout) + private var baseLayout = basicCoordinatorLayout + + fun replaceBaseLayout(coordinatorLayout: CoordinatorLayout) { + baseLayout = WeakReference(coordinatorLayout) + } + + fun returnBaseLayout() { + baseLayout = basicCoordinatorLayout + } + + fun showNotification(message: String) { + baseLayout.get()?.let { layout -> + Snackbar.make(layout, message, Snackbar.LENGTH_LONG) + .also { snackbar -> snackbar.show() } + } + } + + fun showNotification(message: Int, args: List? = null) { + baseLayout.get()?.let { + showNotification(getMessageString(it.context, message, args)) + } + } + + fun showToastNotification(message: Int, args: List? = null) { + baseLayout.get()?.let { + Toast.makeText(it.context, getMessageString(it.context, message, args), Toast.LENGTH_LONG).show() + } + } + + fun showNotification(errorList: List?>>, builder: (List) -> String) { + val context = baseLayout.get()?.context ?: return + + val message = builder(errorList.map { getMessageString(context, it.first, it.second) }) + showNotification(message) + } +} + +fun getMessageString(context: Context, message: Int, args: List?): String { + return if (args.isNullOrEmpty()) { + context.getString(message) + } else { + context.getString(message, *args.toTypedArray()) + } +} + +val notificationsMiddleware: Middleware = { dispatch, state -> + { next -> + { action -> + when (action) { + is NotificationAction -> notificationsHandler?.showNotification(action.messageResource) + is ToastNotificationAction -> notificationsHandler?.showToastNotification(action.messageResource) + is ErrorAction -> { + when (action.error) { + is MultiMessageError -> { + val multiError = action.error as MultiMessageError + notificationsHandler?.showNotification(multiError.assembleErrors(), multiError.builder) + } + else -> { + val args = (action.error as? ArgError)?.args ?: listOf() + notificationsHandler?.showNotification(action.error.localizedMessage, args) + } + } + } + } + next(action) + } + } +} + +interface ToastNotificationAction : Action { + val messageResource: Int +} + +interface NotificationAction : Action { + val messageResource: Int +} + +interface ErrorAction : Action { + val error: TapError +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/Request.kt b/app/src/main/java/com/tangem/tap/common/redux/Request.kt new file mode 100644 index 0000000000..4faae37c93 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/redux/Request.kt @@ -0,0 +1,7 @@ +package com.tangem.tap.common.redux + +import org.rekotlin.Action + +abstract class Request : Action { + abstract suspend fun execute() +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt new file mode 100644 index 0000000000..de18ac1a98 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt @@ -0,0 +1,18 @@ +package com.tangem.tap.common.redux.global + +import com.tangem.tap.domain.tasks.ScanNoteResponse +import org.rekotlin.Action +import java.math.BigDecimal + +sealed class GlobalAction : Action { + + data class SaveScanNoteResponse(val scanNoteResponse: ScanNoteResponse) : GlobalAction() + data class SetFiatRate( + val fiatRates: Pair + ) : GlobalAction() + data class ChangeAppCurrency(val appCurrency: FiatCurrencyName) : GlobalAction() + object RestoreAppCurrency : GlobalAction() { + data class Success(val appCurrency: FiatCurrencyName) : GlobalAction() + } + data class UpdateWalletSignedHashes(val walletSignedHashes: Int) : GlobalAction() +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt new file mode 100644 index 0000000000..d7ec41960f --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt @@ -0,0 +1,21 @@ +package com.tangem.tap.common.redux.global + +import com.tangem.tap.common.redux.AppState +import com.tangem.tap.preferencesStorage +import com.tangem.tap.store +import org.rekotlin.Middleware + +val globalMiddleware: Middleware = { dispatch, appState -> + { nextDispatch -> + { action -> + when (action) { + is GlobalAction.RestoreAppCurrency -> { + store.dispatch(GlobalAction.RestoreAppCurrency.Success( + preferencesStorage.getAppCurrency() + )) + } + } + nextDispatch(action) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt new file mode 100644 index 0000000000..66fbb80140 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt @@ -0,0 +1,38 @@ +package com.tangem.tap.common.redux.global + +import com.tangem.tap.common.redux.AppState +import org.rekotlin.Action + +fun globalReducer(action: Action, state: AppState): GlobalState { + + if (action !is GlobalAction) return state.globalState + + val globalState = state.globalState + + return when (action) { + is GlobalAction.SaveScanNoteResponse -> + globalState.copy(scanNoteResponse = action.scanNoteResponse) + is GlobalAction.SetFiatRate -> { + val rates = globalState.conversionRates.rates.toMutableMap() + rates[action.fiatRates.first] = action.fiatRates.second + globalState.copy(conversionRates = ConversionRates(rates)) + } + is GlobalAction.ChangeAppCurrency -> { + globalState.copy(appCurrency = action.appCurrency, conversionRates = ConversionRates(mapOf())) + } + is GlobalAction.RestoreAppCurrency.Success -> { + globalState.copy(appCurrency = action.appCurrency, conversionRates = ConversionRates(mapOf())) + } + is GlobalAction.UpdateWalletSignedHashes -> { + val card = globalState.scanNoteResponse?.card?.copy( + walletSignedHashes = action.walletSignedHashes + ) + if (card != null) { + globalState.copy(scanNoteResponse = globalState.scanNoteResponse.copy(card = card)) + } else { + globalState + } + } + else -> globalState + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt new file mode 100644 index 0000000000..9da6906d9c --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt @@ -0,0 +1,33 @@ +package com.tangem.tap.common.redux.global + +import com.tangem.commands.common.network.TangemService +import com.tangem.tap.common.entities.TapCurrency.Companion.DEFAULT_FIAT_CURRENCY +import com.tangem.tap.domain.PayIdManager +import com.tangem.tap.domain.TapWalletManager +import com.tangem.tap.domain.tasks.ScanNoteResponse +import com.tangem.tap.network.coinmarketcap.CoinMarketCapService +import org.rekotlin.StateType +import java.math.BigDecimal + +data class GlobalState( + val scanNoteResponse: ScanNoteResponse? = null, + val tapWalletManager: TapWalletManager = TapWalletManager(), + val payIdManager: PayIdManager = PayIdManager(), + val coinMarketCapService: CoinMarketCapService = CoinMarketCapService(), + val tangemService: TangemService = TangemService(), + val conversionRates: ConversionRates = ConversionRates(emptyMap()), + val appCurrency: FiatCurrencyName = DEFAULT_FIAT_CURRENCY +) : StateType + +data class ConversionRates( + val rates: Map, +) { + + fun getRate(cryptoCurrency: CryptoCurrencyName): BigDecimal? { + return rates[cryptoCurrency] + } +} + +typealias CryptoCurrencyName = String +typealias FiatCurrencyName = String + diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationAction.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationAction.kt new file mode 100644 index 0000000000..5cbdb915f6 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationAction.kt @@ -0,0 +1,15 @@ +package com.tangem.tap.common.redux.navigation + +import androidx.fragment.app.FragmentActivity +import org.rekotlin.Action +import java.lang.ref.WeakReference + +sealed class NavigationAction : Action { + data class NavigateTo(val screen: AppScreen, val addToBackstack: Boolean = true) : + NavigationAction() + + data class PopBackTo(val screen: AppScreen? = null) : NavigationAction() + + data class ActivityCreated(val activity: WeakReference) : NavigationAction() + object ActivityDestroyed : NavigationAction() +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt new file mode 100644 index 0000000000..8c4d5e9fea --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt @@ -0,0 +1,30 @@ +package com.tangem.tap.common.redux.navigation + +import com.tangem.tap.common.extensions.openFragment +import com.tangem.tap.common.extensions.popBackTo +import com.tangem.tap.common.redux.AppState +import com.tangem.tap.store +import org.rekotlin.Middleware + +val navigationMiddleware: Middleware = { dispatch, state -> + { next -> + { action -> + if (action is NavigationAction) { + val navState = store.state.navigationState + when (action) { + is NavigationAction.NavigateTo -> { + navState.activity?.get()?.openFragment(action.screen, action.addToBackstack) + } + is NavigationAction.PopBackTo -> { + if (action.screen == AppScreen.Home) { + navState.activity?.get()?.popBackTo(null, true) + } else { + navState.activity?.get()?.popBackTo(action.screen) + } + } + } + } + next(action) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationReducer.kt new file mode 100644 index 0000000000..a4ce784435 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationReducer.kt @@ -0,0 +1,31 @@ +package com.tangem.tap.common.redux.navigation + +import com.tangem.tap.common.extensions.getPreviousScreen +import com.tangem.tap.common.redux.AppState +import org.rekotlin.Action + +class NavigationReducer { + companion object { + fun reduce(action: Action, state: AppState): NavigationState = internalReduce(action, state) + } +} + +private fun internalReduce(action: Action, state: AppState): NavigationState { + + val navigationAction = action as? NavigationAction ?: return state.navigationState + val navState = state.navigationState + + return when (navigationAction) { + is NavigationAction.NavigateTo -> { + navState.copy(backStack = navState.backStack + navigationAction.screen) + } + is NavigationAction.PopBackTo -> { + val screen = + navigationAction.screen ?: navState.activity?.get()?.getPreviousScreen() + val index = navState.backStack.lastIndexOf(screen) + 1 + state.navigationState.copy(backStack = navState.backStack.subList(0, index)) + } + is NavigationAction.ActivityCreated -> navState.copy(activity = navigationAction.activity) + is NavigationAction.ActivityDestroyed -> navState.copy(activity = null) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt new file mode 100644 index 0000000000..3a062653d8 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt @@ -0,0 +1,12 @@ +package com.tangem.tap.common.redux.navigation + +import androidx.fragment.app.FragmentActivity +import org.rekotlin.StateType +import java.lang.ref.WeakReference + +data class NavigationState( + val backStack: List = listOf(AppScreen.Home), + val activity: WeakReference? = null +) : StateType + +enum class AppScreen { Home, Wallet, Send, Details, DetailsConfirm, DetailsSecurity } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/snackBar/MaxAmountSnackbar.kt b/app/src/main/java/com/tangem/tap/common/snackBar/MaxAmountSnackbar.kt new file mode 100644 index 0000000000..69f2114528 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/snackBar/MaxAmountSnackbar.kt @@ -0,0 +1,81 @@ +package com.tangem.tap.common.snackBar + +import android.content.Context +import android.util.AttributeSet +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.coordinatorlayout.widget.CoordinatorLayout +import com.google.android.material.snackbar.BaseTransientBottomBar +import com.google.android.material.snackbar.ContentViewCallback +import com.google.android.material.snackbar.Snackbar +import com.tangem.wallet.R + +/** +[REDACTED_AUTHOR] + */ +class MaxAmountSnackbar( + parent: ViewGroup, + content: MaxAmountSnackbarView +) : BaseTransientBottomBar(parent, content, content) { + + companion object { + + fun make(view: View, onClick: () -> Unit): MaxAmountSnackbar { + val parent = view.findSuitableParent() ?: throw IllegalArgumentException( + "No suitable parent found from the given view. Please provide a valid view." + ) + val inflater = LayoutInflater.from(view.context) + val customView = inflater.inflate(R.layout.view_snackbar_max_amount, parent, false) as MaxAmountSnackbarView + customView.setOnClickListener { onClick() } + + return MaxAmountSnackbar(parent, customView).apply { + duration = Snackbar.LENGTH_INDEFINITE + } + } + + private fun View?.findSuitableParent(): ViewGroup? { + var view = this + var fallback: ViewGroup? = null + do { + if (view is CoordinatorLayout) { + return view + } else if (view is FrameLayout) { + if (view.id == android.R.id.content) { + return view + } else { + fallback = view + } + } + + if (view != null) { + val parent = view.parent + view = if (parent is View) parent else null + } + } while (view != null) + return fallback + } + + } +} + +class MaxAmountSnackbarView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : ConstraintLayout(context, attrs, defStyleAttr), ContentViewCallback { + + init { + View.inflate(context, R.layout.view_snackbar_max_amount_content, this) + clipToPadding = false + } + + + override fun animateContentIn(delay: Int, duration: Int) { + } + + override fun animateContentOut(delay: Int, duration: Int) { + } +} diff --git a/app/src/main/java/com/tangem/tap/common/text/DecimalDigitsInputFilter.kt b/app/src/main/java/com/tangem/tap/common/text/DecimalDigitsInputFilter.kt new file mode 100644 index 0000000000..c105940b9d --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/text/DecimalDigitsInputFilter.kt @@ -0,0 +1,30 @@ +package com.tangem.tap.common.text + +import android.text.InputFilter +import android.text.Spanned +import java.util.regex.Pattern + +/** +[REDACTED_AUTHOR] + */ +class DecimalDigitsInputFilter( + digitsBeforeDecimal: Int, + digitsAfterDecimal: Int, + decimalSeparator: String +) : InputFilter { + private val pattern: Pattern = Pattern.compile("(([1-9]{1}[0-9]{0,${digitsBeforeDecimal -1}})?||[0]{1})((\\$decimalSeparator[0-9]{0,$digitsAfterDecimal})?)||(\\$decimalSeparator)?") + + override fun filter(source: CharSequence, sourceStart: Int, sourceEnd: Int, destination: Spanned, destinationStart: Int, destinationEnd: Int): CharSequence? { + val destString = destination.toString() + val prefix = destString.substring(0, destinationStart) + val suffix = destString.substring(destinationEnd, destString.length) + val newDestination = prefix + suffix + + val resultPrefix = newDestination.substring(0, destinationStart) + val resultSuffix = newDestination.substring(destinationStart, newDestination.length) + val result = resultPrefix + source.toString() + resultSuffix + + val hasMatches = pattern.matcher(result).matches() + return if (hasMatches) null else "" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/text/Truncate.kt b/app/src/main/java/com/tangem/tap/common/text/Truncate.kt new file mode 100644 index 0000000000..3ed50ca1d2 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/text/Truncate.kt @@ -0,0 +1,136 @@ +package com.tangem.tap.common.text + +import android.widget.TextView +import com.tangem.tap.common.extensions.isEven + +/** +[REDACTED_AUTHOR] + */ +enum class TruncateType { + START, MIDDLE, END +} + +interface Truncate { + fun apply(tv: TextView, text: String, with: String): String + + companion object { + fun create(type: TruncateType): Truncate { + return when (type) { + TruncateType.START -> TruncateStart() + TruncateType.MIDDLE -> TruncateMiddle() + TruncateType.END -> TruncateEnd() + } + } + } +} + +abstract class BaseTruncate : Truncate { + protected var hasBeenTruncated = false + + override fun apply(tv: TextView, text: String, with: String): String { + val roughLength = getRoughFitLength(tv, text, with) + val fittedText = preciseFitting(tv, roughTruncate(text, roughLength), with) + return if (hasBeenTruncated) attachWith(fittedText, with) else fittedText + } + + protected fun getRoughFitLength(tv: TextView, text: String, with: String): Int { + val existingSpace = tv.measuredWidth - (tv.paddingStart + tv.paddingEnd) + val textWillTakeSpace = tv.paint.measureText(text) + val overSizeRatio: Float = textWillTakeSpace / existingSpace + + val maxLengthOfText = text.length / overSizeRatio + if (text.length <= maxLengthOfText) return text.length + + return maxLengthOfText.toInt() + } + + protected fun preciseFitting(tv: TextView, text: String, with: String): String { + if (!hasBeenTruncated) return text + + val spaceForText = tv.measuredWidth - (tv.paddingStart + tv.paddingEnd) + + var fittedText = text + while (tv.paint.measureText(fittedText + with) > spaceForText) { + fittedText = preciseTruncate(fittedText) + } + return fittedText + } + + protected abstract fun roughTruncate(text: String, residualLength: Int): String + protected abstract fun preciseTruncate(text: String): String + protected abstract fun attachWith(text: String, with: String): String +} + +class TruncateStart : BaseTruncate() { + override fun roughTruncate(text: String, residualLength: Int): String { + if (text.length <= residualLength) return text + + hasBeenTruncated = true + return text.substring(residualLength, text.length) + } + + override fun preciseTruncate(text: String): String = text.substring(1, text.length) + + override fun attachWith(text: String, with: String): String = with + text +} + +class TruncateMiddle : BaseTruncate() { + + override fun roughTruncate(text: String, residualLength: Int): String { + if (text.length <= residualLength) return text + + hasBeenTruncated = true + val halfOfResidualLength = residualLength / 2 + val leftSide = text.substring(0, halfOfResidualLength) + val rightSide = text.substring(text.length - halfOfResidualLength, text.length) + + return leftSide + rightSide + } + + override fun preciseTruncate(text: String): String { + val middlePosition = text.length / 2 + return if (text.length.isEven()) { + val leftSide = text.substring(0, middlePosition - 1) + val rightSide = text.substring(middlePosition, text.length) + leftSide + rightSide + } else { + val leftSide = text.substring(0, middlePosition) + val rightSide = text.substring(middlePosition + 1, text.length) + leftSide + rightSide + } + } + + override fun attachWith(text: String, with: String): String { + val cuttingPosition = text.length / 2 + val leftSide = text.substring(0, cuttingPosition) + val rightSide = text.substring(cuttingPosition, text.length) + return leftSide + with + rightSide + } +} + +class TruncateEnd : BaseTruncate() { + override fun roughTruncate(text: String, residualLength: Int): String { + if (text.length <= residualLength) return text + + hasBeenTruncated = true + return text.substring(0, residualLength) + } + + override fun preciseTruncate(text: String): String = text.substring(0, text.length - 1) + + override fun attachWith(text: String, with: String): String = text + with +} + +fun TextView.truncateWith(text: String, type: TruncateType, with: String = "..."): String { + val truncate = Truncate.create(type) + return truncate.apply(this, text, with) +} + +fun TextView.truncateStartWith(text: String, with: String = "..."): String = + this.truncateWith(text, TruncateType.START, with) + +fun TextView.truncateMiddleWith(text: String, with: String = "..."): String = + this.truncateWith(text, TruncateType.MIDDLE, with) + +fun TextView.truncateEndWith(text: String, with: String = "..."): String = + this.truncateWith(text, TruncateType.END, with) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/toggleWidget/ToggleWidget.kt b/app/src/main/java/com/tangem/tap/common/toggleWidget/ToggleWidget.kt new file mode 100644 index 0000000000..153a5eddc8 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/toggleWidget/ToggleWidget.kt @@ -0,0 +1,85 @@ +package com.tangem.merchant.common.toggleWidget + +import android.view.View +import android.view.ViewGroup + + +/** +[REDACTED_AUTHOR] + */ +interface ToggleState + +interface StateModifier { + fun stateChanged(container: ViewGroup, view: View, state: ToggleState) +} + +interface ToggleView { + val mainViewModifiers: MutableList + val toggleViewModifiers: MutableList + + fun setState(state: ToggleState, andApply: Boolean = true) + fun applyState() + fun getView(): View + fun getMainView(): View + fun getToggleView(): View +} + +class ToggleWidget : ToggleView { + private val container: ViewGroup + private val mainView: View + private val toggleView: View + + private var state: ToggleState + + constructor( + container: ViewGroup, + mainView: View, + toggleView: View, + initialState: ToggleState, + mainViewModifier: List = mutableListOf(), + loadingViewModifier: List = mutableListOf() + ) { + this.container = container + this.mainView = mainView + this.toggleView = toggleView + this.state = initialState + this.mainViewModifiers.addAll(mainViewModifier) + this.toggleViewModifiers.addAll(loadingViewModifier) + } + + constructor( + container: ViewGroup, + mainViewId: Int, + toggleViewId: Int, + initialState: ToggleState, + mainViewModifier: List = mutableListOf(), + loadingViewModifier: List = mutableListOf() + ) { + this.container = container + this.mainView = container.findViewById(mainViewId) + this.toggleView = container.findViewById(toggleViewId) + this.state = initialState + this.mainViewModifiers.addAll(mainViewModifier) + this.toggleViewModifiers.addAll(loadingViewModifier) + } + + override val mainViewModifiers: MutableList = mutableListOf() + + override val toggleViewModifiers: MutableList = mutableListOf() + + override fun setState(state: ToggleState, andApply: Boolean) { + this.state = state + if (andApply) applyState() + } + + override fun applyState() { + mainViewModifiers.forEach { it.stateChanged(container, mainView, state) } + toggleViewModifiers.forEach { it.stateChanged(container, toggleView, state) } + } + + override fun getView(): View = container + + override fun getMainView(): View = mainView + + override fun getToggleView(): View = toggleView +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/toggleWidget/progressModifiers.kt b/app/src/main/java/com/tangem/tap/common/toggleWidget/progressModifiers.kt new file mode 100644 index 0000000000..bc66fa830b --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/toggleWidget/progressModifiers.kt @@ -0,0 +1,145 @@ +package com.tangem.tap.common.toggleWidget + +import android.graphics.drawable.Drawable +import android.view.View +import android.view.ViewGroup +import android.widget.Button +import android.widget.TextView +import androidx.core.widget.TextViewCompat +import com.google.android.material.button.MaterialButton +import com.tangem.merchant.common.toggleWidget.StateModifier +import com.tangem.merchant.common.toggleWidget.ToggleState +import com.tangem.tap.common.extensions.beginDelayedTransition + +/** +[REDACTED_AUTHOR] + */ + +sealed class ProgressState : ToggleState { + class Progress : ProgressState() + class None : ProgressState() +} + +class ReplaceTextStateModifier( + private val initialText: String, + private val replaceText: String = "" +) : StateModifier { + + override fun stateChanged(container: ViewGroup, view: View, state: ToggleState) { + val tv = view as? TextView ?: return + + when (state) { + is ProgressState.Progress -> { + tv.text = replaceText + } + is ProgressState.None -> { + tv.text = initialText + } + } + } +} + +class TextViewDrawableStateModifier( + private val initialDrawable: Drawable?, + private val replaceDrawable: Drawable?, + private val position: Int +) : StateModifier { + companion object { + val LEFT = 1 + val RIGHT = 2 + } + + override fun stateChanged(container: ViewGroup, view: View, state: ToggleState) { + val drawable = when (state) { + is ProgressState.Progress -> replaceDrawable + is ProgressState.None -> initialDrawable + else -> null + } + val drawableChanger = getChanger(view) + drawableChanger?.change(drawable, position) + } + + private fun getChanger(view: View): DrawableChanger? = when (view) { + is MaterialButton -> MaterialButtonChanger(view) + is Button -> TextViewChanger(view) + is TextView -> TextViewChanger(view) + else -> null + } + + internal interface DrawableChanger { + fun change(drawable: Drawable?, position: Int) + } + + internal class TextViewChanger(private val view: TextView) : DrawableChanger { + override fun change(drawable: Drawable?, position: Int) { + when (position) { + LEFT -> setLeft(drawable) + RIGHT -> setRight(drawable) + } + } + + private fun setLeft(drawable: Drawable?) { + if (drawable == null) { + TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(view, 0, 0, 0, 0) + } else { + TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(view, drawable, null, null, null) + } + } + + private fun setRight(drawable: Drawable?) { + if (drawable == null) { + TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(view, 0, 0, 0, 0) + } else { + TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(view, null, null, drawable, null) + } + } + } + + internal class MaterialButtonChanger(private val view: MaterialButton) : DrawableChanger { + override fun change(drawable: Drawable?, position: Int) { + when (position) { + LEFT -> setLeft(drawable) + RIGHT -> setRight(drawable) + } + } + + private fun setLeft(drawable: Drawable?) { + view.iconGravity = MaterialButton.ICON_GRAVITY_START + view.icon = drawable + } + + private fun setRight(drawable: Drawable?) { + view.iconGravity = MaterialButton.ICON_GRAVITY_END + view.icon = drawable + } + } +} + +class ShowHideStateModifier( + private val isShowOnLoading: Boolean = true, + private val typeOfHiding: Int = View.INVISIBLE +) : StateModifier { + + override fun stateChanged(container: ViewGroup, view: View, state: ToggleState) { + container.beginDelayedTransition() + view.visibility = when (state) { + is ProgressState.Progress -> if (isShowOnLoading) View.VISIBLE else typeOfHiding + is ProgressState.None -> if (isShowOnLoading) typeOfHiding else View.VISIBLE + else -> return + } + } +} + +class ClickableStateModifier( + private val isClickableOnLoading: Boolean = false +) : StateModifier { + + override fun stateChanged(container: ViewGroup, view: View, state: ToggleState) { + view.isClickable = when (state) { + is ProgressState.Progress -> isClickableOnLoading + is ProgressState.None -> !isClickableOnLoading + else -> return + + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/PayIdManager.kt b/app/src/main/java/com/tangem/tap/domain/PayIdManager.kt new file mode 100644 index 0000000000..ddf6c99669 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/PayIdManager.kt @@ -0,0 +1,83 @@ +package com.tangem.tap.domain + +import com.tangem.blockchain.common.Blockchain +import com.tangem.commands.common.network.Result +import com.tangem.tap.network.payid.PayIdService +import com.tangem.tap.network.payid.PayIdVerifyService +import com.tangem.tap.network.payid.SetPayIdResponse +import com.tangem.tap.network.payid.VerifyPayIdResponse +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import retrofit2.HttpException +import java.util.* + +class PayIdManager { + private val payIdService = PayIdService() + + suspend fun getPayId(cardId: String, publicKey: String): Result = withContext(Dispatchers.IO) { + val result = payIdService.getPayId(cardId, publicKey) + when (result) { + is Result.Success -> return@withContext Result.Success(result.data.payId) + is Result.Failure -> { + (result.error as? HttpException)?.let { + if (it.code() == 404) return@withContext Result.Success(null) + } + return@withContext result + } + } + } + + + suspend fun setPayId( + cardId: String, publicKey: String, payId: String, address: String, blockchain: Blockchain + ): Result = withContext(Dispatchers.IO) { + val result = payIdService.setPayId(cardId, publicKey, payId, address, blockchain.getPayIdNetwork()) + when (result) { + is Result.Success -> return@withContext result + is Result.Failure -> { + (result.error as? HttpException)?.let { + if (it.code() == 409) return@withContext Result.Failure(TapError.PayIdAlreadyCreated) + } + return@withContext result + } + } + } + + suspend fun verifyPayId(payId: String, blockchain: Blockchain): Result = withContext(Dispatchers.IO) { + val splitPayId = payId.split("\$") + val user = splitPayId[0] + val baseUrl = "https://${splitPayId[1]}/" + return@withContext PayIdVerifyService(baseUrl).verifyAddress(user, blockchain.getPayIdNetwork()) + } + + private fun Blockchain.getPayIdNetwork(): String { + return when (this) { + Blockchain.XRP -> "XRPL" + Blockchain.RSK -> "RSK" + else -> this.currency + }.toLowerCase() + } + + companion object { + val payIdRegExp = "^[a-z0-9!#@%&*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#@%&*+/=?^_`{|}~-]+)*\\\$(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z-]*[a-z0-9])?|(?:[0-9]{1,3}\\.){3}[0-9]{1,3})\$".toRegex() + + val payIdSupported: EnumSet = EnumSet.of( + Blockchain.XRP, + Blockchain.Ethereum, + Blockchain.Bitcoin, + Blockchain.Litecoin, + Blockchain.Stellar, + Blockchain.Cardano, + Blockchain.Ducatus, + Blockchain.BitcoinCash, + Blockchain.Binance, + Blockchain.RSK, + ) + + fun isPayId(value: String?): Boolean = value?.contains(payIdRegExp) ?: false + } +} + +fun Blockchain.isPayIdSupported(): Boolean { + return PayIdManager.payIdSupported.contains(this) +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt new file mode 100644 index 0000000000..d122202ca1 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt @@ -0,0 +1,81 @@ +package com.tangem.tap.domain + +import androidx.activity.ComponentActivity +import com.tangem.* +import com.tangem.commands.* +import com.tangem.common.CompletionResult +import com.tangem.common.extensions.CardType +import com.tangem.common.extensions.calculateSha256 +import com.tangem.tangem_sdk_new.extensions.init +import com.tangem.tap.domain.tasks.CreateWalletAndRescanTask +import com.tangem.tap.domain.tasks.ScanNoteResponse +import com.tangem.tap.domain.tasks.ScanNoteTask +import com.tangem.wallet.R +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import java.util.* +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine + +class TangemSdkManager(val activity: ComponentActivity) { + private val tangemSdk = TangemSdk.init( + activity, Config(cardFilter = CardFilter(EnumSet.allOf(CardType::class.java))) + ) + + suspend fun scanNote(): CompletionResult { + return runTaskAsyncReturnOnMain(ScanNoteTask(), + initialMessage = Message(activity.getString(R.string.tap_to_scan))) + } + + suspend fun createWallet(cardId: String?): CompletionResult { + return runTaskAsyncReturnOnMain(CreateWalletAndRescanTask(), cardId, + initialMessage = Message(activity.getString(R.string.tap_to_create_wallet))) + } + + suspend fun eraseWallet(cardId: String?): CompletionResult { + return runTaskAsyncReturnOnMain(PurgeWalletCommand(), cardId, + initialMessage = Message(activity.getString(R.string.tap_to_purge_wallet))) + } + + suspend fun setPasscode(cardId: String?): CompletionResult { + return runTaskAsyncReturnOnMain(SetPinCommand( + pinType = PinType.Pin2, + newPin1 = tangemSdk.config.defaultPin1.calculateSha256(), + newPin2 = null + ), cardId, initialMessage = Message(activity.getString(R.string.tap_to_change_passcode))) + } + + suspend fun setAccessCode(cardId: String?): CompletionResult { + return runTaskAsyncReturnOnMain(SetPinCommand( + pinType = PinType.Pin1, + newPin1 = null, + newPin2 = tangemSdk.config.defaultPin2.calculateSha256() + ), cardId, initialMessage = Message(activity.getString(R.string.tap_to_change_access_code))) + } + + suspend fun setLongTap(cardId: String?): CompletionResult { + return runTaskAsyncReturnOnMain(SetPinCommand( + pinType = PinType.Pin1, + newPin1 = tangemSdk.config.defaultPin1.calculateSha256(), + newPin2 = tangemSdk.config.defaultPin2.calculateSha256() + ), cardId, initialMessage = Message(activity.getString(R.string.tap_the_card))) + } + + private suspend fun runTaskAsync( + runnable: CardSessionRunnable, cardId: String? = null, initialMessage: Message? = null + ): CompletionResult = + withContext(Dispatchers.IO) { + suspendCoroutine { continuation -> + tangemSdk.startSessionWithRunnable(runnable, cardId, initialMessage) { result -> + continuation.resume(result) + } + } + } + + private suspend fun runTaskAsyncReturnOnMain( + runnable: CardSessionRunnable, cardId: String? = null, initialMessage: Message? = null + ): CompletionResult { + val result = runTaskAsync(runnable, cardId, initialMessage) + return withContext(Dispatchers.Main) { result } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/TapErrors.kt b/app/src/main/java/com/tangem/tap/domain/TapErrors.kt new file mode 100644 index 0000000000..8b01a14b04 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/TapErrors.kt @@ -0,0 +1,61 @@ +package com.tangem.tap.domain + +import androidx.annotation.StringRes +import com.tangem.TangemError +import com.tangem.wallet.R + +interface TapErrors + +interface ArgError{ + val args: List? +} + +interface MultiMessageError : TapErrors { + val errorList: List + val builder: (List) -> String +} + +sealed class TapError( + @StringRes val localizedMessage: Int, + override val args: List? = null +) : Throwable(), TapErrors, ArgError { + + object UnknownError : TapError(R.string.error_unknown) + object PayIdAlreadyCreated : TapError(R.string.error_payid_already_created) + object PayIdCreatingError : TapError(R.string.error_creating_payid) + object PayIdEmptyField : TapError(R.string.wallet_create_payid_empty) + object UnknownBlockchain : TapError(R.string.wallet_unknown_blockchain) + object NoInternetConnection : TapError(R.string.notification_no_internet) + object InsufficientBalance : TapError(R.string.error_insufficient_balance) + object BlockchainInternalError : TapError(R.string.error_blockchain_internal) + object AmountExceedsBalance : TapError(R.string.amount_exceeds_balance) + object FeeExceedsBalance : TapError(R.string.fee_exceeds_balance) + object TotalExceedsBalance : TapError(R.string.total_exceeds_balance) + object InvalidAmountValue : TapError(R.string.invalid_amount_value) + object InvalidFeeValue : TapError(R.string.invalid_fee_value) + data class DustAmount(override val args: List) : TapError(R.string.dust_amount) + object DustChange : TapError(R.string.dust_change) + data class CreateAccountUnderfunded(override val args: List) : TapError(R.string.create_account_underfunded) + + data class ValidateTransactionErrors( + override val errorList: List, + override val builder: (List) -> String + ) : TapError(-1), MultiMessageError +} + +sealed class TapSdkError(override val messageResId: Int?) : Throwable(), TangemError { + final override val code: Int = 1 + override var customMessage: String = code.toString() + + object CardForDifferentApp : TapSdkError(R.string.error_card_for_different_app) +} + + +fun TapErrors.assembleErrors(): MutableList?>> { + val idList = mutableListOf?>>() + when (this) { + is MultiMessageError -> this.errorList.forEach { idList.addAll(it.assembleErrors()) } + is TapError -> idList.add(Pair(this.localizedMessage, this.args)) + } + return idList +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt new file mode 100644 index 0000000000..c6e77e8a9f --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -0,0 +1,189 @@ +package com.tangem.tap.domain + +import com.tangem.blockchain.common.Wallet +import com.tangem.blockchain.common.WalletManager +import com.tangem.commands.CardStatus +import com.tangem.commands.common.network.Result +import com.tangem.common.extensions.toHexString +import com.tangem.tap.TapConfig +import com.tangem.tap.common.redux.global.CryptoCurrencyName +import com.tangem.tap.common.redux.global.FiatCurrencyName +import com.tangem.tap.common.redux.global.GlobalAction +import com.tangem.tap.domain.extensions.amountToCreateAccount +import com.tangem.tap.domain.extensions.isNoAccountError +import com.tangem.tap.domain.tasks.ScanNoteResponse +import com.tangem.tap.features.wallet.redux.PayIdState +import com.tangem.tap.features.wallet.redux.WalletAction +import com.tangem.tap.network.NetworkConnectivity +import com.tangem.tap.network.coinmarketcap.CoinMarketCapService +import com.tangem.tap.store +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import java.math.BigDecimal + + +class TapWalletManager { + private val payIdManager = PayIdManager() + private val coinMarketCapService = CoinMarketCapService() + + suspend fun loadWalletData() { + val walletManager = store.state.globalState.scanNoteResponse?.walletManager + if (walletManager == null) { + store.dispatch(WalletAction.LoadWallet.Failure()) + return + } + loadWallet(walletManager) + } + + suspend fun loadPayId() { + val result = loadPayIdIfNeeded() + result?.let { handlePayIdResult(it) } + } + + suspend fun updateWallet() { + val walletManager = store.state.globalState.scanNoteResponse?.walletManager + if (walletManager == null) { + store.dispatch(WalletAction.UpdateWallet.Failure()) + return + } + val result = try { + walletManager.update() + Result.Success(walletManager.wallet) + } catch (exception: Exception) { + Result.Failure(exception) + } + withContext(Dispatchers.Main) { + when (result) { + is Result.Success -> store.dispatch(WalletAction.UpdateWallet.Success(result.data)) + is Result.Failure -> + store.dispatch(WalletAction.UpdateWallet.Failure(result.error?.localizedMessage)) + } + } + + } + + suspend fun loadFiatRate(fiatCurrency: FiatCurrencyName) { + val wallet = store.state.globalState.scanNoteResponse?.walletManager?.wallet + val blockchainCurrency = wallet?.blockchain?.currency + val tokenCurrency = wallet?.token?.symbol + + val blockchainRate = blockchainCurrency?.let { coinMarketCapService.getRate(it, fiatCurrency) } + val tokenRate = tokenCurrency?.let { coinMarketCapService.getRate(it, fiatCurrency) } + + val results = mutableListOf?>>() + if (blockchainCurrency != null) results.add(blockchainCurrency to blockchainRate) + if (tokenCurrency != null) results.add(tokenCurrency to tokenRate) + + handleFiatRatesResult(results) + } + + suspend fun onCardScanned(data: ScanNoteResponse) { + withContext(Dispatchers.Main) { + store.dispatch(WalletAction.ResetState) + store.dispatch(GlobalAction.SaveScanNoteResponse(data)) + loadData(data) + } + } + + suspend fun loadData(data: ScanNoteResponse) { + withContext(Dispatchers.Main) { + store.dispatch(WalletAction.CheckIfWarningNeeded) + val artworkId = data.verifyResponse?.artworkInfo?.id + if (data.walletManager != null) { + if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) { + store.dispatch(WalletAction.LoadData.Failure(TapError.NoInternetConnection)) + return@withContext + } + store.dispatch(WalletAction.LoadWallet) + store.dispatch(WalletAction.LoadArtwork(data.card, artworkId)) + store.dispatch(WalletAction.LoadFiatRate) + store.dispatch(WalletAction.LoadPayId) + } else if (data.card.status == CardStatus.Empty) { + store.dispatch(WalletAction.EmptyWallet) + store.dispatch(WalletAction.LoadArtwork(data.card, artworkId)) + } else { + store.dispatch(WalletAction.LoadData.Failure(TapError.UnknownBlockchain)) + store.dispatch(WalletAction.LoadArtwork(data.card, artworkId)) + } + } + } + + private suspend fun loadWallet(walletManager: WalletManager) { + val result = try { + walletManager.update() + Result.Success(walletManager.wallet) + } catch (exception: Exception) { + Result.Failure(exception) + } + handleUpdateWalletResult(result, walletManager) + } + + private suspend fun handleUpdateWalletResult(result: Result, walletManager: WalletManager) { + withContext(Dispatchers.Main) { + when (result) { + is Result.Success -> store.dispatch(WalletAction.LoadWallet.Success(result.data)) + is Result.Failure -> { + val error = result.error + val blockchain = walletManager.wallet.blockchain + if (error != null && blockchain.isNoAccountError(error)) { + val amountToCreateAccount = blockchain.amountToCreateAccount(walletManager.wallet.token) + if (amountToCreateAccount != null) { + store.dispatch(WalletAction.LoadWallet.NoAccount(amountToCreateAccount.toString())) + return@withContext + } + } + store.dispatch(WalletAction.LoadWallet.Failure(result.error?.localizedMessage)) + } + } + } + } + + + private suspend fun loadPayIdIfNeeded(): Result? { + val scanNoteResponse = store.state.globalState.scanNoteResponse + if (!TapConfig.usePayId || + store.state.walletState.payIdData.payIdState == PayIdState.Disabled || + scanNoteResponse?.walletManager?.wallet?.blockchain?.isPayIdSupported() == false) { + return null + } + val cardId = scanNoteResponse?.card?.cardId + val publicKey = scanNoteResponse?.card?.cardPublicKey + if (cardId == null || publicKey == null) { + return null + } + return payIdManager.getPayId(cardId, publicKey.toHexString()) + } + + private suspend fun handlePayIdResult(result: Result) { + withContext(Dispatchers.Main) { + when (result) { + is Result.Success -> { + val payId = result.data + if (payId == null) { + store.dispatch(WalletAction.LoadPayId.NotCreated) + } else { + store.dispatch(WalletAction.LoadPayId.Success(payId)) + } + } + is Result.Failure -> store.dispatch(WalletAction.LoadPayId.Failure) + } + } + } + + private suspend fun handleFiatRatesResult(results: List?>>) { + withContext(Dispatchers.Main) { + results.map { + when (it.second) { + is Result.Success -> { + val rate = it.first to (it.second as Result.Success).data + store.dispatch(GlobalAction.SetFiatRate(rate)) + store.dispatch(WalletAction.LoadFiatRate.Success(rate)) + } + is Result.Failure -> store.dispatch(WalletAction.LoadFiatRate.Failure) + null -> { + } + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/extensions/Amount.kt b/app/src/main/java/com/tangem/tap/domain/extensions/Amount.kt new file mode 100644 index 0000000000..08703ba05c --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/extensions/Amount.kt @@ -0,0 +1,11 @@ +package com.tangem.tap.domain.extensions + +import com.tangem.blockchain.common.Amount +import com.tangem.blockchain.common.AmountType +import com.tangem.blockchain.extensions.isAboveZero + +fun Map.toSendableAmounts(): List { + return this.toList().unzip().second + .filter { it.type != AmountType.Reserve } + .filter { it.isAboveZero() } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/extensions/BlockchainExtensions.kt b/app/src/main/java/com/tangem/tap/domain/extensions/BlockchainExtensions.kt new file mode 100644 index 0000000000..b9866e5359 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/extensions/BlockchainExtensions.kt @@ -0,0 +1,26 @@ +package com.tangem.tap.domain.extensions + +import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchain.common.Token +import org.stellar.sdk.requests.ErrorResponse + + +fun Blockchain.isNoAccountError(exception: Throwable): Boolean { + return when (this) { + Blockchain.Stellar -> { + (exception is ErrorResponse) && (exception.code == 404) + } + Blockchain.XRP -> exception.message?.contains("Account not found") == true + else -> false + } +} + +fun Blockchain.amountToCreateAccount(token: Token? = null): Double? { + return when (this) { + Blockchain.Stellar -> if (token?.symbol == NODL) 1.5 else 1.toDouble() + Blockchain.XRP -> 20.toDouble() + else -> null + } +} + +private const val NODL = "NODL" \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/CreateWalletAndRescanTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/CreateWalletAndRescanTask.kt new file mode 100644 index 0000000000..0af8e1f29b --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/tasks/CreateWalletAndRescanTask.kt @@ -0,0 +1,27 @@ +package com.tangem.tap.domain.tasks + +import com.tangem.CardSession +import com.tangem.CardSessionRunnable +import com.tangem.commands.ReadCommand +import com.tangem.common.CompletionResult +import com.tangem.tasks.CreateWalletTask + +class CreateWalletAndRescanTask : CardSessionRunnable { + override val requiresPin2 = false + + override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { + CreateWalletTask().run(session) { result -> + when (result) { + is CompletionResult.Success -> { + ReadCommand().run(session) { readResult -> + when (readResult) { + is CompletionResult.Success -> ScanNoteTask(readResult.data).run(session, callback) + is CompletionResult.Failure -> callback(CompletionResult.Failure(readResult.error)) + } + } + } + is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/ScanNoteTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/ScanNoteTask.kt new file mode 100644 index 0000000000..93a2617258 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/tasks/ScanNoteTask.kt @@ -0,0 +1,84 @@ +package com.tangem.tap.domain.tasks + +import com.tangem.CardSession +import com.tangem.CardSessionRunnable +import com.tangem.TangemError +import com.tangem.TangemSdkError +import com.tangem.blockchain.common.WalletManager +import com.tangem.blockchain.common.WalletManagerFactory +import com.tangem.commands.Card +import com.tangem.commands.CardStatus +import com.tangem.commands.CommandResponse +import com.tangem.commands.Product +import com.tangem.commands.verifycard.VerifyCardCommand +import com.tangem.commands.verifycard.VerifyCardResponse +import com.tangem.common.CompletionResult +import com.tangem.tap.domain.TapSdkError +import com.tangem.tasks.ScanTask + +data class ScanNoteResponse( + val walletManager: WalletManager?, + val card: Card, + val verifyResponse: VerifyCardResponse? = null +) : CommandResponse + +class ScanNoteTask(val card: Card? = null) : CardSessionRunnable { + override val requiresPin2 = false + + override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { + ScanTask().run(session) { result -> + when (result) { + is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) + + is CompletionResult.Success -> { + val card = this.card?.copy( + isPin1Default = result.data.isPin1Default, + isPin2Default = result.data.isPin2Default + ) ?: result.data + + val error = getErrorIfExcludedCard(card) + if (error != null) { + callback(CompletionResult.Failure(error)) + return@run + } + + val walletManager = try { + WalletManagerFactory.makeWalletManager(card) + } catch (exception: Exception) { + return@run callback(CompletionResult.Success(ScanNoteResponse(null, card))) + } + VerifyCardCommand(true).run(session) { verifyResult -> + when (verifyResult) { + is CompletionResult.Success -> { + callback(CompletionResult.Success(ScanNoteResponse( + walletManager, card, verifyResult.data + ))) + } + is CompletionResult.Failure -> { + callback(CompletionResult.Failure(TangemSdkError.VerificationFailed())) + } + } + } + } + } + } + } + + private fun getErrorIfExcludedCard(card: Card): TangemError? { + if (card.cardData?.productMask != null && + card.cardData?.productMask?.contains(Product.Note) != true) { + return TapSdkError.CardForDifferentApp + } + if (excludedBatches.contains(card.cardData?.batchId)) { + return TapSdkError.CardForDifferentApp + } + if (card.status == CardStatus.Purged) return TangemSdkError.CardIsPurged() + if (card.status == CardStatus.NotPersonalized) return TangemSdkError.NotPersonalized() + + return null + } + + companion object { + private val excludedBatches = listOf("0027", "0030", "0031") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt new file mode 100644 index 0000000000..1b3d174176 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt @@ -0,0 +1,58 @@ +package com.tangem.tap.features.details.redux + +import com.tangem.blockchain.common.Wallet +import com.tangem.commands.Card +import com.tangem.tap.common.redux.NotificationAction +import com.tangem.tap.common.redux.global.FiatCurrencyName +import com.tangem.tap.network.coinmarketcap.FiatCurrency +import com.tangem.wallet.R +import org.rekotlin.Action + +sealed class DetailsAction : Action { + + data class PrepareScreen( + val card: Card, + val wallet: Wallet?, + val fiatCurrencyName: FiatCurrencyName, + val fiatCurrencies: List? = null, + ): DetailsAction() + + + sealed class EraseWallet : DetailsAction() { + object Check : EraseWallet() + object Proceed : EraseWallet() { + object NotAllowedByCard: EraseWallet(), NotificationAction { + override val messageResource = R.string.details_notification_erase_wallet_not_allowed + } + object NotEmpty: EraseWallet(), NotificationAction { + override val messageResource = R.string.details_notification_erase_wallet_not_possible + } + } + object Confirm : EraseWallet() + object Cancel : EraseWallet() + object Failure : EraseWallet() + object Success : EraseWallet() + } + + sealed class AppCurrencyAction : DetailsAction() { + data class SetCurrencies(val currencies: List) : AppCurrencyAction() + object ChooseAppCurrency : AppCurrencyAction() + object Cancel: AppCurrencyAction() + data class SelectAppCurrency(val fiatCurrencyName: FiatCurrencyName): AppCurrencyAction() + } + + sealed class ManageSecurity : DetailsAction() { + object OpenSecurity : ManageSecurity() + data class SelectOption(val option: SecurityOption) : ManageSecurity() + object SaveChanges : ManageSecurity() { + object Success : ManageSecurity() + object Failure : ManageSecurity() + } + data class ConfirmSelection(val option: SecurityOption) : ManageSecurity() { + object AlreadySet: ManageSecurity(), NotificationAction { + override val messageResource = R.string.details_notification_security_option_already_active + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt new file mode 100644 index 0000000000..b70f14c6d5 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt @@ -0,0 +1,151 @@ +package com.tangem.tap.features.details.redux + +import com.tangem.commands.common.network.Result +import com.tangem.common.CompletionResult +import com.tangem.tap.common.redux.AppState +import com.tangem.tap.common.redux.global.GlobalAction +import com.tangem.tap.common.redux.navigation.AppScreen +import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.features.wallet.redux.WalletAction +import com.tangem.tap.network.coinmarketcap.CoinMarketCapService +import com.tangem.tap.preferencesStorage +import com.tangem.tap.scope +import com.tangem.tap.store +import com.tangem.tap.tangemSdkManager +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.rekotlin.Middleware + +class DetailsMiddleware { + private val eraseWalletMiddleware = EraseWalletMiddleware() + private val appCurrencyMiddleware = AppCurrencyMiddleware() + private val manageSecurityMiddleware = ManageSecurityMiddleware() + val detailsMiddleware: Middleware = { dispatch, state -> + { next -> + { action -> + when (action) { + is DetailsAction.PrepareScreen -> prepareData() + is DetailsAction.EraseWallet -> eraseWalletMiddleware.handle(action) + is DetailsAction.AppCurrencyAction -> appCurrencyMiddleware.handle(action) + is DetailsAction.ManageSecurity -> manageSecurityMiddleware.handle(action) + } + next(action) + } + } + } + + private fun prepareData() { + scope.launch { + val loadedCurrencies = preferencesStorage.getFiatCurrencies() + if (loadedCurrencies.isNullOrEmpty()) { + val response = CoinMarketCapService().getFiatCurrencies() + withContext(Dispatchers.Main) { + when (response) { + is Result.Success -> { + preferencesStorage.saveFiatCurrencies(response.data) + store.dispatch(DetailsAction.AppCurrencyAction.SetCurrencies(response.data)) + } + } + } + } else { + withContext(Dispatchers.Main) { + store.dispatch(DetailsAction.AppCurrencyAction.SetCurrencies(loadedCurrencies)) + } + } + } + } + + class EraseWalletMiddleware() { + fun handle(action: DetailsAction.EraseWallet) { + when (action) { + is DetailsAction.EraseWallet.Proceed -> { + when (store.state.detailsState.eraseWalletState) { + EraseWalletState.Allowed -> + store.dispatch(NavigationAction.NavigateTo(AppScreen.DetailsConfirm)) + EraseWalletState.NotAllowedByCard -> + store.dispatch(DetailsAction.EraseWallet.Proceed.NotAllowedByCard) + EraseWalletState.NotEmpty -> + store.dispatch(DetailsAction.EraseWallet.Proceed.NotEmpty) + } + } + is DetailsAction.EraseWallet.Cancel -> { + store.dispatch(NavigationAction.PopBackTo()) + } + is DetailsAction.EraseWallet.Confirm -> { + scope.launch { + val result = tangemSdkManager.eraseWallet( + store.state.detailsState.card?.cardId + ) + withContext(Dispatchers.Main) { + when (result) { + is CompletionResult.Success -> { + store.dispatch(NavigationAction.PopBackTo(AppScreen.Home)) + } + } + } + } + } + } + } + } + + class AppCurrencyMiddleware { + fun handle(action: DetailsAction.AppCurrencyAction) { + when (action) { + is DetailsAction.AppCurrencyAction.SelectAppCurrency -> { + preferencesStorage.saveAppCurrency(action.fiatCurrencyName) + store.dispatch(GlobalAction.ChangeAppCurrency(action.fiatCurrencyName)) + store.dispatch(WalletAction.LoadFiatRate) + } + } + } + } + + class ManageSecurityMiddleware { + fun handle(action: DetailsAction.ManageSecurity) { + when (action) { + is DetailsAction.ManageSecurity.OpenSecurity -> { + store.dispatch(NavigationAction.NavigateTo(AppScreen.DetailsSecurity)) + } + is DetailsAction.ManageSecurity.ConfirmSelection -> { + if (action.option != store.state.detailsState.securityScreenState?.currentOption) { + if (action.option == SecurityOption.LongTap) { + store.dispatch(DetailsAction.ManageSecurity.SaveChanges) + } else { + store.dispatch(NavigationAction.NavigateTo(AppScreen.DetailsConfirm)) + } + } else { + store.dispatch(DetailsAction.ManageSecurity.ConfirmSelection.AlreadySet) + } + } + is DetailsAction.ManageSecurity.SaveChanges -> { + val cardId = store.state.detailsState.card?.cardId + val selectedOption = store.state.detailsState.securityScreenState?.selectedOption + scope.launch { + val result = when (selectedOption) { + SecurityOption.LongTap -> tangemSdkManager.setLongTap(cardId) + SecurityOption.PassCode -> tangemSdkManager.setPasscode(cardId) + SecurityOption.AccessCode -> tangemSdkManager.setAccessCode(cardId) + else -> null + } + withContext(Dispatchers.Main) { + when (result) { + is CompletionResult.Success -> { + if (selectedOption != SecurityOption.LongTap) { + store.dispatch(NavigationAction.PopBackTo()) + } + store.dispatch(DetailsAction.ManageSecurity.SaveChanges.Success) + } + is CompletionResult.Failure, null -> + store.dispatch(DetailsAction.ManageSecurity.SaveChanges.Failure) + } + } + + } + + } + } + } + } +} diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt new file mode 100644 index 0000000000..de11a23644 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt @@ -0,0 +1,169 @@ +package com.tangem.tap.features.details.redux + +import com.tangem.commands.Card +import com.tangem.commands.Settings +import com.tangem.tap.common.redux.AppState +import com.tangem.tap.domain.extensions.toSendableAmounts +import org.rekotlin.Action +import java.util.* + +class DetailsReducer { + companion object { + fun reduce(action: Action, state: AppState): DetailsState = internalReduce(action, state) + } +} + +private fun internalReduce(action: Action, state: AppState): DetailsState { + if (action !is DetailsAction) return state.detailsState + + val detailsState = state.detailsState + return when (action) { + is DetailsAction.PrepareScreen -> { + handlePrepareScreen(action, detailsState) + } + is DetailsAction.EraseWallet -> { + handleEraseWallet(action, detailsState) + } + is DetailsAction.AppCurrencyAction -> { + handleAppCurrencyAction(action, detailsState) + } + is DetailsAction.ManageSecurity -> { + handleSecurityAction(action, detailsState) + } + } +} + +private fun handlePrepareScreen(action: DetailsAction.PrepareScreen, state: DetailsState): DetailsState { + val securityOption = when { + action.card.isPin1Default == false -> { + SecurityOption.AccessCode + } + action.card.isPin2Default == false -> { + SecurityOption.PassCode + } + else -> { + SecurityOption.LongTap + } + } + return DetailsState( + card = action.card, wallet = action.wallet, + cardInfo = action.card.toCardInfo(), + appCurrencyState = AppCurrencyState( + action.fiatCurrencyName + ), + securityScreenState = SecurityScreenState(currentOption = securityOption) + ) +} + +private fun handleEraseWallet(action: DetailsAction.EraseWallet, state: DetailsState): DetailsState { + return when (action) { + DetailsAction.EraseWallet.Check -> { + val notAllowedByCard = state.card?.settingsMask?.contains(Settings.ProhibitPurgeWallet) == true + val notEmpty = state.wallet?.recentTransactions?.isNullOrEmpty() != true || + state.wallet.amounts.toSendableAmounts().isNotEmpty() + val eraseWalletState = when { + notAllowedByCard -> EraseWalletState.NotAllowedByCard + notEmpty -> EraseWalletState.NotEmpty + else -> EraseWalletState.Allowed + } + state.copy(eraseWalletState = eraseWalletState) + } + DetailsAction.EraseWallet.Proceed -> { + if (state.eraseWalletState == EraseWalletState.Allowed) { + state.copy(confirmScreenState = ConfirmScreenState.EraseWallet) + } else { + state + } + } + DetailsAction.EraseWallet.Cancel -> state.copy(eraseWalletState = null) + DetailsAction.EraseWallet.Failure -> state.copy(eraseWalletState = null) + DetailsAction.EraseWallet.Success -> state.copy(eraseWalletState = null) + else -> state + } +} + +private fun handleAppCurrencyAction( + action: DetailsAction.AppCurrencyAction, state: DetailsState +): DetailsState { + return when (action) { + is DetailsAction.AppCurrencyAction.SetCurrencies -> { + state.copy(appCurrencyState = state.appCurrencyState.copy(fiatCurrencies = action.currencies)) + } + DetailsAction.AppCurrencyAction.ChooseAppCurrency -> { + state.copy(appCurrencyState = state.appCurrencyState.copy(showAppCurrencyDialog = true)) + } + DetailsAction.AppCurrencyAction.Cancel -> { + state.copy(appCurrencyState = state.appCurrencyState.copy(showAppCurrencyDialog = false)) + } + is DetailsAction.AppCurrencyAction.SelectAppCurrency -> { + state.copy( + appCurrencyState = state.appCurrencyState.copy( + fiatCurrencyName = action.fiatCurrencyName, showAppCurrencyDialog = false + ) + ) + } + else -> state + } +} + +private fun handleSecurityAction( + action: DetailsAction.ManageSecurity, state: DetailsState +): DetailsState { + return when (action) { + is DetailsAction.ManageSecurity.OpenSecurity -> { + if (state.card?.isPin2Default == null) { + return state.copy(securityScreenState = state.securityScreenState?.copy( + allowedOptions = EnumSet.noneOf(SecurityOption::class.java) + )) + } + val prohibitDefaultPin = state.card.settingsMask?.contains(Settings.ProhibitDefaultPIN1) == true + val allowSetPin1 = state.card.settingsMask?.contains(Settings.AllowSetPIN1) != false + val allowSetPin2 = state.card.settingsMask?.contains(Settings.AllowSetPIN2) != false + val isDefaultPin1 = state.card.isPin1Default != false + val isDefaultPin2 = state.card.isPin2Default != false + + val allowedSecurityOptions = EnumSet.noneOf(SecurityOption::class.java) + if ((isDefaultPin1 && isDefaultPin2) || !prohibitDefaultPin) { + allowedSecurityOptions.add(SecurityOption.LongTap) + } + if (allowSetPin1 && (isDefaultPin2 || !prohibitDefaultPin)) { + allowedSecurityOptions.add(SecurityOption.AccessCode) + } + if (allowSetPin2 && (isDefaultPin1 || !prohibitDefaultPin)) { + allowedSecurityOptions.add(SecurityOption.PassCode) + } + + state.copy(securityScreenState = state.securityScreenState?.copy( + allowedOptions = allowedSecurityOptions, + selectedOption = state.securityScreenState.currentOption + )) + } + is DetailsAction.ManageSecurity.SelectOption -> { + state.copy(securityScreenState = state.securityScreenState?.copy( + selectedOption = action.option + )) + } + is DetailsAction.ManageSecurity.ConfirmSelection -> { + val confirmScreenState = when (action.option) { + SecurityOption.LongTap -> ConfirmScreenState.LongTap + SecurityOption.PassCode -> ConfirmScreenState.PassCode + SecurityOption.AccessCode -> ConfirmScreenState.AccessCode + } + state.copy(confirmScreenState = confirmScreenState) + } + is DetailsAction.ManageSecurity.SaveChanges.Success -> { + state.copy(securityScreenState = state.securityScreenState?.copy( + currentOption = state.securityScreenState.selectedOption + )) + } + + else -> state + } +} + +private fun Card.toCardInfo(): CardInfo? { + val cardId = this.cardId.chunked(4).joinToString(separator = " ") + val issuer = this.cardData?.issuerName ?: return null + val signedHashes = this.walletSignedHashes ?: return null + return CardInfo(cardId, issuer, signedHashes) +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt new file mode 100644 index 0000000000..7c152c57ee --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt @@ -0,0 +1,42 @@ +package com.tangem.tap.features.details.redux + +import com.tangem.blockchain.common.Wallet +import com.tangem.commands.Card +import com.tangem.tap.common.entities.Button +import com.tangem.tap.common.entities.TapCurrency.Companion.DEFAULT_FIAT_CURRENCY +import com.tangem.tap.common.redux.global.FiatCurrencyName +import com.tangem.tap.network.coinmarketcap.FiatCurrency +import org.rekotlin.StateType +import java.util.* + +data class DetailsState( + val card: Card? = null, + val wallet: Wallet? = null, + val cardInfo: CardInfo? = null, + val appCurrencyState: AppCurrencyState = AppCurrencyState(), + val eraseWalletState: EraseWalletState? = null, + val confirmScreenState: ConfirmScreenState? = null, + val securityScreenState: SecurityScreenState? = null, +) : StateType + +data class CardInfo( + val cardId: String, + val issuer: String, + val signedHashes: Int +) + +enum class EraseWalletState { Allowed, NotAllowedByCard, NotEmpty } +enum class ConfirmScreenState { EraseWallet, LongTap, AccessCode, PassCode } +data class SecurityScreenState( + val currentOption: SecurityOption = SecurityOption.LongTap, + val selectedOption: SecurityOption = currentOption, + val allowedOptions: EnumSet = EnumSet.allOf(SecurityOption::class.java), + val buttonProceed: Button = Button(true) +) + +enum class SecurityOption { LongTap, PassCode, AccessCode } +data class AppCurrencyState( + val fiatCurrencyName: FiatCurrencyName = DEFAULT_FIAT_CURRENCY, + val showAppCurrencyDialog: Boolean = false, + val fiatCurrencies: List? = null, +) diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/CurrencySelectionDialog.kt b/app/src/main/java/com/tangem/tap/features/details/ui/CurrencySelectionDialog.kt new file mode 100644 index 0000000000..dc00b896f8 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/CurrencySelectionDialog.kt @@ -0,0 +1,44 @@ +package com.tangem.tap.features.details.ui + +import android.content.Context +import androidx.appcompat.app.AlertDialog +import com.tangem.tap.common.extensions.toFormattedString +import com.tangem.tap.common.redux.global.FiatCurrencyName +import com.tangem.tap.features.details.redux.DetailsAction +import com.tangem.tap.network.coinmarketcap.FiatCurrency +import com.tangem.tap.store +import com.tangem.wallet.R + +class CurrencySelectionDialog { + + var dialog: AlertDialog? = null + + fun show(currencies: List, currentAppCurrency: FiatCurrencyName, context: Context) { + + if (dialog == null) { + val currenciesToShow = currencies.map { it.toFormattedString() }.toTypedArray() + var currentSelection = currencies.indexOfFirst { it.symbol == currentAppCurrency } + + dialog = AlertDialog.Builder(context) + .setTitle(context.getString(R.string.details_currency)) + .setNegativeButton(context.getString(R.string.generic_cancel)) { _, _ -> + store.dispatch(DetailsAction.AppCurrencyAction.Cancel) + } + .setPositiveButton(context.getString(R.string.generic_done)) { _, _ -> + val selectedCurrency = currencies[currentSelection] + store.dispatch(DetailsAction.AppCurrencyAction.SelectAppCurrency(selectedCurrency.symbol)) + } + .setOnDismissListener { + store.dispatch(DetailsAction.AppCurrencyAction.Cancel) + } + .setSingleChoiceItems(currenciesToShow, currentSelection) { _, which -> + currentSelection = which + }.show() + } + } + + fun clear() { + dialog = null + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/DetailsConfirmFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/DetailsConfirmFragment.kt new file mode 100644 index 0000000000..d75799b5c1 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/DetailsConfirmFragment.kt @@ -0,0 +1,84 @@ +package com.tangem.tap.features.details.ui + +import android.os.Bundle +import android.view.View +import androidx.activity.OnBackPressedCallback +import androidx.fragment.app.Fragment +import androidx.transition.TransitionInflater +import com.tangem.tap.common.extensions.getDrawable +import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.features.details.redux.ConfirmScreenState +import com.tangem.tap.features.details.redux.DetailsAction +import com.tangem.tap.features.details.redux.DetailsState +import com.tangem.tap.store +import com.tangem.wallet.R +import kotlinx.android.synthetic.main.fragment_details_confirm.* +import org.rekotlin.StoreSubscriber + +class DetailsConfirmFragment : Fragment(R.layout.fragment_details_confirm), + StoreSubscriber { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + store.dispatch(NavigationAction.PopBackTo()) + } + }) + val inflater = TransitionInflater.from(requireContext()) + enterTransition = inflater.inflateTransition(R.transition.slide_right) + exitTransition = inflater.inflateTransition(R.transition.fade) + } + + override fun onStart() { + super.onStart() + store.subscribe(this) { state -> + state.skipRepeats { oldState, newState -> + oldState.detailsState == newState.detailsState + }.select { it.detailsState } + } + } + + override fun onStop() { + super.onStop() + store.unsubscribe(this) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + toolbar.setNavigationOnClickListener { + store.dispatch(NavigationAction.PopBackTo()) + } + } + + + override fun newState(state: DetailsState) { + if (activity == null) return + + when (state.confirmScreenState) { + ConfirmScreenState.EraseWallet -> { + toolbar.title = getString(R.string.details_erase_wallet) + tv_warning_description.text = getString(R.string.details_erase_wallet_warning) + btn_confirm.text = getString(R.string.details_erase_wallet) + btn_confirm.setCompoundDrawablesRelativeWithIntrinsicBounds( + null, null, getDrawable(R.drawable.ic_send), null + ) + btn_confirm.setOnClickListener { store.dispatch(DetailsAction.EraseWallet.Confirm) } + } + ConfirmScreenState.LongTap, ConfirmScreenState.AccessCode, + ConfirmScreenState.PassCode -> { + toolbar.title = getString(R.string.details_manage_security) + tv_warning_description.text = getString(R.string.details_manage_security_warning) + btn_confirm.text = getString(R.string.details_save_changes) + btn_confirm.setCompoundDrawablesRelativeWithIntrinsicBounds( + null, null, getDrawable(R.drawable.ic_save), null + ) + btn_confirm.setOnClickListener { store.dispatch(DetailsAction.ManageSecurity.SaveChanges) } + } + } + + + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt new file mode 100644 index 0000000000..f4bdf9519a --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt @@ -0,0 +1,103 @@ +package com.tangem.tap.features.details.ui + +import android.os.Bundle +import android.view.View +import androidx.activity.OnBackPressedCallback +import androidx.fragment.app.Fragment +import androidx.transition.TransitionInflater +import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.features.details.redux.DetailsAction +import com.tangem.tap.features.details.redux.DetailsState +import com.tangem.tap.features.details.redux.SecurityOption +import com.tangem.tap.store +import com.tangem.wallet.R +import kotlinx.android.synthetic.main.fragment_details.* +import kotlinx.android.synthetic.main.fragment_wallet.toolbar +import org.rekotlin.StoreSubscriber + +class DetailsFragment : Fragment(R.layout.fragment_details), StoreSubscriber { + + private var currencySelectionDialog = CurrencySelectionDialog() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + store.dispatch(NavigationAction.PopBackTo()) + } + }) + val inflater = TransitionInflater.from(requireContext()) + enterTransition = inflater.inflateTransition(R.transition.slide_right) + exitTransition = inflater.inflateTransition(R.transition.fade) + } + + override fun onStart() { + super.onStart() + store.subscribe(this) { state -> + state.skipRepeats { oldState, newState -> + oldState.detailsState == newState.detailsState + }.select { it.detailsState } + } + } + + override fun onStop() { + super.onStop() + store.unsubscribe(this) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + toolbar.setNavigationOnClickListener { + store.dispatch(NavigationAction.PopBackTo()) + } + } + + + override fun newState(state: DetailsState) { + if (activity == null) return + + + if (state.cardInfo != null) { + tv_card_id.text = state.cardInfo.cardId + tv_issuer.text = state.cardInfo.issuer + tv_signed_hashes.text = state.cardInfo.signedHashes.toString() + } + + tv_erase_wallet.setOnClickListener { + store.dispatch(DetailsAction.EraseWallet.Check) + store.dispatch(DetailsAction.EraseWallet.Proceed) + } + + tv_app_currency.text = state.appCurrencyState.fiatCurrencyName + + tv_app_currency_title.setOnClickListener { + store.dispatch(DetailsAction.AppCurrencyAction.ChooseAppCurrency) + } + + tv_security_title.setOnClickListener { + store.dispatch(DetailsAction.ManageSecurity.OpenSecurity) + } + + val currentSecurity = when (state.securityScreenState?.currentOption) { + SecurityOption.LongTap -> R.string.details_manage_security_long_tap + SecurityOption.PassCode -> R.string.details_manage_security_passcode + SecurityOption.AccessCode -> R.string.details_manage_security_access_code + null -> null + } + currentSecurity?.let { tv_security.text = getString(it) } + + if (state.appCurrencyState.showAppCurrencyDialog && + !state.appCurrencyState.fiatCurrencies.isNullOrEmpty()) { + currencySelectionDialog.show( + state.appCurrencyState.fiatCurrencies, + state.appCurrencyState.fiatCurrencyName, + requireContext() + ) + } else { + currencySelectionDialog.clear() + } + + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/DetailsSecurityFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/DetailsSecurityFragment.kt new file mode 100644 index 0000000000..caa822e069 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/DetailsSecurityFragment.kt @@ -0,0 +1,134 @@ +package com.tangem.tap.features.details.ui + +import android.os.Bundle +import android.view.View +import androidx.activity.OnBackPressedCallback +import androidx.fragment.app.Fragment +import androidx.transition.TransitionInflater +import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.features.details.redux.DetailsAction +import com.tangem.tap.features.details.redux.DetailsState +import com.tangem.tap.features.details.redux.SecurityOption +import com.tangem.tap.store +import com.tangem.wallet.R +import kotlinx.android.synthetic.main.fragment_details_confirm.toolbar +import kotlinx.android.synthetic.main.fragment_details_security.* +import org.rekotlin.StoreSubscriber +import java.util.* + +class DetailsSecurityFragment : Fragment(R.layout.fragment_details_security), + StoreSubscriber { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + store.dispatch(NavigationAction.PopBackTo()) + } + }) + val inflater = TransitionInflater.from(requireContext()) + enterTransition = inflater.inflateTransition(R.transition.slide_right) + exitTransition = inflater.inflateTransition(R.transition.fade) + } + + override fun onStart() { + super.onStart() + store.subscribe(this) { state -> + state.skipRepeats { oldState, newState -> + oldState.detailsState == newState.detailsState + }.select { it.detailsState } + } + } + + override fun onStop() { + super.onStop() + store.unsubscribe(this) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + toolbar.setNavigationOnClickListener { + store.dispatch(NavigationAction.PopBackTo()) + } + setOnClickListeners() + } + + private fun setOnClickListeners() { + btn_save_changes.setOnClickListener { + store.state.detailsState.securityScreenState?.selectedOption?.let { + store.dispatch(DetailsAction.ManageSecurity.ConfirmSelection(it)) + } + } + } + + override fun newState(state: DetailsState) { + if (activity == null) return + selectSecurityOption(state.securityScreenState?.selectedOption) + for (option in SecurityOption.values()) { + setupOption( + option, + state.securityScreenState?.allowedOptions + ?: EnumSet.noneOf(SecurityOption::class.java) + ) + } + } + + private fun selectSecurityOption(securityOption: SecurityOption?) { + radiobutton_long_tap.isChecked = + securityOption == SecurityOption.LongTap && radiobutton_long_tap.isEnabled + radiobutton_passcode.isChecked = + securityOption == SecurityOption.PassCode && radiobutton_passcode.isEnabled + radiobutton_access_code.isChecked = + securityOption == SecurityOption.AccessCode && radiobutton_access_code.isEnabled + } + + private fun setupOption(option: SecurityOption, allowedOptions: EnumSet) { + when (option) { + SecurityOption.LongTap -> { enableLongTap(allowedOptions.contains(option)) } + SecurityOption.PassCode -> { enablePasscode(allowedOptions.contains(option)) } + SecurityOption.AccessCode -> { enableAccessCode(allowedOptions.contains(option)) } + } + } + + private fun enableLongTap(enable: Boolean) { + val alpha = if (enable) 1f else 0.5f + tv_long_tap_description.alpha = alpha + tv_long_tap_title.alpha = alpha + radiobutton_long_tap.alpha = alpha + radiobutton_long_tap.isEnabled = enable + v_long_tap.isClickable = enable + if (enable) { + v_long_tap.setOnClickListener { + store.dispatch(DetailsAction.ManageSecurity.SelectOption(SecurityOption.LongTap)) + } + } + } + + private fun enablePasscode(enable: Boolean) { + val alpha = if (enable) 1f else 0.5f + tv_passcode_description.alpha = alpha + tv_passcode_title.alpha = alpha + radiobutton_passcode.alpha = alpha + radiobutton_passcode.isEnabled = enable + v_passcode.isClickable = enable + if (enable) { + v_passcode.setOnClickListener { + store.dispatch(DetailsAction.ManageSecurity.SelectOption(SecurityOption.PassCode)) + } + } + } + + private fun enableAccessCode(enable: Boolean) { + val alpha = if (enable) 1f else 0.5f + tv_access_code_description.alpha = alpha + tv_access_code_title.alpha = alpha + radiobutton_access_code.alpha = alpha + radiobutton_access_code.isEnabled = enable + v_access_code.isClickable = enable + if (enable) { + v_access_code.setOnClickListener { + store.dispatch(DetailsAction.ManageSecurity.SelectOption(SecurityOption.AccessCode)) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt b/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt new file mode 100644 index 0000000000..4d4ade93a9 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt @@ -0,0 +1,59 @@ +package com.tangem.tap.features.home + +import android.os.Bundle +import android.view.View +import androidx.fragment.app.Fragment +import androidx.transition.TransitionInflater +import com.tangem.tap.features.home.redux.HomeAction +import com.tangem.tap.features.home.redux.HomeState +import com.tangem.tap.store +import com.tangem.wallet.R +import kotlinx.android.synthetic.main.fragment_home.* +import org.rekotlin.StoreSubscriber + +class HomeFragment : Fragment(R.layout.fragment_home), StoreSubscriber { + + override fun onStart() { + super.onStart() + store.subscribe(this) { state -> + state.skipRepeats { oldState, newState -> + oldState.homeState == newState.homeState + }.select { it.homeState } + } + } + + override fun onStop() { + super.onStop() + store.unsubscribe(this) + } + + + override fun newState(state: HomeState) { + if (activity == null) return + if (state.firstLaunch) { + tv_home_description.text = getText(R.string.home_do_you_have_card) + btn_shop.text = getText(R.string.home_button_no_card) + btn_yes.text = getText(R.string.home_button_yes) + } else { + tv_home_description.text = getText(R.string.home_welcome_back) + btn_shop.text = getText(R.string.home_button_shop) + btn_yes.text = getText(R.string.home_button_scan) + } + } + + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + btn_yes?.setOnClickListener { store.dispatch(HomeAction.ReadCard) } + btn_shop?.setOnClickListener { store.dispatch(HomeAction.GoToShop(requireContext())) } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val inflater = TransitionInflater.from(requireContext()) + exitTransition = inflater.inflateTransition(R.transition.fade) + } + + +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt new file mode 100644 index 0000000000..5a9edf9fe7 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt @@ -0,0 +1,12 @@ +package com.tangem.tap.features.home.redux + +import android.content.Context +import org.rekotlin.Action + +sealed class HomeAction : Action { + object ReadCard : HomeAction() + data class GoToShop(val context: Context) : HomeAction() + object CheckIfFirstLaunch : HomeAction() { + data class Result(val firstLaunch: Boolean) : HomeAction() + } +} diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt new file mode 100644 index 0000000000..faca493fd0 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt @@ -0,0 +1,68 @@ +package com.tangem.tap.features.home.redux + +import android.content.Intent +import android.net.Uri +import androidx.core.content.ContextCompat.startActivity +import com.tangem.common.CompletionResult +import com.tangem.tap.common.redux.AppState +import com.tangem.tap.common.redux.global.GlobalAction +import com.tangem.tap.common.redux.navigation.AppScreen +import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.preferencesStorage +import com.tangem.tap.scope +import com.tangem.tap.store +import com.tangem.tap.tangemSdkManager +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.rekotlin.Middleware + +val homeMiddleware: Middleware = { dispatch, state -> + { next -> + { action -> + when (action) { + is HomeAction.CheckIfFirstLaunch -> { + store.dispatch( + HomeAction.CheckIfFirstLaunch.Result(preferencesStorage.isFirstLaunch()) + ) + } + is HomeAction.ReadCard -> { + scope.launch { + val result = tangemSdkManager.scanNote() + withContext(Dispatchers.Main) { + when (result) { + is CompletionResult.Success -> { + store.dispatch(GlobalAction.RestoreAppCurrency) + store.state.globalState.tapWalletManager.onCardScanned(result.data) + navigateToWallet() + } + } + } + } + } + is HomeAction.GoToShop -> { + val uri = Uri.parse(CARD_SHOP_URI) + val intent = Intent(Intent.ACTION_VIEW, uri) + startActivity(action.context, intent, null) + } + } + next(action) + } + } +} + +private fun navigateToWallet() { + if (store.state.navigationState.backStack.isNotEmpty()) { + val currentScreen = store.state.navigationState.backStack.last() + if (currentScreen == AppScreen.Home) { + store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet)) + } else if (currentScreen != AppScreen.Wallet) { + store.dispatch(NavigationAction.PopBackTo(AppScreen.Home)) + store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet)) + } + } else { + store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet)) + } +} + +private const val CARD_SHOP_URI = "https://shop.tangem.com/?afmc=1i&utm_campaign=1i&utm_source=leaddyno&utm_medium=affiliate" diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeReducer.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeReducer.kt new file mode 100644 index 0000000000..b88da93641 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeReducer.kt @@ -0,0 +1,24 @@ +package com.tangem.tap.features.home.redux + +import com.tangem.tap.common.redux.AppState +import org.rekotlin.Action + +class HomeReducer { + companion object { + fun reduce(action: Action, state: AppState): HomeState = internalReduce(action, state) + } +} + +private fun internalReduce(action: Action, state: AppState): HomeState { + + if (action !is HomeAction) return state.homeState + + var homeState = state.homeState + when (action) { + is HomeAction.CheckIfFirstLaunch.Result -> { + homeState = homeState.copy(firstLaunch = action.firstLaunch) + } + } + + return homeState +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeState.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeState.kt new file mode 100644 index 0000000000..b167e66785 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeState.kt @@ -0,0 +1,7 @@ +package com.tangem.tap.features.home.redux + +import org.rekotlin.StateType + +data class HomeState( + val firstLaunch: Boolean = true +) : StateType diff --git a/app/src/main/java/com/tangem/tap/features/send/BaseStoreFragment.kt b/app/src/main/java/com/tangem/tap/features/send/BaseStoreFragment.kt new file mode 100644 index 0000000000..fc2fef7c7b --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/send/BaseStoreFragment.kt @@ -0,0 +1,63 @@ +package com.tangem.tap.features.send + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.activity.OnBackPressedCallback +import androidx.fragment.app.Fragment +import com.google.android.material.snackbar.Snackbar +import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.store +import com.tangem.wallet.R +import kotlinx.android.synthetic.main.fragment_wallet.* +import org.rekotlin.StoreSubscriber + +/** +[REDACTED_AUTHOR] + */ +abstract class BaseStoreFragment(layoutId: Int) : Fragment(layoutId) { + + abstract fun subscribeToStore() + + protected lateinit var mainView: View + protected val storeSubscribersList = mutableListOf>() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + store.dispatch(NavigationAction.PopBackTo()) + } + }) + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + mainView = super.onCreateView(inflater, container, savedInstanceState)!! + return mainView + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + toolbar.setNavigationOnClickListener { store.dispatch(NavigationAction.PopBackTo()) } + } + + override fun onStart() { + super.onStart() + subscribeToStore() + } + + + override fun onStop() { + storeSubscribersList.forEach { store.unsubscribe(it) } + super.onStop() + } + + fun showRetrySnackbar(message: String, action: () -> Unit) { + val snackbar = Snackbar.make(mainView, message, Snackbar.LENGTH_INDEFINITE) + snackbar.setAction(getString(R.string.generic_retry)) { + snackbar.dismiss() + action() + }.show() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt b/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt new file mode 100644 index 0000000000..67e22baa41 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt @@ -0,0 +1,117 @@ +package com.tangem.tap.features.send.redux + +import com.tangem.Message +import com.tangem.blockchain.common.Amount +import com.tangem.tap.common.redux.ErrorAction +import com.tangem.tap.common.redux.ToastNotificationAction +import com.tangem.tap.domain.TapError +import com.tangem.tap.features.send.redux.states.FeeType +import com.tangem.tap.features.send.redux.states.MainCurrencyType +import com.tangem.tap.features.send.redux.states.SendButtonState +import com.tangem.wallet.R +import org.rekotlin.Action +import java.math.BigDecimal + +/** +[REDACTED_AUTHOR] + */ +interface SendScreenAction : Action +interface SendScreenActionUi : SendScreenAction + +object ReleaseSendState : Action + +data class PrepareSendScreen( + val coinAmount: Amount?, + val tokenAmount: Amount? = null +) : SendScreenAction + +// Address or PayId +sealed class AddressPayIdActionUi : SendScreenActionUi { + data class HandleUserInput(val data: String) : AddressPayIdActionUi() + data class PasteAddressPayId(val data: String) : AddressPayIdActionUi() + data class CheckClipboard(val data: String?) : AddressPayIdActionUi() + object CheckAddressPayId : AddressPayIdActionUi() + data class SetTruncateHandler(val handler: (String) -> String) : AddressPayIdActionUi() + data class TruncateOrRestore(val truncate: Boolean) : AddressPayIdActionUi() +} + +sealed class AddressPayIdVerifyAction : SendScreenAction { + enum class Error { + PAY_ID_UNSUPPORTED_BY_BLOCKCHAIN, + PAY_ID_NOT_REGISTERED, + PAY_ID_REQUEST_FAILED, + ADDRESS_INVALID_OR_UNSUPPORTED_BY_BLOCKCHAIN, + ADDRESS_SAME_AS_WALLET + } + + data class ChangePasteBtnEnableState(val isEnabled: Boolean) : AddressPayIdVerifyAction() + + sealed class PayIdVerification : AddressPayIdVerifyAction() { + data class SetPayIdError(val error: Error?) : PayIdVerification() + data class SetPayIdWalletAddress(val payId: String, val payIdWalletAddress: String, val isUserInput: Boolean) : PayIdVerification() + } + + sealed class AddressVerification : AddressPayIdVerifyAction() { + data class SetAddressError(val error: Error?) : AddressVerification() + data class SetWalletAddress(val address: String, val isUserInput: Boolean) : AddressVerification() + } +} + +// Amount to send +sealed class AmountActionUi : SendScreenActionUi { + data class HandleUserInput(val data: String) : AmountActionUi() + object CheckAmountToSend : AmountActionUi() + object SetMaxAmount : AmountActionUi() + data class SetMainCurrency(val mainCurrency: MainCurrencyType) : AmountActionUi() + object ToggleMainCurrency : AmountActionUi() +} + +sealed class AmountAction : SendScreenAction { + data class SetAmount(val amountCrypto: BigDecimal, val isUserInput: Boolean) : AmountAction() + data class SetAmountError(val error: TapError?) : AmountAction() + data class SetDecimalSeparator(val separator: String) : AmountAction() +} + +// Fee +sealed class FeeActionUi : SendScreenActionUi { + object ToggleControlsVisibility : FeeActionUi() + data class ChangeSelectedFee(val feeType: FeeType) : FeeActionUi() + class ChangeIncludeFee(val isIncluded: Boolean) : FeeActionUi() +} + +sealed class FeeAction : SendScreenAction { + enum class Error { + ADDRESS_OR_AMOUNT_IS_EMPTY, + REQUEST_FAILED + } + + object RequestFee : FeeAction() + sealed class FeeCalculation : FeeAction() { + data class SetFeeResult(val fee: List) : FeeCalculation() + data class SetFeeError(val error: Error) : FeeCalculation() + } + + data class ChangeLayoutVisibility( + val main: Boolean? = null, + val controls: Boolean? = null, + val chipGroup: Boolean? = null, + ) : FeeAction() +} + +sealed class ReceiptAction : SendScreenAction { + object RefreshReceipt : ReceiptAction() +} + +sealed class SendActionUi : SendScreenActionUi { + data class SendAmountToRecipient(val messageForSigner: Message) : SendScreenActionUi +} + +sealed class SendAction : SendScreenAction { + + data class ChangeSendButtonState(val state: SendButtonState) : SendAction() + object SendSuccess : SendAction(), ToastNotificationAction { + override val messageResource: Int = R.string.send_transaction_complete + } + + data class SendError(override val error: TapError) : SendAction(), ErrorAction +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AddressPayIdMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AddressPayIdMiddleware.kt new file mode 100644 index 0000000000..320eac8f91 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AddressPayIdMiddleware.kt @@ -0,0 +1,158 @@ +package com.tangem.tap.features.send.redux.middlewares + +import com.tangem.blockchain.common.Wallet +import com.tangem.commands.common.network.Result +import com.tangem.tap.common.redux.AppState +import com.tangem.tap.domain.PayIdManager +import com.tangem.tap.domain.isPayIdSupported +import com.tangem.tap.features.send.redux.AddressPayIdActionUi +import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction +import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.AddressVerification.SetAddressError +import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.AddressVerification.SetWalletAddress +import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.Error +import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.PayIdVerification.SetPayIdError +import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.PayIdVerification.SetPayIdWalletAddress +import com.tangem.tap.features.send.redux.FeeAction +import com.tangem.tap.scope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.rekotlin.Action +import org.rekotlin.DispatchFunction + +/** +[REDACTED_AUTHOR] + */ +internal class AddressPayIdMiddleware { + + fun handle(action: AddressPayIdActionUi, appState: AppState?, dispatch: (Action) -> Unit) { + when (action) { + is AddressPayIdActionUi.HandleUserInput -> handleUserInput(action.data, appState, dispatch) + is AddressPayIdActionUi.PasteAddressPayId -> pasteAddressPayId(action.data, dispatch) + is AddressPayIdActionUi.CheckClipboard -> verifyClipboard(action.data, appState, dispatch) + is AddressPayIdActionUi.CheckAddressPayId -> verifyAddressPayId(appState, dispatch) + else -> return + } + } + + private fun handleUserInput(input: String, appState: AppState?, dispatch: DispatchFunction) { + val sendState = appState?.sendState ?: return + if (input == sendState.addressPayIdState.viewFieldValue.value) return + + setAddressAndCheck(input, true, dispatch) + } + + private fun pasteAddressPayId(data: String, dispatch: (Action) -> Unit) { + setAddressAndCheck(data, false, dispatch) + } + + private fun setAddressAndCheck(data: String, isUserInput: Boolean, dispatch: (Action) -> Unit) { + val potentialPayId = data.toLowerCase() + if (PayIdManager.isPayId(potentialPayId)) { + dispatch(SetPayIdWalletAddress(potentialPayId, "", isUserInput)) + } else { + dispatch(SetWalletAddress(data, isUserInput)) + } + dispatch(AddressPayIdActionUi.CheckAddressPayId) + } + + private fun verifyAddressPayId(appState: AppState?, dispatch: (Action) -> Unit) { + val sendState = appState?.sendState ?: return + val wallet = sendState.walletManager?.wallet ?: return + val addressPayId = sendState.addressPayIdState.normalFieldValue ?: return + val isUserInput = sendState.addressPayIdState.viewFieldValue.isFromUserInput + + if (PayIdManager.isPayId(addressPayId)) { + verifyPayId(addressPayId, wallet, isUserInput, dispatch) + } else { + verifyAddress(addressPayId, wallet, isUserInput, dispatch) + } + } + + private fun verifyPayId(payId: String, wallet: Wallet, isUserInput: Boolean, dispatch: DispatchFunction) { + val blockchain = wallet.blockchain + if (!blockchain.isPayIdSupported()) { + dispatch(SetPayIdError(Error.PAY_ID_UNSUPPORTED_BY_BLOCKCHAIN)) + return + } + + scope.launch { + val result = PayIdManager().verifyPayId(payId, blockchain) + withContext(Dispatchers.Main) { + when (result) { + is Result.Success -> { + val address = result.data.getAddress() + if (address == null) { + dispatch(SetPayIdError(Error.PAY_ID_NOT_REGISTERED)) + return@withContext + } + + val failReason = isValidBlockchainAddressAndNotTheSameAsWallet(wallet, address) + if (failReason == null) { + dispatch(SetPayIdWalletAddress(payId, address, isUserInput)) + dispatch(FeeAction.RequestFee) + } else { + dispatch(SetAddressError(failReason)) + } + } + is Result.Failure -> { + dispatch(SetPayIdError(Error.PAY_ID_REQUEST_FAILED)) + } + } + } + } + } + + private fun verifyAddress(address: String, wallet: Wallet, isUserInput: Boolean, dispatch: (Action) -> Unit) { + val supposedAddress = extractAddressFromShareUri(address) + + val failReason = isValidBlockchainAddressAndNotTheSameAsWallet(wallet, supposedAddress) + if (failReason == null) { + dispatch(SetWalletAddress(supposedAddress, isUserInput)) + } else { + dispatch(SetAddressError(failReason)) + } + } + + private fun isValidBlockchainAddressAndNotTheSameAsWallet(wallet: Wallet, address: String): Error? { + return if (wallet.blockchain.validateAddress(address)) { + if (wallet.address != address) { + null + } else { + Error.ADDRESS_SAME_AS_WALLET + } + } else { + Error.ADDRESS_INVALID_OR_UNSUPPORTED_BY_BLOCKCHAIN + } + } + + //TODO: move to the blockchainSDK + private fun extractAddressFromShareUri(shareUri: String): String { + val sharePrefix = listOf("bitcoin:", "ethereum:", "xrpl:", "litecoin:", "bnb:") + val prefixes = sharePrefix.filter { shareUri.contains(it) } + return if (prefixes.isEmpty()) shareUri else shareUri.replace(prefixes[0], "") + } + + private fun verifyClipboard(input: String?, appState: AppState?, dispatch: DispatchFunction) { + val addressPayId = input ?: return + val wallet = appState?.sendState?.walletManager?.wallet ?: return + + + val internalDispatcher: (Action) -> Unit = { + when (it) { + is SetWalletAddress, is SetPayIdWalletAddress -> { + dispatch(AddressPayIdVerifyAction.ChangePasteBtnEnableState(true)) + } + is SetAddressError, is SetPayIdError -> { + dispatch(AddressPayIdVerifyAction.ChangePasteBtnEnableState(false)) + } + } + } + + if (PayIdManager.isPayId(addressPayId)) { + verifyPayId(addressPayId, wallet, false, internalDispatcher) + } else { + verifyAddress(addressPayId, wallet, false, internalDispatcher) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AmountMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AmountMiddleware.kt new file mode 100644 index 0000000000..389822d53a --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AmountMiddleware.kt @@ -0,0 +1,102 @@ +package com.tangem.tap.features.send.redux.middlewares + +import com.tangem.blockchain.common.Amount +import com.tangem.common.extensions.isZero +import com.tangem.tap.common.redux.AppState +import com.tangem.tap.features.send.redux.* +import com.tangem.tap.features.send.redux.states.MainCurrencyType +import org.rekotlin.Action +import java.math.BigDecimal + +/** +[REDACTED_AUTHOR] + */ +class AmountMiddleware { + + fun handle(action: AmountActionUi, appState: AppState?, dispatch: (Action) -> Unit) { + when (action) { + is AmountActionUi.HandleUserInput -> handleUserInput(action.data, appState, dispatch) + is AmountActionUi.CheckAmountToSend -> checkAmountToSend(appState, dispatch) + is AmountActionUi.SetMaxAmount -> setMaxAmount(appState, dispatch) + is AmountActionUi.ToggleMainCurrency -> toggleMainCurrency(appState, dispatch) + is AmountActionUi.SetMainCurrency -> return + } + } + + private fun handleUserInput(data: String, appState: AppState?, dispatch: (Action) -> Unit) { + val sendState = appState?.sendState ?: return + val amountState = sendState.amountState + + var input = amountState.toBigDecimalSeparator(data) + input = if (input == ".") "0.0" else input + val inputValue = when { + input.isEmpty() || input == "0" -> BigDecimal.ZERO + else -> BigDecimal(input) + } + if (inputValue.isZero() && amountState.amountToSendCrypto.isZero()) return + + val inputValueCrypto = if (amountState.mainCurrency.type == MainCurrencyType.CRYPTO) { + inputValue + } else { + sendState.convertFiatToExtractCrypto(inputValue) + } + + dispatch(AmountAction.SetAmount(inputValueCrypto, true)) + dispatch(FeeAction.RequestFee) + } + + private fun checkAmountToSend(appState: AppState?, dispatch: (Action) -> Unit) { + val sendState = appState?.sendState ?: return + val walletManager = sendState.walletManager ?: return + val typedAmount = sendState.amountState.amountToExtract ?: return + + val inputCrypto = sendState.amountState.amountToSendCrypto + if (sendState.amountState.viewAmountValue.value == "0" && inputCrypto.isZero()) { + dispatch(ReceiptAction.RefreshReceipt) + dispatch(SendAction.ChangeSendButtonState(sendState.getButtonState())) + return + } + + val amountToSend = Amount(typedAmount, sendState.getTotalAmountToSend(inputCrypto)) + val transactionErrors = walletManager.validateTransaction(amountToSend, sendState.feeState.currentFee) + if (transactionErrors.isEmpty()) { + dispatch(AmountAction.SetAmountError(null)) + } else { + val amountErrors = extractErrorsForAmountField(transactionErrors) + if (amountErrors.isNotEmpty()) { + dispatch(AmountAction.SetAmountError(createValidateTransactionError(amountErrors, walletManager))) + } + transactionErrors.removeAll(amountErrors) + if (transactionErrors.isNotEmpty()) { + dispatch(SendAction.SendError(createValidateTransactionError(transactionErrors, walletManager))) + } + } + dispatch(ReceiptAction.RefreshReceipt) + dispatch(SendAction.ChangeSendButtonState(sendState.getButtonState())) + } + + private fun setMaxAmount(appState: AppState?, dispatch: (Action) -> Unit) { + val amountState = appState?.sendState?.amountState ?: return + + dispatch(AmountAction.SetAmount(amountState.balanceCrypto, false)) + dispatch(FeeAction.RequestFee) + if (!amountState.isCoinAmount()) return + + dispatch(FeeAction.ChangeLayoutVisibility(main = true, controls = true, chipGroup = true)) + dispatch(FeeActionUi.ChangeIncludeFee(true)) + dispatch(AmountActionUi.CheckAmountToSend) + } + + private fun toggleMainCurrency(appState: AppState?, dispatch: (Action) -> Unit) { + val amountState = appState?.sendState?.amountState ?: return + if (!appState.sendState.coinIsConvertible()) return + + val type = if (amountState.mainCurrency.type == MainCurrencyType.FIAT) { + MainCurrencyType.CRYPTO + } else { + MainCurrencyType.FIAT + } + + dispatch(AmountActionUi.SetMainCurrency(type)) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/RequestFeeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/RequestFeeMiddleware.kt new file mode 100644 index 0000000000..2b662e5852 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/RequestFeeMiddleware.kt @@ -0,0 +1,101 @@ +package com.tangem.tap.features.send.redux.middlewares + +import com.tangem.blockchain.common.Amount +import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchain.common.TransactionSender +import com.tangem.blockchain.extensions.Result +import com.tangem.common.extensions.isZero +import com.tangem.tap.common.redux.AppState +import com.tangem.tap.features.send.redux.AmountActionUi +import com.tangem.tap.features.send.redux.FeeAction +import com.tangem.tap.features.send.redux.ReceiptAction +import com.tangem.tap.features.send.redux.SendAction +import com.tangem.tap.scope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.rekotlin.DispatchFunction +import java.math.BigDecimal + +/** +[REDACTED_AUTHOR] + */ +class RequestFeeMiddleware { + + fun handle(appState: AppState?, dispatch: DispatchFunction) { + val sendState = appState?.sendState ?: return + val walletManager = sendState.walletManager ?: return + + if (!sendState.addressPayIdIsReady()) { + dispatch(FeeAction.FeeCalculation.SetFeeError(FeeAction.Error.ADDRESS_OR_AMOUNT_IS_EMPTY)) + dispatch(FeeAction.ChangeLayoutVisibility(main = false, chipGroup = true)) + dispatch(ReceiptAction.RefreshReceipt) + dispatch(SendAction.ChangeSendButtonState(sendState.getButtonState())) + return + } + + val typedAmount = sendState.amountState.amountToExtract ?: return + val recipientAddress = sendState.addressPayIdState.recipientWalletAddress!! + val cryptoSendToRecipient = sendState.amountState.amountToSendCrypto + + val recipientAmount = Amount(typedAmount, cryptoSendToRecipient) + val txSender = walletManager as TransactionSender + scope.launch { + val feeResult = txSender.getFee(recipientAmount, recipientAddress) + withContext(Dispatchers.Main) { + when (feeResult) { + is Result.Success -> { + val result = feeResult.data +// val result = FeeMock.getFee(walletManager.wallet.blockchain) + dispatch(FeeAction.FeeCalculation.SetFeeResult(result)) + if (result.size == 1) { + val fee = result[0].value ?: BigDecimal.ZERO + if (fee.isZero()) { + dispatch(FeeAction.ChangeLayoutVisibility(main = false)) + } else { + dispatch(FeeAction.ChangeLayoutVisibility(main = true, chipGroup = false)) + } + } else { + dispatch(FeeAction.ChangeLayoutVisibility(main = true, chipGroup = true)) + } + } + is Result.Failure -> { + dispatch(FeeAction.FeeCalculation.SetFeeError(FeeAction.Error.REQUEST_FAILED)) + dispatch(FeeAction.ChangeLayoutVisibility(main = false, controls = false, chipGroup = false)) + } + } + dispatch(AmountActionUi.CheckAmountToSend) + } + } + + } +} + +class FeeMock { + companion object { + suspend fun getFee(blockchain: Blockchain): List { + return feeStandard(blockchain) +// return feeSingle(blockchain) +// return feeStellar(blockchain) +// return feeZero(blockchain) + } + + suspend fun feeStellar(blockchain: Blockchain): List = listOf(Amount(0.0001.toBigDecimal(), blockchain)) + + suspend fun feeZero(blockchain: Blockchain): List = listOf(Amount(BigDecimal.ZERO, blockchain)) + + suspend fun feeStandard(blockchain: Blockchain): List = listOf( + Amount(0.001500.toBigDecimal(), blockchain), + Amount(0.0030.toBigDecimal(), blockchain), + Amount(0.0045001.toBigDecimal(), blockchain) + ) + + suspend fun feeStandardBig(blockchain: Blockchain): List = listOf( + Amount(1.76.toBigDecimal(), blockchain), + Amount(2.30.toBigDecimal(), blockchain), + Amount(3.45.toBigDecimal(), blockchain) + ) + + suspend fun feeSingle(blockchain: Blockchain): List = listOf(Amount(0.0015.toBigDecimal(), blockchain)) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt new file mode 100644 index 0000000000..aad00d6912 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt @@ -0,0 +1,155 @@ +package com.tangem.tap.features.send.redux.middlewares + +import com.google.firebase.crashlytics.FirebaseCrashlytics +import com.tangem.blockchain.common.* +import com.tangem.blockchain.extensions.Result +import com.tangem.blockchain.extensions.Signer +import com.tangem.tap.common.extensions.stripZeroPlainString +import com.tangem.tap.common.redux.AppState +import com.tangem.tap.common.redux.global.GlobalAction +import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.domain.TapError +import com.tangem.tap.features.send.redux.AddressPayIdActionUi +import com.tangem.tap.features.send.redux.AmountActionUi +import com.tangem.tap.features.send.redux.FeeAction.RequestFee +import com.tangem.tap.features.send.redux.SendAction +import com.tangem.tap.features.send.redux.SendActionUi +import com.tangem.tap.features.send.redux.states.SendButtonState +import com.tangem.tap.features.wallet.redux.WalletAction +import com.tangem.tap.scope +import com.tangem.tap.tangemSdk +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.rekotlin.Action +import org.rekotlin.Middleware +import timber.log.Timber +import java.util.* + +/** +[REDACTED_AUTHOR] + */ +val sendMiddleware: Middleware = { dispatch, appState -> + { nextDispatch -> + { action -> + when (action) { + is AddressPayIdActionUi -> AddressPayIdMiddleware().handle(action, appState(), dispatch) + is AmountActionUi -> AmountMiddleware().handle(action, appState(), dispatch) + is RequestFee -> RequestFeeMiddleware().handle(appState(), dispatch) + is SendActionUi.SendAmountToRecipient -> + verifyAndSendTransaction(action, appState(), dispatch) + } + nextDispatch(action) + } + } +} + +private fun verifyAndSendTransaction( + action: SendActionUi.SendAmountToRecipient, appState: AppState?, dispatch: (Action) -> Unit +) { + val sendState = appState?.sendState ?: return + val walletManager = appState.globalState.scanNoteResponse?.walletManager ?: return + val recipientAddress = sendState.addressPayIdState.recipientWalletAddress ?: return + val typedAmount = sendState.amountState.amountToExtract ?: return + val feeAmount = sendState.feeState.currentFee ?: return + + val amountToSend = Amount(typedAmount, sendState.getTotalAmountToSend()) + + val verifyResult = walletManager.validateTransaction(amountToSend, feeAmount) + if (verifyResult.isNotEmpty()) { + dispatch(SendAction.SendError(createValidateTransactionError(verifyResult, walletManager))) + return + } + + dispatch(SendAction.ChangeSendButtonState(SendButtonState.PROGRESS)) + val txData = walletManager.createTransaction(amountToSend, feeAmount, recipientAddress) + scope.launch { + walletManager.update() + val signer = Signer(tangemSdk, action.messageForSigner) + val result = (walletManager as TransactionSender).send(txData, signer) + withContext(Dispatchers.Main) { + when (result) { + is Result.Success -> { + dispatch(SendAction.SendSuccess) + dispatch(GlobalAction.UpdateWalletSignedHashes(result.data.walletSignedHashes)) + dispatch(WalletAction.UpdateWallet) + dispatch(NavigationAction.PopBackTo()) + } + is Result.Failure -> { + when (result.error) { + is CreateAccountUnderfunded -> { + val error = result.error as CreateAccountUnderfunded + val reserve = error.minReserve.value?.stripZeroPlainString() ?: "0" + val symbol = error.minReserve.currencySymbol + dispatch(SendAction.SendError(TapError.CreateAccountUnderfunded(listOf(reserve, symbol)))) + } + is SendException -> { + result.error?.let { + FirebaseCrashlytics.getInstance().recordException(it) + } + } + is Throwable -> { + val message = (result.error as Throwable).message + when { + message == null -> { + dispatch(SendAction.SendError(TapError.UnknownError)) + } + message.contains("50002") -> { + // user was cancelled the operation by closing the Sdk bottom sheet + } + else -> { + Timber.e(result.error) + dispatch(SendAction.SendError(TapError.BlockchainInternalError)) + } + } + } + } + } + } + dispatch(SendAction.ChangeSendButtonState(SendButtonState.ENABLED)) + } + } + +} + +fun extractErrorsForAmountField(errors: EnumSet): EnumSet { + val showIntoAmountField = EnumSet.noneOf(TransactionError::class.java) + errors.forEach { + when (it) { + TransactionError.AmountExceedsBalance -> { + showIntoAmountField.remove(TransactionError.TotalExceedsBalance) + showIntoAmountField.add(it) + } + TransactionError.FeeExceedsBalance -> { + showIntoAmountField.remove(TransactionError.TotalExceedsBalance) + showIntoAmountField.add(it) + } + TransactionError.TotalExceedsBalance -> { + val notAcceptable = listOf(TransactionError.FeeExceedsBalance, TransactionError.FeeExceedsBalance) + if (!showIntoAmountField.containsAll(notAcceptable)) showIntoAmountField.add(it) + } + TransactionError.InvalidAmountValue -> showIntoAmountField.add(it) + TransactionError.InvalidFeeValue -> showIntoAmountField.add(it) + } + } + return showIntoAmountField +} + +fun createValidateTransactionError(errorList: EnumSet, walletManager: WalletManager): TapError.ValidateTransactionErrors { + val tapErrors = errorList.map { + when (it) { + TransactionError.AmountExceedsBalance -> TapError.AmountExceedsBalance + TransactionError.FeeExceedsBalance -> TapError.FeeExceedsBalance + TransactionError.TotalExceedsBalance -> TapError.TotalExceedsBalance + TransactionError.InvalidAmountValue -> TapError.InvalidAmountValue + TransactionError.InvalidFeeValue -> TapError.InvalidFeeValue + TransactionError.DustAmount -> { + TapError.DustAmount(listOf(walletManager.dustValue?.stripZeroPlainString() ?: "0")) + } + TransactionError.DustChange -> TapError.DustChange + else -> TapError.UnknownError + } + } + return TapError.ValidateTransactionErrors(tapErrors) { it.joinToString("\r\n") } +} + diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/AddressPayIdReducer.kt b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/AddressPayIdReducer.kt new file mode 100644 index 0000000000..e7c726241f --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/AddressPayIdReducer.kt @@ -0,0 +1,63 @@ +package com.tangem.tap.features.send.redux.reducers + +import com.tangem.tap.features.send.redux.AddressPayIdActionUi +import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction +import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.AddressVerification +import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.PayIdVerification +import com.tangem.tap.features.send.redux.SendScreenAction +import com.tangem.tap.features.send.redux.states.AddressPayIdState +import com.tangem.tap.features.send.redux.states.InputViewValue +import com.tangem.tap.features.send.redux.states.SendState + +/** +[REDACTED_AUTHOR] + */ +class AddressPayIdReducer : SendInternalReducer { + override fun handle(action: SendScreenAction, sendState: SendState): SendState = when (action) { + is AddressPayIdActionUi -> handleUiAction(action, sendState, sendState.addressPayIdState) + is AddressPayIdVerifyAction -> handleAction(action, sendState, sendState.addressPayIdState) + else -> sendState + } + + private fun handleUiAction(action: AddressPayIdActionUi, sendState: SendState, state: AddressPayIdState): SendState { + val result = when (action) { + is AddressPayIdActionUi.HandleUserInput -> state + is AddressPayIdActionUi.SetTruncateHandler -> state.copy(truncateHandler = action.handler) + is AddressPayIdActionUi.TruncateOrRestore -> { + val value = if (action.truncate) state.truncatedFieldValue ?: "" else state.normalFieldValue ?: "" + state.copy(viewFieldValue = state.viewFieldValue.copy(value = value)) + } + is AddressPayIdActionUi.PasteAddressPayId -> return sendState + is AddressPayIdActionUi.CheckClipboard -> return sendState + is AddressPayIdActionUi.CheckAddressPayId -> return sendState + } + return updateLastState(sendState.copy(addressPayIdState = result), result) + } + + private fun handleAction(action: AddressPayIdVerifyAction, sendState: SendState, state: AddressPayIdState): SendState { + val result = when (action) { + is PayIdVerification.SetPayIdWalletAddress -> { + state.copy( + viewFieldValue = InputViewValue(action.payId, action.isUserInput), + normalFieldValue = action.payId, + truncatedFieldValue = state.truncate(action.payId), + recipientWalletAddress = action.payIdWalletAddress, + error = null + ) + } + is AddressVerification.SetWalletAddress -> { + state.copy( + viewFieldValue = InputViewValue(action.address, action.isUserInput), + normalFieldValue = action.address, + truncatedFieldValue = state.truncate(action.address), + recipientWalletAddress = action.address, + error = null + ) + } + is AddressPayIdVerifyAction.ChangePasteBtnEnableState -> state.copy(pasteIsEnabled = action.isEnabled) + is AddressVerification.SetAddressError -> state.copy(error = action.error, recipientWalletAddress = null) + is PayIdVerification.SetPayIdError -> state.copy(error = action.error, recipientWalletAddress = null) + } + return updateLastState(sendState.copy(addressPayIdState = result), result) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/AmountReducer.kt b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/AmountReducer.kt new file mode 100644 index 0000000000..7429910244 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/AmountReducer.kt @@ -0,0 +1,86 @@ +package com.tangem.tap.features.send.redux.reducers + +import com.tangem.common.extensions.isZero +import com.tangem.tap.common.extensions.stripZeroPlainString +import com.tangem.tap.features.send.redux.AmountAction +import com.tangem.tap.features.send.redux.AmountActionUi +import com.tangem.tap.features.send.redux.AmountActionUi.SetMainCurrency +import com.tangem.tap.features.send.redux.SendScreenAction +import com.tangem.tap.features.send.redux.states.AmountState +import com.tangem.tap.features.send.redux.states.InputViewValue +import com.tangem.tap.features.send.redux.states.MainCurrencyType +import com.tangem.tap.features.send.redux.states.SendState +import java.math.BigDecimal + +/** +[REDACTED_AUTHOR] + */ +class AmountReducer : SendInternalReducer { + override fun handle(action: SendScreenAction, sendState: SendState): SendState = when (action) { + is AmountActionUi -> handleUiAction(action, sendState, sendState.amountState) + is AmountAction -> handleAction(action, sendState, sendState.amountState) + else -> sendState + } + + private fun handleUiAction(action: AmountActionUi, sendState: SendState, state: AmountState): SendState { + val result = when (action) { + is SetMainCurrency -> { + val currencyCanBeSwitched = sendState.mainCurrencyCanBeSwitched() + + when (val currency = if (currencyCanBeSwitched) action.mainCurrency else MainCurrencyType.CRYPTO) { + MainCurrencyType.FIAT -> { + val fiatToSend = if (state.amountToSendCrypto.isZero()) { + BigDecimal.ZERO + } else { + sendState.convertExtractCryptoToFiat(state.amountToSendCrypto) + } + val rescaledBalance = sendState.convertExtractCryptoToFiat(state.balanceCrypto, true) + val viewValue = state.restoreDecimalSeparator(fiatToSend.stripZeroPlainString()) + state.copy( + viewAmountValue = InputViewValue(viewValue), + viewBalanceValue = rescaledBalance.stripZeroPlainString(), + mainCurrency = state.createMainCurrency(currency, currencyCanBeSwitched), + maxLengthOfAmount = sendState.getDecimals(currency), + cursorAtTheSamePosition = false + ) + } + MainCurrencyType.CRYPTO -> { + val viewValue = state.restoreDecimalSeparator(state.amountToSendCrypto.stripZeroPlainString()) + state.copy( + viewAmountValue = InputViewValue(viewValue), + viewBalanceValue = state.balanceCrypto.stripZeroPlainString(), + mainCurrency = state.createMainCurrency(currency, currencyCanBeSwitched), + maxLengthOfAmount = sendState.getDecimals(currency), + cursorAtTheSamePosition = false + ) + } + } + } + else -> return sendState + } + + return updateLastState(sendState.copy(amountState = result), result) + } + + private fun handleAction(action: AmountAction, sendState: SendState, state: AmountState): SendState { + val result = when (action) { + is AmountAction.SetAmount -> { + val amount = if (state.mainCurrency.type == MainCurrencyType.CRYPTO) { + action.amountCrypto + } else { + sendState.convertExtractCryptoToFiat(action.amountCrypto, true) + } + val viewValue = state.restoreDecimalSeparator(amount.stripZeroPlainString()) + state.copy( + viewAmountValue = InputViewValue(viewValue, action.isUserInput), + amountToSendCrypto = action.amountCrypto, + cursorAtTheSamePosition = true, + error = null + ) + } + is AmountAction.SetAmountError -> state.copy(error = action.error) + is AmountAction.SetDecimalSeparator -> state.copy(decimalSeparator = action.separator) + } + return updateLastState(sendState.copy(amountState = result), result) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/FeeReducer.kt b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/FeeReducer.kt new file mode 100644 index 0000000000..f5da58ab54 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/FeeReducer.kt @@ -0,0 +1,107 @@ +package com.tangem.tap.features.send.redux.reducers + +import com.tangem.blockchain.common.Amount +import com.tangem.blockchain.extensions.isAboveZero +import com.tangem.tap.features.send.redux.FeeAction +import com.tangem.tap.features.send.redux.FeeActionUi +import com.tangem.tap.features.send.redux.SendScreenAction +import com.tangem.tap.features.send.redux.states.FeeState +import com.tangem.tap.features.send.redux.states.FeeType +import com.tangem.tap.features.send.redux.states.SendState + +/** +[REDACTED_AUTHOR] + */ +class FeeReducer : SendInternalReducer { + override fun handle(action: SendScreenAction, sendState: SendState): SendState = when (action) { + is FeeActionUi -> handleUiAction(action, sendState, sendState.feeState) + is FeeAction -> handleAction(action, sendState, sendState.feeState) + else -> sendState + } + + private fun handleUiAction(action: FeeActionUi, sendState: SendState, state: FeeState): SendState { + val result = when (action) { + is FeeActionUi.ToggleControlsVisibility -> state.copy(controlsLayoutIsVisible = !state.controlsLayoutIsVisible) + is FeeActionUi.ChangeSelectedFee -> { + val currentFee = createValueOfFeeAmount(action.feeType, state.feeList) + state.copy( + selectedFeeType = action.feeType, + currentFee = currentFee + ) + + } + is FeeActionUi.ChangeIncludeFee -> state.copy(feeIsIncluded = action.isIncluded) + } + return updateLastState(sendState.copy(feeState = result), result) + } + + private fun handleAction(action: FeeAction, sendState: SendState, state: FeeState): SendState { + val result = when (action) { + is FeeAction.RequestFee -> { + state.copy(error = null) + } + is FeeAction.ChangeLayoutVisibility -> { + fun getVisibility(current: Boolean, proposed: Boolean?): Boolean = proposed ?: current + state.copy( + mainLayoutIsVisible = getVisibility(state.mainLayoutIsVisible, action.main), + controlsLayoutIsVisible = getVisibility(state.controlsLayoutIsVisible, action.controls), + feeChipGroupIsVisible = getVisibility(state.mainLayoutIsVisible, action.chipGroup) + ) + } + is FeeAction.FeeCalculation.SetFeeResult -> { + val fees = action.fee + if (fees.size == 1) { + val feeType = FeeType.SINGLE + val currentFee = createValueOfFeeAmount(feeType, fees) + + state.copy( + selectedFeeType = feeType, + feeList = fees, + currentFee = currentFee, + error = null + ) + } else { + val feeType = getCurrentFeeType(state) + val currentFee = createValueOfFeeAmount(feeType, fees) + + state.copy( + selectedFeeType = feeType, + feeList = fees, + currentFee = currentFee, + error = null + ) + } + } + is FeeAction.FeeCalculation.SetFeeError -> { + state.copy( + feeList = null, + currentFee = null, + error = action.error + ) + } + } + + return updateLastState(sendState.copy(feeState = result), result) + } + + private fun createValueOfFeeAmount(feeType: FeeType, list: List?): Amount? { + if (list == null || list.isEmpty()) return null + + return if (list.size == 1) { + if (!list[0].isAboveZero()) return null + + list[0] + } else { + when (feeType) { + FeeType.SINGLE -> list[1] + FeeType.LOW -> list[0] + FeeType.NORMAL -> list[1] + FeeType.PRIORITY -> list[2] + } + } + } + + private fun getCurrentFeeType(state: FeeState): FeeType { + return if (state.selectedFeeType == FeeType.SINGLE) FeeType.NORMAL else state.selectedFeeType + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/ReceiptReducer.kt b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/ReceiptReducer.kt new file mode 100644 index 0000000000..c6f6511773 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/ReceiptReducer.kt @@ -0,0 +1,208 @@ +package com.tangem.tap.features.send.redux.reducers + +import com.tangem.blockchain.common.AmountType +import com.tangem.blockchain.common.Wallet +import com.tangem.tap.common.extensions.scaleToFiat +import com.tangem.tap.common.extensions.stripZeroPlainString +import com.tangem.tap.features.send.redux.ReceiptAction.RefreshReceipt +import com.tangem.tap.features.send.redux.SendScreenAction +import com.tangem.tap.features.send.redux.states.* +import com.tangem.tap.store +import java.math.BigDecimal + +/** +[REDACTED_AUTHOR] + */ +class ReceiptReducer : SendInternalReducer { + + companion object { + const val EMPTY = "-" + } + + private lateinit var sendState: SendState + private lateinit var amountState: AmountState + private lateinit var feeState: FeeState + + override fun handle(action: SendScreenAction, sendState: SendState): SendState { + this.sendState = sendState + this.amountState = sendState.amountState + this.feeState = sendState.feeState + + return when (action) { + is RefreshReceipt -> handleRefresh(action, sendState.receiptState) + else -> sendState + } + } + + + private fun handleRefresh(action: RefreshReceipt, state: ReceiptState): SendState { + val wallet = sendState.walletManager?.wallet ?: return sendState + + val layoutType = determineLayoutType(amountState.mainCurrency.type, amountState.typeOfAmount) + val symbols = determineSymbols(wallet) + val showBlank = !sendState.isReadyToSend() + val result = state.copy( + visibleTypeOfReceipt = layoutType, + mainCurrency = amountState.mainCurrency, + fiat = createFiatType(symbols, showBlank), + crypto = createCryptoType(symbols, showBlank), + tokenFiat = createTokenFiatType(symbols, showBlank), + tokenCrypto = createTokenCryptoType(symbols, showBlank) + ) + return updateLastState(sendState.copy(receiptState = result), result) + } + + private fun createFiatType(symbols: ReceiptSymbols, showBlank: Boolean): ReceiptFiat { + val feeCrypto = feeState.getCurrentFeeValue() + val feeFiat = convertToFiatPrecision(feeCrypto) + + if (showBlank) { + return ReceiptFiat("0", feeFiat, "0", "0", symbols) + } + + return if (feeState.feeIsIncluded) { + val amountFiat = convertToFiatPrecision(amountState.amountToSendCrypto.minus(feeCrypto)) + val totalFiat = convertToFiatPrecision(amountState.amountToSendCrypto) + ReceiptFiat( + amountFiat = amountFiat, + feeFiat = feeFiat, + totalFiat = totalFiat, + willSentCrypto = amountState.amountToSendCrypto.stripZeroPlainString(), + symbols = symbols + ) + } else { + val totalAmountCrypto = amountState.amountToSendCrypto.plus(feeCrypto) + val amountFiat = convertToFiatPrecision(amountState.amountToSendCrypto) + val totalFiat = convertToFiatPrecision(totalAmountCrypto) + ReceiptFiat( + amountFiat = amountFiat, + feeFiat = feeFiat, + totalFiat = totalFiat, + willSentCrypto = totalAmountCrypto.stripZeroPlainString(), + symbols = symbols + ) + } + } + + private fun createCryptoType(symbols: ReceiptSymbols, showBlank: Boolean): ReceiptCrypto { + val feeCrypto = feeState.getCurrentFeeValue() + val feeFiat = convertToFiatPrecision(feeCrypto) + if (showBlank) { + return ReceiptCrypto("0", feeCrypto.stripZeroPlainString(), "0", "0", "0", symbols) + } + + if (feeState.feeIsIncluded) { + return ReceiptCrypto( + amountCrypto = amountState.amountToSendCrypto.minus(feeCrypto).stripZeroPlainString(), + feeCrypto = feeCrypto.stripZeroPlainString(), + totalCrypto = amountState.amountToSendCrypto.stripZeroPlainString(), + feeFiat = feeFiat, + willSentFiat = convertToFiatPrecision(amountState.amountToSendCrypto), + symbols = symbols + ) + } else { + val totalCrypto = amountState.amountToSendCrypto.plus(feeCrypto) + return ReceiptCrypto( + amountCrypto = amountState.amountToSendCrypto.stripZeroPlainString(), + feeCrypto = feeCrypto.stripZeroPlainString(), + totalCrypto = totalCrypto.stripZeroPlainString(), + feeFiat = feeFiat, + willSentFiat = convertToFiatPrecision(totalCrypto), + symbols = symbols + ) + } + } + + private fun createTokenFiatType(symbols: ReceiptSymbols, showBlank: Boolean): ReceiptTokenFiat { + val feeCoin = feeState.getCurrentFeeValue() + if (showBlank) { + val feeFiat = convertToFiatPrecision(feeCoin) + return ReceiptTokenFiat("0", feeFiat, "0", "0", "0", symbols) + } + + val tokensToSend = amountState.amountToSendCrypto + + return if (sendState.coinIsConvertible() && sendState.tokenIsConvertible()) { + val feeFiat = sendState.coinConverter!!.toFiatUnscaled(feeCoin) + val amountFiat = sendState.tokenConverter!!.toFiatUnscaled(tokensToSend) + val totalFiat = amountFiat.plus(feeFiat) + ReceiptTokenFiat( + amountFiat = amountFiat.scaleToFiat(true).stripZeroPlainString(), + feeFiat = feeFiat.scaleToFiat(true).stripZeroPlainString(), + totalFiat = totalFiat.scaleToFiat(true).stripZeroPlainString(), + willSentToken = tokensToSend.stripZeroPlainString(), + willSentFeeCoin = feeCoin.stripZeroPlainString(), + symbols = symbols + ) + } else { + ReceiptTokenFiat( + amountFiat = EMPTY, + feeFiat = EMPTY, + totalFiat = EMPTY, + willSentToken = tokensToSend.stripZeroPlainString(), + willSentFeeCoin = feeCoin.stripZeroPlainString(), + symbols = symbols + ) + } + } + + private fun createTokenCryptoType(symbols: ReceiptSymbols, showBlank: Boolean): ReceiptTokenCrypto { + val feeCoin = feeState.getCurrentFeeValue() + if (showBlank) { + return ReceiptTokenCrypto("0", feeCoin.stripZeroPlainString(), "0", symbols) + } + val tokensToSend = amountState.amountToSendCrypto + + return if (sendState.coinIsConvertible() && sendState.tokenIsConvertible()) { + val tokenFiat = sendState.tokenConverter!!.toFiatUnscaled(tokensToSend) + val feeFiat = sendState.coinConverter!!.toFiatUnscaled(feeCoin) + val totalFiat = tokenFiat.plus(feeFiat) + + ReceiptTokenCrypto( + amountToken = tokensToSend.stripZeroPlainString(), + feeCoin = feeCoin.stripZeroPlainString(), + totalFiat = totalFiat.scaleToFiat(true).stripZeroPlainString(), + symbols = symbols + ) + } else { + ReceiptTokenCrypto( + amountToken = tokensToSend.stripZeroPlainString(), + feeCoin = feeCoin.stripZeroPlainString(), + totalFiat = EMPTY, + symbols = symbols + ) + } + + } + + private fun determineSymbols(wallet: Wallet): ReceiptSymbols { + return ReceiptSymbols( + fiat = store.state.globalState.appCurrency, + crypto = wallet.blockchain.currency, + token = wallet.amounts[AmountType.Token]?.currencySymbol + ) + } + + private fun determineLayoutType(mainCurrencyType: MainCurrencyType, amountType: AmountType): ReceiptLayoutType { + return when (mainCurrencyType) { + MainCurrencyType.FIAT -> when (amountType) { + AmountType.Coin -> ReceiptLayoutType.FIAT + AmountType.Token -> ReceiptLayoutType.TOKEN_FIAT + AmountType.Reserve -> ReceiptLayoutType.UNKNOWN + } + MainCurrencyType.CRYPTO -> when (amountType) { + AmountType.Coin -> ReceiptLayoutType.CRYPTO + AmountType.Token -> ReceiptLayoutType.TOKEN_CRYPTO + AmountType.Reserve -> ReceiptLayoutType.UNKNOWN + } + } + } + + private fun convertToFiatPrecision(value: BigDecimal, isToken: Boolean = false): String { + return when { + !isToken && sendState.coinIsConvertible() -> sendState.coinConverter!!.toFiatWithPrecision(value).stripZeroPlainString() + isToken && sendState.tokenIsConvertible() -> sendState.tokenConverter!!.toFiatWithPrecision(value).stripZeroPlainString() + else -> EMPTY + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt new file mode 100644 index 0000000000..d09cb9b695 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt @@ -0,0 +1,88 @@ +package com.tangem.tap.features.send.redux.reducers + +import com.tangem.blockchain.common.AmountType +import com.tangem.tap.common.CurrencyConverter +import com.tangem.tap.features.send.redux.* +import com.tangem.tap.features.send.redux.states.IdStateHolder +import com.tangem.tap.features.send.redux.states.SendState +import com.tangem.tap.store +import org.rekotlin.Action +import java.math.BigDecimal + +/** +[REDACTED_AUTHOR] + */ +interface SendInternalReducer { + fun handle(action: SendScreenAction, sendState: SendState): SendState +} + +class SendScreenReducer { + companion object { + fun reduce(incomingAction: Action, sendState: SendState): SendState { + if (incomingAction is ReleaseSendState) return SendState() + val action = incomingAction as? SendScreenAction ?: return sendState + + val reducer: SendInternalReducer = when (action) { + is PrepareSendScreen -> PrepareSendScreenStatesReducer() + is AddressPayIdActionUi, is AddressPayIdVerifyAction -> AddressPayIdReducer() + is AmountActionUi, is AmountAction -> AmountReducer() + is FeeActionUi, is FeeAction -> FeeReducer() + is ReceiptAction -> ReceiptReducer() + is SendAction -> SendReducer() + else -> EmptyReducer() + } + + return reducer.handle(action, sendState) + } + } +} + +private class SendReducer : SendInternalReducer { + override fun handle(action: SendScreenAction, sendState: SendState): SendState { + val result = when (action) { + is SendAction.ChangeSendButtonState -> sendState.copy(sendButtonState = action.state) + else -> return sendState + } + + return updateLastState(result, result) + } +} + +private class EmptyReducer : SendInternalReducer { + override fun handle(action: SendScreenAction, sendState: SendState): SendState = sendState +} + +private class PrepareSendScreenStatesReducer : SendInternalReducer { + override fun handle(action: SendScreenAction, sendState: SendState): SendState { + val prepareAction = action as PrepareSendScreen + val walletManager = store.state.globalState.scanNoteResponse!!.walletManager!! + val amountToExtract = prepareAction.tokenAmount ?: prepareAction.coinAmount!! + val decimals = amountToExtract.decimals + + val coinConverter = createCurrencyConverter(walletManager.wallet.blockchain.currency, decimals) + val tokenConverter = createCurrencyConverter(prepareAction.tokenAmount?.currencySymbol ?: "", decimals) + return sendState.copy( + walletManager = walletManager, + coinConverter = coinConverter, + tokenConverter = tokenConverter, + amountState = sendState.amountState.copy( + amountToExtract = amountToExtract, + typeOfAmount = amountToExtract.type, + balanceCrypto = amountToExtract.value ?: BigDecimal.ZERO + ), + feeState = sendState.feeState.copy( + includeFeeSwitcherIsEnabled = amountToExtract.type == AmountType.Coin + ) + ) + } + + private fun createCurrencyConverter(currency: String, decimals: Int): CurrencyConverter? { + val rate = store.state.globalState.conversionRates.getRate(currency) + return if (rate == null) null else CurrencyConverter(rate, decimals) + } +} + +internal fun updateLastState(sendState: SendState, lastChangedState: IdStateHolder): SendState { + sendState.lastChangedStates.add(lastChangedState.stateId) + return sendState +} diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/states/AddressPayIdState.kt b/app/src/main/java/com/tangem/tap/features/send/redux/states/AddressPayIdState.kt new file mode 100644 index 0000000000..dfcc6ca4c5 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/send/redux/states/AddressPayIdState.kt @@ -0,0 +1,22 @@ +package com.tangem.tap.features.send.redux.states + +import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction + +data class AddressPayIdState( + val viewFieldValue: InputViewValue = InputViewValue(""), + val normalFieldValue: String? = null, + val truncatedFieldValue: String? = null, + val recipientWalletAddress: String? = null, + val error: AddressPayIdVerifyAction.Error? = null, + val truncateHandler: ((String) -> String)? = null, + val pasteIsEnabled: Boolean = false +) : SendScreenState { + + override val stateId: StateId = StateId.ADDRESS_PAY_ID + + fun truncate(value: String): String = truncateHandler?.invoke(value) ?: value + + fun isReady(): Boolean = error == null && recipientWalletAddress?.isNotEmpty() ?: false + + fun isPayIdState(): Boolean = recipientWalletAddress != null && recipientWalletAddress != normalFieldValue +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/states/FeeState.kt b/app/src/main/java/com/tangem/tap/features/send/redux/states/FeeState.kt new file mode 100644 index 0000000000..532031bbb4 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/send/redux/states/FeeState.kt @@ -0,0 +1,31 @@ +package com.tangem.tap.features.send.redux.states + +import com.tangem.blockchain.common.Amount +import com.tangem.tap.features.send.redux.FeeAction +import java.math.BigDecimal + +/** +[REDACTED_AUTHOR] + */ +enum class FeeType { + SINGLE, LOW, NORMAL, PRIORITY +} + +data class FeeState( + val selectedFeeType: FeeType = FeeType.NORMAL, + val feeList: List? = null, + val currentFee: Amount? = null, + val feeIsIncluded: Boolean = false, + val mainLayoutIsVisible: Boolean = false, + val controlsLayoutIsVisible: Boolean = false, + val feeChipGroupIsVisible: Boolean = true, + val includeFeeSwitcherIsEnabled: Boolean = true, + val error: FeeAction.Error? = null, +) : SendScreenState { + + override val stateId: StateId = StateId.FEE + + fun isReady(): Boolean = error == null && currentFee != null + + fun getCurrentFeeValue(): BigDecimal = currentFee?.value ?: BigDecimal.ZERO +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/states/ReceiptState.kt b/app/src/main/java/com/tangem/tap/features/send/redux/states/ReceiptState.kt new file mode 100644 index 0000000000..90e22a0e9e --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/send/redux/states/ReceiptState.kt @@ -0,0 +1,56 @@ +package com.tangem.tap.features.send.redux.states + +// Shows only one type of the layout +enum class ReceiptLayoutType { + UNKNOWN, FIAT, CRYPTO, TOKEN_FIAT, TOKEN_CRYPTO +} + +data class ReceiptState( + val visibleTypeOfReceipt: ReceiptLayoutType? = null, + val fiat: ReceiptFiat? = null, + val crypto: ReceiptCrypto? = null, + val tokenFiat: ReceiptTokenFiat? = null, + val tokenCrypto: ReceiptTokenCrypto? = null, + val mainCurrency: MainCurrency? = null +) : SendScreenState { + override val stateId: StateId = StateId.RECEIPT +} + +data class ReceiptSymbols( + val fiat: String, + val crypto: String, + val token: String? = null +) + +data class ReceiptFiat( + val amountFiat: String, + val feeFiat: String, + val totalFiat: String, + val willSentCrypto: String, + val symbols: ReceiptSymbols +) + +data class ReceiptCrypto( + val amountCrypto: String, + val feeCrypto: String, + val totalCrypto: String, + val feeFiat: String, + val willSentFiat: String, + val symbols: ReceiptSymbols +) + +data class ReceiptTokenCrypto( + val amountToken: String, + val feeCoin: String, + val totalFiat: String, + val symbols: ReceiptSymbols +) + +data class ReceiptTokenFiat( + val amountFiat: String, + val feeFiat: String, + val totalFiat: String, + val willSentToken: String, + val willSentFeeCoin: String, + val symbols: ReceiptSymbols +) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt b/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt new file mode 100644 index 0000000000..97bf169413 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt @@ -0,0 +1,165 @@ +package com.tangem.tap.features.send.redux.states + +import com.tangem.blockchain.common.Amount +import com.tangem.blockchain.common.AmountType +import com.tangem.blockchain.common.WalletManager +import com.tangem.common.extensions.isZero +import com.tangem.tap.common.CurrencyConverter +import com.tangem.tap.common.entities.TapCurrency +import com.tangem.tap.domain.TapError +import com.tangem.tap.store +import org.rekotlin.StateType +import java.math.BigDecimal + +/** +[REDACTED_AUTHOR] + */ + +interface IdStateHolder { + val stateId: StateId +} + +enum class StateId { + SEND_SCREEN, ADDRESS_PAY_ID, AMOUNT, FEE, RECEIPT +} + +interface SendScreenState : StateType, IdStateHolder + +data class SendState( + val walletManager: WalletManager? = null, + val coinConverter: CurrencyConverter? = null, + val tokenConverter: CurrencyConverter? = null, + val lastChangedStates: LinkedHashSet = linkedSetOf(), + val addressPayIdState: AddressPayIdState = AddressPayIdState(), + val amountState: AmountState = AmountState(), + val feeState: FeeState = FeeState(), + val receiptState: ReceiptState = ReceiptState(), + val sendButtonState: SendButtonState = SendButtonState.DISABLED +) : SendScreenState { + + override val stateId: StateId = StateId.SEND_SCREEN + + fun isReadyToSend(): Boolean { + val sendState = store.state.sendState + return addressPayIdIsReady() && sendState.amountState.isReady() && sendState.feeState.isReady() + } + + fun addressPayIdIsReady(): Boolean = store.state.sendState.addressPayIdState.isReady() + + fun getDecimals(type: MainCurrencyType): Int = when (type) { + MainCurrencyType.FIAT -> 2 + MainCurrencyType.CRYPTO -> amountState.amountToExtract?.decimals ?: 0 + } + + fun convertFiatToCoin(value: BigDecimal): BigDecimal { + return if (!this.coinIsConvertible()) value else coinConverter!!.toCrypto(value) + } + + fun convertFiatToToken(value: BigDecimal): BigDecimal { + return if (!this.tokenIsConvertible()) value else tokenConverter!!.toCrypto(value) + } + + fun convertCoinToFiat(value: BigDecimal, scaleWithPrecision: Boolean = false): BigDecimal { + if (!this.coinIsConvertible()) return value + + val converter = coinConverter!! + return if (!scaleWithPrecision) converter.toFiat(value) else converter.toFiatWithPrecision(value) + } + + fun convertTokenToFiat(value: BigDecimal, scaleWithPrecision: Boolean = false): BigDecimal { + if (!this.tokenIsConvertible()) return value + + val converter = tokenConverter!! + return if (!scaleWithPrecision) converter.toFiat(value) else converter.toFiatWithPrecision(value) + } + + fun convertFiatToExtractCrypto(fiatValue: BigDecimal): BigDecimal = when (amountState.typeOfAmount) { + AmountType.Coin -> convertFiatToCoin(fiatValue) + AmountType.Token -> convertFiatToToken(fiatValue) + AmountType.Reserve -> fiatValue + } + + fun convertExtractCryptoToFiat(cryptoValue: BigDecimal, scaleWithPrecision: Boolean = false): BigDecimal { + return when (amountState.typeOfAmount) { + AmountType.Coin -> convertCoinToFiat(cryptoValue, scaleWithPrecision) + AmountType.Token -> convertTokenToFiat(cryptoValue, scaleWithPrecision) + AmountType.Reserve -> cryptoValue + } + } + + fun getButtonState(): SendButtonState = if (isReadyToSend()) SendButtonState.ENABLED else SendButtonState.DISABLED + + fun getTotalAmountToSend(value: BigDecimal = amountState.amountToSendCrypto): BigDecimal { + val needToExtractFee = amountState.isCoinAmount() && feeState.feeIsIncluded + return if (needToExtractFee) value.minus(feeState.getCurrentFeeValue()) else value + } + + fun coinIsConvertible(): Boolean = coinConverter != null + fun tokenIsConvertible(): Boolean = tokenConverter != null + + fun mainCurrencyCanBeSwitched(): Boolean { + return when (amountState.typeOfAmount) { + AmountType.Coin -> coinIsConvertible() + AmountType.Token -> tokenIsConvertible() + AmountType.Reserve -> false + } + } +} + +enum class SendButtonState { + ENABLED, DISABLED, PROGRESS +} + +data class AmountState( + val amountToExtract: Amount? = null, + val typeOfAmount: AmountType = AmountType.Coin, + val viewAmountValue: InputViewValue = InputViewValue(BigDecimal.ZERO.toPlainString()), + val viewBalanceValue: String = BigDecimal.ZERO.toPlainString(), + val mainCurrency: MainCurrency = MainCurrency(MainCurrencyType.FIAT, TapCurrency.DEFAULT_FIAT_CURRENCY), + val amountToSendCrypto: BigDecimal = BigDecimal.ZERO, + val balanceCrypto: BigDecimal = BigDecimal.ZERO, + val cursorAtTheSamePosition: Boolean = true, + val maxLengthOfAmount: Int = 2, + val decimalSeparator: String = ".", + val error: TapError? = null +) : SendScreenState { + + override val stateId: StateId = StateId.AMOUNT + + fun isReady(): Boolean = error == null && !amountToSendCrypto.isZero() + + fun isCoinAmount(): Boolean = typeOfAmount == AmountType.Coin + + fun createMainCurrency(type: MainCurrencyType, canSwitched: Boolean): MainCurrency { + return if (!canSwitched) MainCurrency(type, amountToExtract?.currencySymbol ?: "NONE", false) + else when (type) { + MainCurrencyType.FIAT -> MainCurrency(type, store.state.globalState.appCurrency) + MainCurrencyType.CRYPTO -> MainCurrency(type, amountToExtract?.currencySymbol ?: "NONE") + } + } + + fun toBigDecimalSeparator(value: String): String { + return value.replace(",", ".") + } + + fun restoreDecimalSeparator(value: String): String { + if (value.contains(decimalSeparator)) return value + + return if (decimalSeparator == ".") { + value.replace(",", decimalSeparator) + } else { + value.replace(".", decimalSeparator) + } + } +} + +data class InputViewValue(val value: String, val isFromUserInput: Boolean = false) +enum class MainCurrencyType { + FIAT, CRYPTO +} + +data class MainCurrency( + val type: MainCurrencyType, + val currencySymbol: String, + val isEnabled: Boolean = true +) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt b/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt new file mode 100644 index 0000000000..2e2e4b9ed2 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt @@ -0,0 +1,267 @@ +package com.tangem.tap.features.send.ui + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.text.method.DigitsKeyListener +import android.view.View +import android.view.inputmethod.EditorInfo +import android.widget.EditText +import androidx.core.view.postDelayed +import androidx.core.widget.addTextChangedListener +import com.tangem.Message +import com.tangem.merchant.common.toggleWidget.ToggleWidget +import com.tangem.tangem_sdk_new.extensions.hideSoftKeyboard +import com.tangem.tap.common.KeyboardObserver +import com.tangem.tap.common.entities.TapCurrency +import com.tangem.tap.common.extensions.getDrawableCompat +import com.tangem.tap.common.extensions.getFromClipboard +import com.tangem.tap.common.extensions.setOnImeActionListener +import com.tangem.tap.common.qrCodeScan.ScanQrCodeActivity +import com.tangem.tap.common.snackBar.MaxAmountSnackbar +import com.tangem.tap.common.text.truncateMiddleWith +import com.tangem.tap.common.toggleWidget.* +import com.tangem.tap.features.send.BaseStoreFragment +import com.tangem.tap.features.send.redux.* +import com.tangem.tap.features.send.redux.AddressPayIdActionUi.* +import com.tangem.tap.features.send.redux.AmountActionUi.* +import com.tangem.tap.features.send.redux.FeeActionUi.* +import com.tangem.tap.features.send.redux.states.FeeType +import com.tangem.tap.features.send.redux.states.MainCurrencyType +import com.tangem.tap.features.send.ui.stateSubscribers.SendStateSubscriber +import com.tangem.tap.mainScope +import com.tangem.tap.store +import com.tangem.wallet.R +import kotlinx.android.synthetic.main.fragment_send.* +import kotlinx.android.synthetic.main.layout_send_address_payid.* +import kotlinx.android.synthetic.main.layout_send_amount.* +import kotlinx.android.synthetic.main.layout_send_fee.* +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.* +import java.text.DecimalFormatSymbols + +/** +[REDACTED_AUTHOR] + */ +class SendFragment : BaseStoreFragment(R.layout.fragment_send) { + + lateinit var sendBtn: ToggleWidget + + private fun initSendButtonStates() { + sendBtn = ToggleWidget(flSendButtonContainer, btnSend, progress, ProgressState.None()) + sendBtn.setupSendButtonStateModifiers(requireContext()) + sendBtn.setState(ProgressState.None()) + } + + private val sendSubscriber = SendStateSubscriber(this) + private lateinit var keyboardObserver: KeyboardObserver + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + initSendButtonStates() + setupAddressOrPayIdLayout() + setupAmountLayout() + setupFeeLayout() + + btnSend.setOnClickListener { + store.dispatch(SendActionUi.SendAmountToRecipient( + Message(getString(R.string.tap_to_sign)) + )) + } + } + + private fun setupAddressOrPayIdLayout() { + store.dispatch(SetTruncateHandler { etAddressOrPayId.truncateMiddleWith(it, "...") }) + store.dispatch(CheckClipboard(requireContext().getFromClipboard()?.toString())) + + etAddressOrPayId.setOnFocusChangeListener { v, hasFocus -> + store.dispatch(TruncateOrRestore(!hasFocus)) + } + etAddressOrPayId.inputtedTextAsFlow() + .debounce(400) + .filter { store.state.sendState.addressPayIdState.viewFieldValue.value != it } + .onEach { + store.dispatch(AddressPayIdActionUi.HandleUserInput(it)) + store.dispatch(FeeAction.RequestFee) + } + .launchIn(mainScope) + + imvPaste.setOnClickListener { + store.dispatch(PasteAddressPayId(requireContext().getFromClipboard()?.toString() ?: "")) + store.dispatch(TruncateOrRestore(!etAddressOrPayId.isFocused)) + store.dispatch(FeeAction.RequestFee) + } + imvQrCode.setOnClickListener { + startActivityForResult( + Intent(requireContext(), ScanQrCodeActivity::class.java), + ScanQrCodeActivity.SCAN_QR_REQUEST_CODE + ) + } + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + if (requestCode != ScanQrCodeActivity.SCAN_QR_REQUEST_CODE) return + + val scannedCode = data?.getStringExtra(ScanQrCodeActivity.SCAN_RESULT) ?: "" + if (scannedCode.isEmpty()) return + + store.dispatch(PasteAddressPayId(scannedCode)) + store.dispatch(TruncateOrRestore(!etAddressOrPayId.isFocused)) + store.dispatch(FeeAction.RequestFee) + } + + private fun setupAmountLayout() { + store.dispatch(SetMainCurrency(restoreMainCurrency())) + store.dispatch(ReceiptAction.RefreshReceipt) + store.dispatch(SendAction.ChangeSendButtonState(store.state.sendState.getButtonState())) + + val decimalSeparator = DecimalFormatSymbols.getInstance().decimalSeparator.toString() + store.dispatch(AmountAction.SetDecimalSeparator(decimalSeparator)) + + tvAmountCurrency.setOnClickListener { + store.dispatch(ToggleMainCurrency) + store.dispatch(ReceiptAction.RefreshReceipt) + store.dispatch(SendAction.ChangeSendButtonState(store.state.sendState.getButtonState())) + } + + val maxAmountSnackbar = MaxAmountSnackbar.make(etAmountToSend) { + etAmountToSend.clearFocus() + etAmountToSend.postDelayed(200) { etAmountToSend.hideSoftKeyboard() } + store.dispatch(SetMaxAmount) + store.dispatch(CheckAmountToSend) + } + var snackbarControlledByChangingFocus = false + keyboardObserver = KeyboardObserver(requireActivity()) + keyboardObserver.registerListener { isShow -> + if (snackbarControlledByChangingFocus) return@registerListener + + if (isShow) { + if (etAmountToSend.isFocused && !maxAmountSnackbar.isShown) maxAmountSnackbar.show() + } else { + if (maxAmountSnackbar.isShown) maxAmountSnackbar.dismiss() + } + } + + etAmountToSend.keyListener = DigitsKeyListener.getInstance("0123456789$decimalSeparator") + etAmountToSend.setOnFocusChangeListener { v, hasFocus -> + snackbarControlledByChangingFocus = true + if (hasFocus) { + etAmountToSend.postDelayed(200) { + maxAmountSnackbar.show() + snackbarControlledByChangingFocus = false + } + } else { + etAmountToSend.postDelayed(350) { + maxAmountSnackbar.dismiss() + snackbarControlledByChangingFocus = false + } + } + } + + val prevFocusChangeListener = etAmountToSend.onFocusChangeListener + etAmountToSend.setOnFocusChangeListener { v, hasFocus -> + prevFocusChangeListener.onFocusChange(v, hasFocus) + if (hasFocus && etAmountToSend.text?.toString() == "0") etAmountToSend.setText("") + if (!hasFocus && etAmountToSend.text?.toString() == "") etAmountToSend.setText("0") + } + + etAmountToSend.inputtedTextAsFlow() + .debounce(400) + .filter { store.state.sendState.amountState.viewAmountValue.value != it && it.isNotEmpty() } + .onEach { store.dispatch(AmountActionUi.HandleUserInput(it)) } + .launchIn(mainScope) + + etAmountToSend.setOnImeActionListener(EditorInfo.IME_ACTION_DONE) { + it.hideSoftKeyboard() + it.clearFocus() + } + } + + private fun setupFeeLayout() { + flExpandCollapse.setOnClickListener { + store.dispatch(ToggleControlsVisibility) + } + chipGroup.setOnCheckedChangeListener { group, checkedId -> + if (checkedId == -1) return@setOnCheckedChangeListener + + store.dispatch(ChangeSelectedFee(FeeUiHelper.idToFee(checkedId))) + store.dispatch(CheckAmountToSend) + } + swIncludeFee.setOnCheckedChangeListener { btn, isChecked -> + store.dispatch(ChangeIncludeFee(isChecked)) + store.dispatch(CheckAmountToSend) + } + } + + override fun subscribeToStore() { + store.subscribe(sendSubscriber) { appState -> + appState.skipRepeats { oldState, newState -> + oldState.sendState == newState.sendState + }.select { it.sendState } + } + storeSubscribersList.add(sendSubscriber) + } + + private fun restoreMainCurrency(): MainCurrencyType { + val sp = requireContext().getSharedPreferences("SendScreen", Context.MODE_PRIVATE) + val mainCurrency = sp.getString("mainCurrency", TapCurrency.DEFAULT_FIAT_CURRENCY) + val foundType = MainCurrencyType.values() + .firstOrNull { it.name.toLowerCase() == mainCurrency!!.toLowerCase() } + ?: MainCurrencyType.FIAT + return foundType + } + + fun saveMainCurrency(type: MainCurrencyType) { + val sp = requireContext().getSharedPreferences("SendScreen", Context.MODE_PRIVATE) + sp.edit().putString("mainCurrency", type.name).apply() + } + + override fun onDestroy() { + keyboardObserver.unregisterListener() + store.dispatch(ReleaseSendState) + super.onDestroy() + } +} + +@ExperimentalCoroutinesApi +fun EditText.inputtedTextAsFlow(): Flow = callbackFlow { + val watcher = addTextChangedListener { editable -> offer(editable?.toString() ?: "") } + awaitClose { removeTextChangedListener(watcher) } +} + +class FeeUiHelper { + companion object { + fun feeToId(fee: FeeType): Int { + return when (fee) { + FeeType.SINGLE -> 0 + FeeType.LOW -> R.id.chipLow + FeeType.NORMAL -> R.id.chipNormal + FeeType.PRIORITY -> R.id.chipPriority + } + } + + fun idToFee(id: Int): FeeType { + return when (id) { + R.id.chipLow -> FeeType.LOW + R.id.chipNormal -> FeeType.NORMAL + R.id.chipPriority -> FeeType.PRIORITY + else -> FeeType.NORMAL + } + } + } +} + +private fun ToggleWidget.setupSendButtonStateModifiers(context: Context) { + mainViewModifiers.clear() + mainViewModifiers.add(ReplaceTextStateModifier(context.getString(R.string.send_btn_send), "")) + mainViewModifiers.add( + TextViewDrawableStateModifier( + context.getDrawableCompat(R.drawable.ic_arrow_right), null, TextViewDrawableStateModifier.RIGHT + )) + mainViewModifiers.add(ClickableStateModifier()) + toggleViewModifiers.clear() + toggleViewModifiers.add(ShowHideStateModifier()) +} + diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/FragmentStateSubscriber.kt b/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/FragmentStateSubscriber.kt new file mode 100644 index 0000000000..aa129ebbb5 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/FragmentStateSubscriber.kt @@ -0,0 +1,21 @@ +package com.tangem.tap.features.send.ui.stateSubscribers + +import com.tangem.tap.features.send.BaseStoreFragment +import org.rekotlin.StateType +import org.rekotlin.StoreSubscriber +import java.lang.ref.WeakReference + +/** +[REDACTED_AUTHOR] + */ +abstract class FragmentStateSubscriber(fragment: BaseStoreFragment) : StoreSubscriber { + private val weakFragment: WeakReference = WeakReference(fragment) + + abstract fun updateWithNewState(fg: BaseStoreFragment, state: S) + + override fun newState(state: S) { + val fg = weakFragment.get() ?: return + + updateWithNewState(fg, state) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt b/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt new file mode 100644 index 0000000000..349898d061 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt @@ -0,0 +1,269 @@ +package com.tangem.tap.features.send.ui.stateSubscribers + +import android.content.Context +import android.text.SpannableStringBuilder +import android.view.ViewGroup +import androidx.core.text.bold +import com.tangem.tap.common.extensions.beginDelayedTransition +import com.tangem.tap.common.extensions.enableError +import com.tangem.tap.common.extensions.show +import com.tangem.tap.common.extensions.update +import com.tangem.tap.common.redux.getMessageString +import com.tangem.tap.common.text.DecimalDigitsInputFilter +import com.tangem.tap.common.toggleWidget.ProgressState +import com.tangem.tap.domain.MultiMessageError +import com.tangem.tap.domain.assembleErrors +import com.tangem.tap.features.send.BaseStoreFragment +import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.Error +import com.tangem.tap.features.send.redux.FeeAction +import com.tangem.tap.features.send.redux.reducers.ReceiptReducer +import com.tangem.tap.features.send.redux.states.* +import com.tangem.tap.features.send.ui.FeeUiHelper +import com.tangem.tap.features.send.ui.SendFragment +import com.tangem.tap.store +import com.tangem.wallet.R +import kotlinx.android.synthetic.main.btn_expand_collapse.* +import kotlinx.android.synthetic.main.fragment_send.* +import kotlinx.android.synthetic.main.fragment_send.clReceiptContainer +import kotlinx.android.synthetic.main.layout_receipt_total.* +import kotlinx.android.synthetic.main.layout_receipt_total.view.* +import kotlinx.android.synthetic.main.layout_send_address_payid.* +import kotlinx.android.synthetic.main.layout_send_amount.* +import kotlinx.android.synthetic.main.layout_send_fee.* +import kotlinx.android.synthetic.main.layout_send_receipt.* + +/** +[REDACTED_AUTHOR] + */ +class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber(fragment) { + + override fun updateWithNewState(fg: BaseStoreFragment, state: SendState) { + val lastChangedStates = state.lastChangedStates.toList() + state.lastChangedStates.clear() + lastChangedStates.forEach { + when (it) { + StateId.SEND_SCREEN -> handleSendScreen(fg, state) + StateId.ADDRESS_PAY_ID -> handleAddressPayIdState(fg, state.addressPayIdState) + StateId.AMOUNT -> handleAmountState(fg, state.amountState) + StateId.FEE -> handleFeeState(fg, state.feeState) + StateId.RECEIPT -> handleReceiptState(fg, state.receiptState) + } + } + } + + private fun handleSendScreen(fg: BaseStoreFragment, state: SendState) { + val sendFragment = (fg as? SendFragment) ?: return + + when (state.sendButtonState) { + SendButtonState.ENABLED -> { + fg.btnSend.isEnabled = true + sendFragment.sendBtn.setState(ProgressState.None(), true) + } + SendButtonState.DISABLED -> { + fg.btnSend.isEnabled = false + sendFragment.sendBtn.setState(ProgressState.None(), true) + } + SendButtonState.PROGRESS -> { + fg.btnSend.isEnabled = true + sendFragment.sendBtn.setState(ProgressState.Progress(), true) + } + } + } + + private fun handleAddressPayIdState(fg: BaseStoreFragment, state: AddressPayIdState) { + fun parseError(context: Context, error: Error?): String? { + val resId = when (error) { + Error.PAY_ID_UNSUPPORTED_BY_BLOCKCHAIN -> R.string.error_payid_unsupported_by_blockchain + Error.PAY_ID_NOT_REGISTERED -> R.string.error_payid_not_registere + Error.PAY_ID_REQUEST_FAILED -> R.string.error_payid_request_failed + Error.ADDRESS_INVALID_OR_UNSUPPORTED_BY_BLOCKCHAIN -> R.string.error_address_invalid_or_unsupported + Error.ADDRESS_SAME_AS_WALLET -> R.string.error_address_same_as_wallet + else -> null + } + return if (resId == null) null else context.getString(resId, "", "") + } + fg.imvPaste.isEnabled = state.pasteIsEnabled + + val et = fg.etAddressOrPayId + val til = fg.tilAddressOrPayId + val parsedError = parseError(til.context, state.error) + + til.parent?.parent?.beginDelayedTransition() + til.error = parsedError + til.isErrorEnabled = parsedError != null + til.helperText = state.recipientWalletAddress + til.isHelperTextEnabled = state.isPayIdState() && parsedError == null + + if (!state.viewFieldValue.isFromUserInput) et.update(state.viewFieldValue.value) + } + + private fun handleAmountState(fg: BaseStoreFragment, state: AmountState) { + if (state.error != null) { + val context = fg.requireContext() + val message = when (state.error) { + is MultiMessageError -> { + val multiError = state.error as MultiMessageError + val messageList = multiError.assembleErrors().map { getMessageString(context, it.first, it.second) } + multiError.builder(messageList) + } + else -> context.getString(state.error.localizedMessage) + } + fg.amountContainer.parent?.beginDelayedTransition() + fg.tilAmountToSend.enableError(true, message) + } else { + if (fg.tilAmountToSend.isErrorEnabled) fg.amountContainer.parent?.beginDelayedTransition() + fg.tilAmountToSend.enableError(false) + } + + val filter = DecimalDigitsInputFilter(12, state.maxLengthOfAmount, state.decimalSeparator) + fg.etAmountToSend.filters = arrayOf(filter) + val amountToSend = state.viewAmountValue + if (!amountToSend.isFromUserInput) fg.etAmountToSend.update(amountToSend.value) + +// fg.tvAmountToSendShadow.text = amountToSend +// if (amountToSend.length > 10) { +// post is needed to wait for text size changes +// fg.tvAmountToSendShadow.post { +// fg.etAmountToSend.setTextSize(TypedValue.COMPLEX_UNIT_PX, fg.tvAmountToSendShadow.textSize - 2) +// fg.etAmountToSend.update(amountToSend) +// if (!state.cursorAtTheSamePosition) fg.etAmountToSend.setSelection(amountToSend.length) +// } +// } else { +// val textSize = fg.resources.getDimension(R.dimen.text_size_amount_to_send) +// fg.tvAmountToSendShadow.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize) +// fg.etAmountToSend.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize) +// fg.etAmountToSend.update(amountToSend) +// if (!state.cursorAtTheSamePosition) fg.etAmountToSend.setSelection(amountToSend.length) +// } + + fg.tvAmountCurrency.update(state.mainCurrency.currencySymbol) + (fg as? SendFragment)?.saveMainCurrency(state.mainCurrency.type) + + val balanceText = fg.getString(R.string.send_balance, + state.mainCurrency.currencySymbol, + state.viewBalanceValue) + fg.tvBalance.update(balanceText) + } + + private fun handleFeeState(fg: BaseStoreFragment, state: FeeState) { + var delayedTransitionScheduled = false + fg.view?.findViewById(R.id.clNetworkFee)?.let { + it.show(state.mainLayoutIsVisible) { + (it.parent as? ViewGroup)?.beginDelayedTransition() + delayedTransitionScheduled = true + } + } + + fg.imvExpandCollapse.rotation = if (state.controlsLayoutIsVisible) 0f else 180f + fg.llFeeControlsContainer.show(state.controlsLayoutIsVisible) { + if (!delayedTransitionScheduled) { + fg.llFeeControlsContainer.parent?.parent?.beginDelayedTransition() + } + } + + fg.chipGroup.show(state.feeChipGroupIsVisible) { + if (!delayedTransitionScheduled) { + fg.llFeeControlsContainer.parent?.parent?.beginDelayedTransition() + } + } + + fg.swIncludeFee.isEnabled = state.includeFeeSwitcherIsEnabled + if (fg.swIncludeFee.isChecked != state.feeIsIncluded) { + fg.swIncludeFee.isChecked = state.feeIsIncluded + } + + if (state.error == FeeAction.Error.REQUEST_FAILED) { + fg.showRetrySnackbar(fg.requireContext().getString(R.string.error_fee_request_failed)) { + store.dispatch(FeeAction.RequestFee) + } + } + + val chipId = FeeUiHelper.feeToId(state.selectedFeeType) + if (fg.chipGroup.checkedChipId != chipId && chipId != 0) fg.chipGroup.check(chipId) + } + + private fun handleReceiptState(fg: BaseStoreFragment, state: ReceiptState) { + val mainLayout = fg.clReceiptContainer as ViewGroup + val totalLayout = fg.llTotal as ViewGroup + val totalTokenLayout = fg.flTotalTokenCrypto as ViewGroup + fun getString(id: Int): String = mainLayout.context.getString(id) + + val rough = getString(R.string.sign_rough) + fun roughOrEmpty(value: String): String = if (value == ReceiptReducer.EMPTY) value else "$rough $value" + + when (state.visibleTypeOfReceipt) { + ReceiptLayoutType.FIAT -> { + val receipt = state.fiat ?: return + + totalLayout.show(true) + totalTokenLayout.show(false) + fg.tvReceiptAmountValue.update("${receipt.amountFiat} ${receipt.symbols.fiat}") + fg.tvReceiptFeeValue.update("${receipt.feeFiat} ${receipt.symbols.fiat}") + totalLayout.tvTotalValue.update("${roughOrEmpty(receipt.totalFiat)} ${receipt.symbols.fiat}") + + val willSent = SpannableStringBuilder() + .bold { append(receipt.willSentCrypto) }.append(" ") + .append(receipt.symbols.crypto).append(" ") + .append(getString(R.string.send_total_will_be_sent)) + totalLayout.tvWillBeSentValue.update(willSent.toString()) + + } + ReceiptLayoutType.CRYPTO -> { + val receipt = state.crypto ?: return + + totalLayout.show(true) + totalTokenLayout.show(false) + fg.tvReceiptAmountValue.update("${receipt.amountCrypto} ${receipt.symbols.crypto}") + fg.tvReceiptFeeValue.update("${receipt.feeCrypto} ${receipt.symbols.crypto}") + totalLayout.tvTotalValue.update("${receipt.totalCrypto} ${receipt.symbols.crypto}") + + val willSent = SpannableStringBuilder() + .bold { + append(roughOrEmpty(receipt.willSentFiat)).append(" ") + append(receipt.symbols.fiat) + append(" (fee: ${receipt.feeFiat} ") + append(receipt.symbols.fiat).append(")") + } + totalLayout.tvWillBeSentValue.update(willSent.toString()) + } + ReceiptLayoutType.TOKEN_FIAT -> { + val receipt = state.tokenFiat ?: return + + totalLayout.show(true) + totalTokenLayout.show(false) + fg.tvReceiptAmountValue.update("${receipt.amountFiat} ${receipt.symbols.fiat}") + fg.tvReceiptFeeValue.update("${receipt.feeFiat} ${receipt.symbols.fiat}") + totalLayout.tvTotalValue.update("${roughOrEmpty(receipt.totalFiat)} ${receipt.symbols.fiat}") + + val willSent = SpannableStringBuilder() + .bold { + append(receipt.symbols.token) + append(" ") + append(receipt.willSentToken) + }.append(" ").append(getString(R.string.generic_and)).append(" ") + .bold { + append(receipt.symbols.crypto).append(" ") + append(receipt.willSentFeeCoin).append(" ") + } + .append(mainLayout.context.getString(R.string.send_total_will_be_sent)) + totalLayout.tvWillBeSentValue.update(willSent.toString()) + } + ReceiptLayoutType.TOKEN_CRYPTO -> { + val receipt = state.tokenCrypto ?: return + + totalLayout.show(false) + totalTokenLayout.show(true) + + fg.tvReceiptAmountValue.update("${receipt.amountToken} ${receipt.symbols.token}") + fg.tvReceiptFeeValue.update("${receipt.feeCoin} ${receipt.symbols.crypto}") + + val willSent = SpannableStringBuilder() + .bold { + append(roughOrEmpty(receipt.totalFiat)).append(" ") + append(receipt.symbols.fiat) + } + totalTokenLayout.tvTotalTokenCryptoValue.update(willSent.toString()) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/models/PendingTransaction.kt b/app/src/main/java/com/tangem/tap/features/wallet/models/PendingTransaction.kt new file mode 100644 index 0000000000..afa8bd8e17 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/wallet/models/PendingTransaction.kt @@ -0,0 +1,40 @@ +package com.tangem.tap.features.wallet.models + +import com.tangem.blockchain.common.TransactionData +import com.tangem.blockchain.common.TransactionStatus +import com.tangem.tap.common.extensions.toFormattedString + +data class PendingTransaction( + val address: String?, + val amount: String?, + val currency: String, + val type: PendingTransactionType +) + +enum class PendingTransactionType { Incoming, Outcoming } + +fun TransactionData.toPendingTransaction(walletAddress: String): PendingTransaction? { + if (this.status == TransactionStatus.Confirmed) return null + + val type: PendingTransactionType = if (this.sourceAddress == walletAddress) { + PendingTransactionType.Outcoming + } else { + PendingTransactionType.Incoming + } + val address = if (this.sourceAddress == walletAddress) { + this.destinationAddress + } else { + this.sourceAddress + } + + return PendingTransaction( + if (address == "unknown") null else address, + this.amount.value?.toFormattedString(amount.decimals), + this.amount.currencySymbol, + type + ) +} + +fun List.toPendingTransactions(walletAddress: String): List{ + return this.mapNotNull { it.toPendingTransaction(walletAddress) } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt new file mode 100644 index 0000000000..5717bdf1ef --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt @@ -0,0 +1,86 @@ +package com.tangem.tap.features.wallet.redux + +import android.content.Context +import com.tangem.blockchain.common.Amount +import com.tangem.blockchain.common.Wallet +import com.tangem.commands.Card +import com.tangem.tap.common.redux.ErrorAction +import com.tangem.tap.common.redux.NotificationAction +import com.tangem.tap.common.redux.global.CryptoCurrencyName +import com.tangem.tap.domain.TapError +import com.tangem.wallet.R +import org.rekotlin.Action +import java.math.BigDecimal + +sealed class WalletAction : Action { + + object ResetState : WalletAction() + + object LoadData : WalletAction() { + data class Failure(val error: TapError) : WalletAction() + } + + object LoadWallet : WalletAction() { + data class Success(val wallet: Wallet) : WalletAction() + data class NoAccount(val amountToCreateAccount: String) : WalletAction() + data class Failure(val errorMessage: String? = null) : WalletAction() + } + + object CheckIfWarningNeeded : WalletAction() + object CheckHashesCountOnline : WalletAction() + object NeedToCheckHashesCountOnline : WalletAction() + object ConfirmHashesCount : WalletAction() + data class ShowWarning(val warningType: WarningType) : WalletAction() + object SaveCardId : WalletAction() + + object UpdateWallet : WalletAction() { + object ScheduleUpdatingWallet : WalletAction() + data class Success(val wallet: Wallet) : WalletAction() + data class Failure(val errorMessage: String? = null) : WalletAction() + } + + object LoadFiatRate : WalletAction() { + data class Success(val fiatRates: Pair) : WalletAction() + object Failure : WalletAction() + } + + object LoadPayId : WalletAction() { + data class Success(val payId: String) : WalletAction() + object NotCreated : WalletAction() + object Failure : WalletAction() + } + + data class LoadArtwork(val card: Card, val artworkId: String?) : WalletAction() { + data class Success(val artwork: Artwork) : WalletAction() + object Failure : WalletAction() + } + + object Scan : WalletAction() + data class Send(val amount: Amount? = null) : WalletAction() { + data class ChooseCurrency(val amounts: List?) : WalletAction() + object Cancel : WalletAction() + } + + object CreatePayId : WalletAction() { + data class CompleteCreatingPayId(val payId: String) : WalletAction() + data class Success(val payId: String) : WalletAction() + object EmptyField : WalletAction(), ErrorAction { + override val error = TapError.PayIdEmptyField + } + + class Failure(override val error: TapError) : WalletAction(), ErrorAction + object Cancel : WalletAction() + } + + data class CopyAddress(val context: Context) : WalletAction() { + object Success : WalletAction(), NotificationAction { + override val messageResource = R.string.notification_address_copied + } + } + + object ShowQrCode : WalletAction() + object HideDialog : WalletAction() + data class ExploreAddress(val context: Context) : WalletAction() + object CreateWallet : WalletAction() + object EmptyWallet : WalletAction() +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletMiddleware.kt new file mode 100644 index 0000000000..da8dc6eeed --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletMiddleware.kt @@ -0,0 +1,240 @@ +package com.tangem.tap.features.wallet.redux + +import android.content.Intent +import android.net.Uri +import androidx.core.content.ContextCompat +import com.tangem.blockchain.common.Amount +import com.tangem.blockchain.common.AmountType +import com.tangem.blockchain.common.SignatureCountValidator +import com.tangem.blockchain.common.Wallet +import com.tangem.blockchain.extensions.SimpleResult +import com.tangem.commands.Card +import com.tangem.commands.common.network.Result +import com.tangem.common.CompletionResult +import com.tangem.common.extensions.CardType +import com.tangem.common.extensions.getType +import com.tangem.common.extensions.toHexString +import com.tangem.tap.common.extensions.copyToClipboard +import com.tangem.tap.common.redux.AppState +import com.tangem.tap.common.redux.navigation.AppScreen +import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.domain.PayIdManager +import com.tangem.tap.domain.TapError +import com.tangem.tap.domain.extensions.toSendableAmounts +import com.tangem.tap.features.send.redux.PrepareSendScreen +import com.tangem.tap.features.wallet.models.toPendingTransactions +import com.tangem.tap.network.NetworkConnectivity +import com.tangem.tap.network.NetworkStateChanged +import com.tangem.tap.preferencesStorage +import com.tangem.tap.scope +import com.tangem.tap.store +import com.tangem.tap.tangemSdkManager +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.rekotlin.Action +import org.rekotlin.Middleware + + +val walletMiddleware: Middleware = { dispatch, state -> + { next -> + { action -> + when (action) { + is WalletAction.LoadWallet -> { + scope.launch { + store.state.globalState.tapWalletManager.loadWalletData() + } + } + is WalletAction.LoadPayId -> { + scope.launch { + store.state.globalState.tapWalletManager.loadPayId() + } + } + is WalletAction.LoadFiatRate -> { + scope.launch { + store.state.globalState.tapWalletManager.loadFiatRate(store.state.globalState.appCurrency) + } + } + is WalletAction.CreateWallet -> { + scope.launch { + val result = tangemSdkManager.createWallet( + store.state.globalState.scanNoteResponse?.card?.cardId + ) + when (result) { + is CompletionResult.Success -> { + store.state.globalState.tapWalletManager.onCardScanned(result.data) + } + + } + } + } + is WalletAction.UpdateWallet -> { + scope.launch { store.state.globalState.tapWalletManager.updateWallet() } + } + is WalletAction.UpdateWallet.Success -> setupWalletUpdate(action.wallet) + is WalletAction.LoadWallet.Success -> { + store.dispatch(WalletAction.CheckHashesCountOnline) + if (!store.state.walletState.updatingWallet) setupWalletUpdate(action.wallet) + } + is WalletAction.CreatePayId.CompleteCreatingPayId -> { + scope.launch { + val cardId = store.state.globalState.scanNoteResponse?.card?.cardId + val wallet = store.state.globalState.scanNoteResponse?.walletManager?.wallet + val publicKey = store.state.globalState.scanNoteResponse?.card?.cardPublicKey + if (cardId != null && wallet != null && publicKey != null) { + val result = PayIdManager().setPayId( + cardId, publicKey.toHexString(), + action.payId, wallet.address, wallet.blockchain + ) + withContext(Dispatchers.Main) { + when (result) { + is Result.Success -> + store.dispatch(WalletAction.CreatePayId.Success(action.payId)) + is Result.Failure -> { + val error = result.error as? TapError + ?: TapError.PayIdCreatingError + store.dispatch(WalletAction.CreatePayId.Failure(error)) + } + } + } + } + } + } + is WalletAction.Scan -> { + scope.launch { + val result = tangemSdkManager.scanNote() + when (result) { + is CompletionResult.Success -> { + store.state.globalState.tapWalletManager.onCardScanned(result.data) + } + } + } + } + is WalletAction.LoadData -> { + scope.launch { + store.state.globalState.scanNoteResponse?.let { + store.state.globalState.tapWalletManager.loadData(it) + } + } + } + is NetworkStateChanged -> { + store.state.globalState.scanNoteResponse?.let { scanNoteResponse -> + store.dispatch(WalletAction.CheckHashesCountOnline) + scope.launch { + store.state.globalState.tapWalletManager.onCardScanned(scanNoteResponse) + } + } + } + is WalletAction.CopyAddress -> { + store.state.walletState.addressData?.address?.let { + action.context.copyToClipboard(it) + store.dispatch(WalletAction.CopyAddress.Success) + } + } + is WalletAction.ExploreAddress -> { + val uri = Uri.parse(store.state.walletState.addressData?.exploreUrl) + val intent = Intent(Intent.ACTION_VIEW, uri) + ContextCompat.startActivity(action.context, intent, null) + } + is WalletAction.Send -> { + val newAction = prepareSendAction(action.amount) + store.dispatch(newAction) + if (newAction is PrepareSendScreen) { + store.dispatch(NavigationAction.NavigateTo(AppScreen.Send)) + } + } + is WalletAction.CheckIfWarningNeeded -> { + val card = store.state.globalState.scanNoteResponse?.card + val validator = store.state.globalState.scanNoteResponse?.walletManager + as? SignatureCountValidator + if (card != null && !preferencesStorage.wasCardScannedBefore(card.cardId)) { + val result = checkIfWarningNeeded(card, validator) + if (result != null) store.dispatch(WalletAction.ShowWarning(result)) + } + } + is WalletAction.CheckHashesCountOnline -> checkHashesCountOnline() + is WalletAction.SaveCardId -> { + val cardId = store.state.globalState.scanNoteResponse?.card?.cardId + cardId?.let { preferencesStorage.saveScannedCardId(it) } + } + } + next(action) + } + } +} + +private fun setupWalletUpdate(wallet: Wallet) { + if (!wallet.recentTransactions.toPendingTransactions(wallet.address).isNullOrEmpty()) { + store.dispatch(WalletAction.UpdateWallet.ScheduleUpdatingWallet) + scope.launch(Dispatchers.IO) { + delay(10000) + withContext(Dispatchers.Main) { + store.dispatch(WalletAction.UpdateWallet) + } + } + } +} + + +private fun prepareSendAction(amount: Amount?): Action { + return if (amount != null) { + if (amount.type == AmountType.Token) { + PrepareSendScreen(store.state.walletState.wallet?.amounts?.get(AmountType.Coin), amount) + } else { + PrepareSendScreen(amount) + } + } else { + val amounts = store.state.walletState.wallet?.amounts?.toSendableAmounts() + if (amounts?.size ?: 0 > 1) { + WalletAction.Send.ChooseCurrency(amounts) + } else { + val amountToSend = amounts?.first() + PrepareSendScreen(amountToSend) + } + } +} + +private fun checkIfWarningNeeded( + card: Card, signatureCountValidator: SignatureCountValidator? = null +): WarningType? { + + if (card.getType() != CardType.Release) { + return WarningType.DevCard + } + + return if (signatureCountValidator == null) { + if (card.walletSignedHashes ?: 0 > 0) { + WarningType.CardSignedHashesBefore + } else { + store.dispatch(WalletAction.SaveCardId) + null + } + } else { + store.dispatch(WalletAction.NeedToCheckHashesCountOnline) + null + } +} + +private fun checkHashesCountOnline() { + if (store.state.walletState.hashesCountVerified != false) return + + if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) return + + val card = store.state.globalState.scanNoteResponse?.card + val validator = store.state.globalState.scanNoteResponse?.walletManager + as? SignatureCountValidator + + scope.launch { + val result = validator?.validateSignatureCount(card?.walletSignedHashes + ?: 0) + withContext(Dispatchers.Main) { + when (result) { + SimpleResult.Success -> store.dispatch(WalletAction.ConfirmHashesCount) + is SimpleResult.Failure -> store.dispatch( + WalletAction.ShowWarning(WarningType.CardSignedHashesBefore) + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletReducer.kt new file mode 100644 index 0000000000..9e1930ca1e --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletReducer.kt @@ -0,0 +1,247 @@ +package com.tangem.tap.features.wallet.redux + +import com.tangem.blockchain.common.AmountType +import com.tangem.blockchain.common.Wallet +import com.tangem.commands.common.network.TangemService +import com.tangem.common.extensions.isZero +import com.tangem.common.extensions.toHexString +import com.tangem.tap.common.extensions.toFiatString +import com.tangem.tap.common.extensions.toFormattedCurrencyString +import com.tangem.tap.common.extensions.toFormattedString +import com.tangem.tap.common.extensions.toQrCode +import com.tangem.tap.common.redux.AppState +import com.tangem.tap.domain.TapError +import com.tangem.tap.features.wallet.models.toPendingTransactions +import com.tangem.tap.features.wallet.ui.BalanceStatus +import com.tangem.tap.features.wallet.ui.BalanceWidgetData +import com.tangem.tap.features.wallet.ui.TokenData +import com.tangem.tap.store +import org.rekotlin.Action + +class WalletReducer { + companion object { + fun reduce(action: Action, state: AppState): WalletState = internalReduce(action, state) + } +} + +private fun internalReduce(action: Action, state: AppState): WalletState { + + if (action !is WalletAction) return state.walletState + + var newState = state.walletState + + when (action) { + is WalletAction.ResetState -> newState = WalletState() + is WalletAction.EmptyWallet -> newState = newState.copy( + state = ProgressState.Done, + currencyData = BalanceWidgetData(BalanceStatus.EmptyCard), + mainButton = WalletMainButton.CreateWalletButton(true) + ) + is WalletAction.LoadData.Failure -> { + when (action.error) { + is TapError.NoInternetConnection -> { + val wallet = state.globalState.scanNoteResponse?.walletManager?.wallet + val addressData = if (wallet == null) { + null + } else { + AddressData(wallet.address, wallet.shareUrl, wallet.exploreUrl) + } + newState = newState.copy( + state = ProgressState.Error, + error = ErrorType.NoInternetConnection, + addressData = addressData, + currencyData = BalanceWidgetData( + status = BalanceStatus.Unreachable, + currency = wallet?.blockchain?.fullName), + mainButton = WalletMainButton.SendButton(false) + ) + } + is TapError.UnknownBlockchain -> { + newState = newState.copy( + state = ProgressState.Done, + currencyData = BalanceWidgetData(BalanceStatus.UnknownBlockchain) + ) + } + } + + } + is WalletAction.LoadWallet -> { + val wallet = state.globalState.scanNoteResponse?.walletManager?.wallet + val addressData = if (wallet == null) { + null + } else { + AddressData(wallet.address, wallet.shareUrl, wallet.exploreUrl) + } + val currentArtworkId = state.globalState.scanNoteResponse?.verifyResponse?.artworkInfo?.id + val cardImage = if (newState.cardImage?.artworkId == currentArtworkId) { + newState.cardImage + } else { + null + } + newState = newState.copy( + state = ProgressState.Loading, + cardImage = cardImage, + currencyData = BalanceWidgetData( + BalanceStatus.Loading, + wallet?.blockchain?.fullName + ), + addressData = addressData, + mainButton = WalletMainButton.SendButton(false) + + ) + } + is WalletAction.LoadWallet.Success -> newState = onWalletLoaded(action.wallet, newState) + + is WalletAction.LoadWallet.NoAccount -> { + val wallet = state.globalState.scanNoteResponse?.walletManager?.wallet + newState = newState.copy( + state = ProgressState.Done, wallet = wallet, + currencyData = BalanceWidgetData( + BalanceStatus.NoAccount, wallet?.blockchain?.fullName, + currencySymbol = wallet?.blockchain?.currency, + amountToCreateAccount = action.amountToCreateAccount + ) + ) + } + is WalletAction.LoadWallet.Failure -> newState = newState.copy( + state = ProgressState.Done, + currencyData = newState.currencyData.copy( + status = BalanceStatus.Unreachable, + errorMessage = action.errorMessage + ) + ) + is WalletAction.UpdateWallet -> newState = newState.copy(updatingWallet = true) + is WalletAction.UpdateWallet.ScheduleUpdatingWallet -> + newState = newState.copy(updatingWallet = true) + is WalletAction.UpdateWallet.Success -> { + newState = onWalletLoaded(action.wallet, newState) + newState = newState.copy(updatingWallet = newState.pendingTransactions.isNotEmpty()) + } + is WalletAction.UpdateWallet.Failure -> newState = newState.copy(updatingWallet = false) + is WalletAction.LoadFiatRate -> { + newState.copy(currencyData = newState.currencyData.copy( + fiatAmount = null, + token = newState.currencyData.token?.copy(fiatAmount = null)) + ) + } + is WalletAction.LoadFiatRate.Success -> { + val rate = action.fiatRates.second ?: return newState + val currency = action.fiatRates.first + val fiatAmount = if (currency == newState.wallet?.blockchain?.currency) { + newState.wallet?.amounts?.get(AmountType.Coin)?.value + ?.toFiatString(rate, state.globalState.appCurrency) + } else { + newState.currencyData.fiatAmount + } + val tokenFiatAmount = if (currency == newState.wallet?.token?.symbol) { + newState.wallet?.amounts?.get(AmountType.Token)?.value + ?.toFiatString(rate, state.globalState.appCurrency) + } else { + newState.currencyData.token?.fiatAmount + } + newState = newState.copy(currencyData = newState.currencyData.copy( + fiatAmount = fiatAmount, + token = newState.currencyData.token?.copy(fiatAmount = tokenFiatAmount) + )) + } + is WalletAction.LoadArtwork -> { + val cardId = action.card.cardId + val cardPublicKey = action.card.cardPublicKey?.toHexString() + val artworkUrl = if (cardPublicKey != null && action.artworkId != null) { + TangemService.getUrlForArtwork(cardId, cardPublicKey, action.artworkId) + } else if (action.card.cardId.startsWith(Artwork.SERGIO_CARD_ID)) { + Artwork.SERGIO_CARD_URL + } else if (action.card.cardId.startsWith(Artwork.MARTA_CARD_ID)) { + Artwork.MARTA_CARD_URL + } else { + Artwork.DEFAULT_IMG_URL + } + newState = newState.copy(cardImage = Artwork(artworkId = artworkUrl)) + } + is WalletAction.ShowQrCode -> { + newState = newState.copy( + walletDialog = WalletDialog.QrDialog( + newState.addressData?.shareUrl?.toQrCode(), + newState.addressData?.shareUrl, + newState.currencyData.currency + ) + ) + } + is WalletAction.HideDialog -> { + newState = newState.copy(walletDialog = null) + } + is WalletAction.LoadPayId.Success -> newState = newState.copy( + payIdData = PayIdData(PayIdState.Created, action.payId) + ) + is WalletAction.LoadPayId.NotCreated -> newState = newState.copy( + payIdData = PayIdData(PayIdState.NotCreated, null) + ) + is WalletAction.CreatePayId, is WalletAction.CreatePayId.Failure -> + newState = newState.copy( + walletDialog = WalletDialog.CreatePayIdDialog(CreatingPayIdState.EnterPayId) + ) + is WalletAction.CreatePayId.CompleteCreatingPayId -> newState = newState.copy( + walletDialog = WalletDialog.CreatePayIdDialog(CreatingPayIdState.Waiting) + ) + is WalletAction.CreatePayId.Success -> newState = newState.copy( + payIdData = PayIdData(PayIdState.Created, action.payId), + walletDialog = null + ) + is WalletAction.CreatePayId.Cancel -> newState = newState.copy(walletDialog = null) + is WalletAction.Send.ChooseCurrency -> { + newState = newState.copy( + walletDialog = WalletDialog.SelectAmountToSendDialog(action.amounts) + ) + } + is WalletAction.Send.Cancel -> newState = newState.copy(walletDialog = null) + is WalletAction.ShowWarning -> + newState = newState.copy(walletDialog = WalletDialog.WarningDialog(action.warningType)) + is WalletAction.NeedToCheckHashesCountOnline -> + newState = newState.copy(hashesCountVerified = false) + is WalletAction.ConfirmHashesCount -> + newState = newState.copy(hashesCountVerified = true) + } + return newState +} + +private fun onWalletLoaded(wallet: Wallet, walletState: WalletState): WalletState { + val fiatCurrencySymbol = store.state.globalState.appCurrency + val token = wallet.amounts[AmountType.Token] + val tokenData = if (token != null) { + val tokenFiatRate = store.state.globalState.conversionRates.getRate(token.currencySymbol) + val tokenFiatAmount = tokenFiatRate?.let { token.value?.toFiatString(it, fiatCurrencySymbol) } + TokenData( + token.value?.toFormattedString(token.decimals) ?: "", + token.currencySymbol, tokenFiatAmount) + } else { + null + } + val amount = wallet.amounts[AmountType.Coin]?.value + val formattedAmount = amount?.toFormattedCurrencyString( + wallet.blockchain.decimals(), + wallet.blockchain.currency) + val fiatRate = store.state.globalState.conversionRates.getRate(wallet.blockchain.currency) + val fiatAmount = fiatRate?.let { amount?.toFiatString(it, fiatCurrencySymbol) } + + val pendingTransactions = wallet.recentTransactions + .toPendingTransactions(wallet.address) + + val sendButtonEnabled = amount?.isZero() == false && pendingTransactions.isEmpty() + val balanceStatus = if (pendingTransactions.isNotEmpty()) { + BalanceStatus.TransactionInProgress + } else { + BalanceStatus.VerifiedOnline + } + return walletState.copy( + state = ProgressState.Done, wallet = wallet, + currencyData = BalanceWidgetData( + balanceStatus, wallet.blockchain.fullName, + currencySymbol = wallet.blockchain.currency, + formattedAmount, + token = tokenData, + fiatAmount = fiatAmount + ), + pendingTransactions = pendingTransactions, + mainButton = WalletMainButton.SendButton(sendButtonEnabled) + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt new file mode 100644 index 0000000000..be5c2fad71 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt @@ -0,0 +1,79 @@ +package com.tangem.tap.features.wallet.redux + +import android.graphics.Bitmap +import com.tangem.blockchain.common.Amount +import com.tangem.blockchain.common.Wallet +import com.tangem.tap.common.entities.Button +import com.tangem.tap.common.redux.global.CryptoCurrencyName +import com.tangem.tap.features.wallet.models.PendingTransaction +import com.tangem.tap.features.wallet.ui.BalanceWidgetData +import org.rekotlin.StateType + +data class WalletState( + val state: ProgressState = ProgressState.Done, + val error: ErrorType? = null, + val cardImage: Artwork? = null, + val wallet: Wallet? = null, + val pendingTransactions: List = emptyList(), + val hashesCountVerified: Boolean? = null, + val addressData: AddressData? = null, + val currencyData: BalanceWidgetData = BalanceWidgetData(), + val payIdData: PayIdData = PayIdData(), + val walletDialog: WalletDialog? = null, + val updatingWallet: Boolean = false, + val mainButton: WalletMainButton = WalletMainButton.SendButton(false) +) : StateType { + val showDetails: Boolean = + currencyData.status != com.tangem.tap.features.wallet.ui.BalanceStatus.EmptyCard && + currencyData.status != com.tangem.tap.features.wallet.ui.BalanceStatus.UnknownBlockchain +} + +sealed class WalletDialog { + data class QrDialog( + val qrCode: Bitmap?, val shareUrl: String?, val currencyName: CryptoCurrencyName? + ) : WalletDialog() + + data class CreatePayIdDialog(val creatingPayIdState: CreatingPayIdState?) : WalletDialog() + data class SelectAmountToSendDialog(val amounts: List?) : WalletDialog() + data class WarningDialog(val type: WarningType) : WalletDialog() +} + +enum class WarningType { CardSignedHashesBefore, DevCard } + + +enum class ProgressState { Loading, Done, Error } + +enum class ErrorType { NoInternetConnection } + +enum class PayIdState { Disabled, Loading, NotCreated, Created, ErrorLoading } + +data class PayIdData( + val payIdState: PayIdState = PayIdState.Loading, + val payId: String? = null +) + +enum class CreatingPayIdState { EnterPayId, Waiting } + +sealed class WalletMainButton(enabled: Boolean) : Button(enabled) { + class SendButton(enabled: Boolean) : WalletMainButton(enabled) + class CreateWalletButton(enabled: Boolean) : WalletMainButton(enabled) +} + +data class AddressData( + val address: String, + val shareUrl: String, + val exploreUrl: String +) + +data class Artwork( + val artworkId: String, + val artwork: Bitmap? = null +) { + companion object { + const val DEFAULT_IMG_URL = "https://app.tangem.com/cards/card_default.png" + const val SERGIO_CARD_URL = "https://app.tangem.com/cards/card_tg059.png" + const val MARTA_CARD_URL = "https://app.tangem.com/cards/card_tg083.png" + const val SERGIO_CARD_ID = "BC01" + const val MARTA_CARD_ID = "BC02" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt new file mode 100644 index 0000000000..4a8cb2ca4f --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt @@ -0,0 +1,132 @@ +package com.tangem.tap.features.wallet.ui + +import androidx.annotation.IdRes +import androidx.fragment.app.Fragment +import com.tangem.tap.common.extensions.hide +import com.tangem.tap.common.extensions.show +import com.tangem.wallet.R +import kotlinx.android.synthetic.main.card_balance.* +import kotlinx.android.synthetic.main.layout_balance.* +import kotlinx.android.synthetic.main.layout_balance_error.* +import kotlinx.android.synthetic.main.layout_token.view.* + +enum class BalanceStatus { + VerifiedOnline, + TransactionInProgress, + Unreachable, + Loading, + NoAccount, + EmptyCard, + UnknownBlockchain +} + +data class BalanceWidgetData( + val status: BalanceStatus? = null, + val currency: String? = null, + val currencySymbol: String? = null, + val amount: String? = null, + val fiatAmount: String? = null, + val token: TokenData? = null, + val amountToCreateAccount: String? = null, + val errorMessage: String? = null +) + +data class TokenData( + val amount: String, + val tokenSymbol: String, + val fiatAmount: String? = null +) + + +class BalanceWidget( + val fragment: Fragment, + val data: BalanceWidgetData, +) { + + fun setup() { + + when (data.status) { + BalanceStatus.Loading -> { + fragment.l_balance.show() + fragment.l_balance_error.hide() + fragment.tv_fiat_amount.hide() + fragment.l_token.hide() + + fragment.tv_currency.text = data.currency + fragment.tv_amount.text = "" + + showStatus(R.id.tv_status_loading) + } + BalanceStatus.VerifiedOnline, BalanceStatus.TransactionInProgress -> { + fragment.l_balance.show() + fragment.l_balance_error.hide() + fragment.tv_currency.text = data.currency + fragment.tv_amount.text = data.amount + fragment.tv_fiat_amount.show() + fragment.tv_fiat_amount.text = data.fiatAmount + val statusView = if (data.status == BalanceStatus.VerifiedOnline) { + R.id.tv_status_verified + } else { + fragment.tv_status_error.text = + fragment.getText(R.string.wallet_transaction_in_progress) + R.id.group_error + } + showStatus(statusView) + fragment.tv_status_error_message.hide() + + if (data.token != null) { + fragment.l_token.show() + fragment.l_token.tv_token_symbol.text = data.token.tokenSymbol + fragment.l_token.tv_token_amount.text = data.token.amount + fragment.l_token.tv_token_fiat_amount.text = data.token.fiatAmount + } else { + fragment.l_token.hide() + } + } + BalanceStatus.Unreachable -> { + fragment.l_balance.show() + fragment.l_balance_error.hide() + fragment.l_token.hide() + fragment.tv_fiat_amount.hide() + + fragment.tv_currency.text = data.currency + fragment.tv_amount.text = "" + fragment.tv_status_error_message.text = data.errorMessage + fragment.tv_status_error.text = + fragment.getString(R.string.wallet_blockchain_is_unreachable) + + showStatus(R.id.group_error) + fragment.tv_status_error_message.show(!data.errorMessage.isNullOrBlank()) + } + BalanceStatus.EmptyCard -> { + fragment.l_balance.hide() + fragment.l_balance_error.show() + fragment.tv_error_title.text = fragment.getText(R.string.wallet_empty_card) + fragment.tv_error_descriptions.text = fragment.getText(R.string.wallet_empty_card_description) + } + BalanceStatus.NoAccount -> { + fragment.l_balance.hide() + fragment.l_balance_error.show() + fragment.tv_error_title.text = fragment.getText(R.string.wallet_no_account) + fragment.tv_error_descriptions.text = + fragment.getString( + R.string.wallet_no_account_description, + data.amountToCreateAccount, data.currencySymbol + ) + } + BalanceStatus.UnknownBlockchain -> { + fragment.l_balance.hide() + fragment.l_balance_error.show() + fragment.tv_error_title.text = fragment.getText(R.string.wallet_unknown_blockchain_title) + fragment.tv_error_descriptions.text = + fragment.getString(R.string.wallet_unknown_blockchain) + } + } + } + + private fun showStatus(@IdRes viewRes: Int) { + fragment.group_error.show(viewRes == R.id.group_error) + fragment.tv_status_loading.show(viewRes == R.id.tv_status_loading) + fragment.tv_status_verified.show(viewRes == R.id.tv_status_verified) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/PendingTransactionsAdapter.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/PendingTransactionsAdapter.kt new file mode 100644 index 0000000000..7602ecd237 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/PendingTransactionsAdapter.kt @@ -0,0 +1,68 @@ +package com.tangem.tap.features.wallet.ui + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import com.tangem.tap.common.extensions.getDrawableCompat +import com.tangem.tap.features.wallet.models.PendingTransaction +import com.tangem.tap.features.wallet.models.PendingTransactionType +import com.tangem.wallet.R +import kotlinx.android.synthetic.main.item_pending_transaction.view.* + +class PendingTransactionsAdapter + : ListAdapter(DiffUtilCallback) { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TransactionsViewHolder { + val layout = LayoutInflater.from(parent.context) + .inflate(R.layout.item_pending_transaction, parent, false) + return TransactionsViewHolder(layout) + } + + override fun onBindViewHolder(holder: TransactionsViewHolder, position: Int) { + holder.bind(currentList[position]) + } + + object DiffUtilCallback : DiffUtil.ItemCallback() { + override fun areContentsTheSame( + oldItem: PendingTransaction, newItem: PendingTransaction + ) = oldItem == newItem + + override fun areItemsTheSame( + oldItem: PendingTransaction, newItem: PendingTransaction + ) = oldItem == newItem + } + + + class TransactionsViewHolder(val view: View) : + RecyclerView.ViewHolder(view) { + + fun bind(transaction: PendingTransaction) { + val transactionDescriptionRes = when (transaction.type) { + PendingTransactionType.Incoming -> R.string.wallet_pending_transaction_incoming + PendingTransactionType.Outcoming -> R.string.wallet_pending_transaction_outcoming + } + val transactionAddressRes = when (transaction.type) { + PendingTransactionType.Incoming -> R.string.wallet_pending_transaction_incoming_address + PendingTransactionType.Outcoming -> R.string.wallet_pending_transaction_outcoming_address + } + val image = when (transaction.type) { + PendingTransactionType.Incoming -> R.drawable.ic_arrow_left + PendingTransactionType.Outcoming -> R.drawable.ic_arrow_right_20 + } + view.tv_pending_transaction.text = view.context.getString(transactionDescriptionRes) + + transaction.amount?.let { view.tv_pending_transaction_amount.text = "$it " } + view.tv_pending_transaction_currency.text = "${transaction.currency}" + + if (transaction.address != null) { + view.tv_pending_transaction_address.text = + view.context.getString(transactionAddressRes, transaction.address) + } + + view.iv_pending_transaction.setImageDrawable(view.context.getDrawableCompat(image)) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt new file mode 100644 index 0000000000..c337a895b8 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt @@ -0,0 +1,262 @@ +package com.tangem.tap.features.wallet.ui + +import android.app.Dialog +import android.os.Bundle +import android.view.Menu +import android.view.Menu.NONE +import android.view.MenuInflater +import android.view.MenuItem +import android.view.View +import androidx.activity.OnBackPressedCallback +import androidx.appcompat.app.AppCompatActivity +import androidx.fragment.app.Fragment +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.transition.TransitionInflater +import com.google.android.material.snackbar.Snackbar +import com.squareup.picasso.Picasso +import com.tangem.tap.common.extensions.hide +import com.tangem.tap.common.extensions.show +import com.tangem.tap.common.redux.navigation.AppScreen +import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.features.details.redux.DetailsAction +import com.tangem.tap.features.wallet.redux.* +import com.tangem.tap.features.wallet.ui.dialogs.AmountToSendDialog +import com.tangem.tap.features.wallet.ui.dialogs.PayIdDialog +import com.tangem.tap.features.wallet.ui.dialogs.QrDialog +import com.tangem.tap.features.wallet.ui.dialogs.WarningDialog +import com.tangem.tap.store +import com.tangem.wallet.R +import kotlinx.android.synthetic.main.card_balance.* +import kotlinx.android.synthetic.main.fragment_wallet.* +import kotlinx.android.synthetic.main.layout_address.* +import org.rekotlin.StoreSubscriber + + +class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber { + + private var dialog: Dialog? = null + private var snackbar: Snackbar? = null + + private lateinit var viewAdapter: PendingTransactionsAdapter + + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setHasOptionsMenu(true) + activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + store.dispatch(NavigationAction.PopBackTo()) + } + }) + val inflater = TransitionInflater.from(requireContext()) + enterTransition = inflater.inflateTransition(R.transition.slide_right) + exitTransition = inflater.inflateTransition(R.transition.fade) + } + + override fun onStart() { + super.onStart() + store.subscribe(this) { state -> + state.skipRepeats { oldState, newState -> + oldState.walletState == newState.walletState + }.select { it.walletState } + } + } + + override fun onStop() { + super.onStop() + store.unsubscribe(this) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + (activity as? AppCompatActivity)?.setSupportActionBar(toolbar) + + toolbar.setNavigationOnClickListener { + store.dispatch(NavigationAction.PopBackTo()) + } + + btn_scan.setOnClickListener { + store.dispatch(WalletAction.Scan) + } + setupTransactionsRecyclerView() + } + + + private fun setupTransactionsRecyclerView() { + viewAdapter = PendingTransactionsAdapter() + rv_pending_transaction.layoutManager = LinearLayoutManager(context) + rv_pending_transaction.adapter = viewAdapter + } + + + override fun newState(state: WalletState) { + if (activity == null) return + + if (!state.showDetails) { + toolbar.menu.removeItem(R.id.details_menu) + } else if (toolbar.menu.findItem(R.id.details_menu) == null) { + toolbar.menu.add(R.menu.wallet, R.id.details_menu, NONE, R.string.details_toolbar_title) + } + + srl_wallet.setOnRefreshListener { + store.dispatch(WalletAction.LoadData) + } + + setupNoInternetHandling(state) + + setupButtons(state) + setupAddressCard(state) + setupCardImage(state.cardImage) + + viewAdapter.submitList(state.pendingTransactions) + if (state.pendingTransactions.isEmpty()) { + rv_pending_transaction.hide() + } else { + rv_pending_transaction.show() + } + + handleDialogs(state.walletDialog) + + l_balance.show() + BalanceWidget(this, state.currencyData).setup() + + if (state.state == ProgressState.Done) { + srl_wallet.isRefreshing = false + } + + when (state.payIdData.payIdState) { + PayIdState.Disabled, PayIdState.Loading -> group_payid.hide() + PayIdState.NotCreated -> { + group_payid.show() + tv_create_payid.show() + tv_create_payid.setOnClickListener { store.dispatch(WalletAction.CreatePayId) } + tv_payid_address.hide() + } + PayIdState.Created -> { + group_payid.show() + tv_create_payid.hide() + tv_payid_address.show() + tv_payid_address.text = state.payIdData.payId + + } + PayIdState.ErrorLoading -> { + } + } + } + + private fun setupNoInternetHandling(state: WalletState) { + if (state.state == ProgressState.Error) { + if (state.error == ErrorType.NoInternetConnection) { + srl_wallet?.isRefreshing = false + snackbar = Snackbar.make( + coordinator_wallet, getString(R.string.notification_no_internet), + Snackbar.LENGTH_INDEFINITE + ).setAction(getString(R.string.notification_no_internet_retry)) + { store.dispatch(WalletAction.LoadData) } + .also { it.show() } + } + } else { + snackbar?.dismiss() + } + } + + private fun setupButtons(state: WalletState) { + btn_copy.setOnClickListener { store.dispatch(WalletAction.CopyAddress(requireContext())) } + btn_show_qr.setOnClickListener { store.dispatch(WalletAction.ShowQrCode) } + + val buttonTitle = when (state.mainButton) { + is WalletMainButton.SendButton -> R.string.wallet_button_send + is WalletMainButton.CreateWalletButton -> R.string.wallet_button_create_wallet + } + btn_confirm.text = getString(buttonTitle) + btn_confirm.isEnabled = state.mainButton.enabled + + btn_confirm.setOnClickListener { + when (state.mainButton) { + is WalletMainButton.SendButton -> store.dispatch(WalletAction.Send()) + is WalletMainButton.CreateWalletButton -> store.dispatch(WalletAction.CreateWallet) + } + } + } + + private fun setupAddressCard(state: WalletState) { + if (state.addressData != null) { + l_address?.show() + tv_address.text = state.addressData.address + tv_explore?.setOnClickListener { + store.dispatch(WalletAction.ExploreAddress(requireContext())) + } + } else { + l_address?.hide() + } + } + + private fun setupCardImage(cardImage: Artwork?) { + Picasso.get() + .load(cardImage?.artworkId) + .placeholder(R.drawable.card_placeholder) + ?.error(R.drawable.card_placeholder) + ?.into(iv_card) + } + + private fun handleDialogs(walletDialog: WalletDialog?) { + when (walletDialog) { + is WalletDialog.QrDialog -> { + if (walletDialog.qrCode != null && walletDialog.shareUrl != null) { + if (dialog == null) dialog = QrDialog(requireContext()).apply { + this.showQr( + walletDialog.qrCode, walletDialog.shareUrl, walletDialog.currencyName + ) + } + } + } + is WalletDialog.CreatePayIdDialog -> { + when (walletDialog.creatingPayIdState) { + CreatingPayIdState.EnterPayId -> { + if (dialog == null) dialog = PayIdDialog(requireContext()).apply { + this.show() + } + (dialog as? PayIdDialog)?.stopProgress() + } + CreatingPayIdState.Waiting -> (dialog as? PayIdDialog)?.showProgress() + } + } + is WalletDialog.SelectAmountToSendDialog -> { + if (dialog == null) dialog = AmountToSendDialog(requireContext()).apply { + this.show(walletDialog.amounts) + } + } + is WalletDialog.WarningDialog -> { + if (dialog == null) dialog = WarningDialog(requireContext()).apply { + this.show(walletDialog.type) + } + } + null -> { + dialog?.dismiss() + dialog = null + } + } + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return when (item.itemId) { + R.id.details_menu -> { + store.state.globalState.scanNoteResponse?.card?.let { card -> + store.dispatch(DetailsAction.PrepareScreen( + card, store.state.walletState.wallet, + store.state.globalState.appCurrency + )) + store.dispatch(NavigationAction.NavigateTo(AppScreen.Details)) + true + } + false + } + else -> super.onOptionsItemSelected(item) + } + } + + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + if (store.state.walletState.showDetails) inflater.inflate(R.menu.wallet, menu) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/AmountToSendDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/AmountToSendDialog.kt new file mode 100644 index 0000000000..0175d55b59 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/AmountToSendDialog.kt @@ -0,0 +1,82 @@ +package com.tangem.tap.features.wallet.ui.dialogs + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.appcompat.view.ContextThemeWrapper +import androidx.recyclerview.widget.* +import com.google.android.material.bottomsheet.BottomSheetDialog +import com.tangem.blockchain.common.Amount +import com.tangem.tap.common.extensions.toFormattedString +import com.tangem.tap.features.wallet.redux.WalletAction +import com.tangem.tap.store +import com.tangem.wallet.R +import kotlinx.android.synthetic.main.dialog_wallet_send.* +import kotlinx.android.synthetic.main.item_wallet_amount_to_send.view.* + + +class AmountToSendDialog(context: Context) : BottomSheetDialog(context) { + + init { + this.setContentView(R.layout.dialog_wallet_send) + } + + fun show(amounts: List?) { + this.setOnDismissListener { + store.dispatch(WalletAction.Send.Cancel) + } + + rv_amounts_to_send.layoutManager = LinearLayoutManager(context) + val dividerItemDecoration = DividerItemDecoration( + ContextThemeWrapper(rv_amounts_to_send.context, R.style.AppTheme), + DividerItemDecoration.VERTICAL + ) + rv_amounts_to_send.addItemDecoration(dividerItemDecoration) + + val viewAdapter = ChooseAmountAdapter() + rv_amounts_to_send.adapter = viewAdapter + + viewAdapter.submitList(amounts) + + show() + } +} + + +class ChooseAmountAdapter + : ListAdapter(DiffUtilCallback) { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AmountViewHolder { + val layout = LayoutInflater.from(parent.context) + .inflate(R.layout.item_wallet_amount_to_send, parent, false) + return AmountViewHolder(layout) + } + + override fun onBindViewHolder(holder: AmountViewHolder, position: Int) { + holder.bind(currentList[position]) + } + + object DiffUtilCallback : DiffUtil.ItemCallback() { + override fun areContentsTheSame( + oldItem: Amount, newItem: Amount + ) = oldItem.currencySymbol == newItem.currencySymbol + + override fun areItemsTheSame( + oldItem: Amount, newItem: Amount + ) = oldItem == newItem + } + + class AmountViewHolder(val view: View) : + RecyclerView.ViewHolder(view) { + + fun bind(amount: Amount) { + view.tv_currency_symbol.text = amount.currencySymbol + view.tv_amount.text = amount.value?.toFormattedString(amount.decimals) + view.setOnClickListener { + store.dispatch(WalletAction.Send.Cancel) + store.dispatch(WalletAction.Send(amount)) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/PayIdDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/PayIdDialog.kt new file mode 100644 index 0000000000..024d185db0 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/PayIdDialog.kt @@ -0,0 +1,47 @@ +package com.tangem.tap.features.wallet.ui.dialogs + +import android.app.Dialog +import android.content.Context +import android.view.View +import com.tangem.tap.common.extensions.hide +import com.tangem.tap.common.extensions.show +import com.tangem.tap.features.wallet.redux.WalletAction +import com.tangem.tap.notificationsHandler +import com.tangem.tap.store +import com.tangem.wallet.R +import kotlinx.android.synthetic.main.dialog_create_payid.* + +class PayIdDialog(context: Context) : Dialog(context) { + + init { + this.setContentView(R.layout.dialog_create_payid) + } + + override fun show() { + notificationsHandler?.replaceBaseLayout(this.cl_payid_dialog) + this.setOnDismissListener { + notificationsHandler?.returnBaseLayout() + store.dispatch(WalletAction.CreatePayId.Cancel) + } + this.btn_create_payid?.setOnClickListener { + if (this.et_payid.text.isNullOrBlank()) { + store.dispatch(WalletAction.CreatePayId.EmptyField) + } else { + val payid = this.et_payid.text!!.toString() + + this.context.getString(R.string.wallet_pay_id_address) + store.dispatch(WalletAction.CreatePayId.CompleteCreatingPayId(payid)) + } + } + super.show() + } + + fun showProgress() { + this.btn_create_payid?.visibility = View.INVISIBLE + this.pb_create_payid?.show() + } + + fun stopProgress() { + this.btn_create_payid?.show() + this.pb_create_payid?.hide() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/QrDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/QrDialog.kt new file mode 100644 index 0000000000..c3cca80cd1 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/QrDialog.kt @@ -0,0 +1,36 @@ +package com.tangem.tap.features.wallet.ui.dialogs + +import android.app.Dialog +import android.content.Context +import android.graphics.Bitmap +import com.tangem.tap.common.extensions.hide +import com.tangem.tap.common.extensions.show +import com.tangem.tap.common.redux.global.CryptoCurrencyName +import com.tangem.tap.features.wallet.redux.WalletAction +import com.tangem.tap.store +import com.tangem.wallet.R +import kotlinx.android.synthetic.main.dialog_qrcode.* + +class QrDialog(context: Context) : Dialog(context) { + + init { + this.setContentView(R.layout.dialog_qrcode) + } + + fun showQr(qrCode: Bitmap, shareUrl: String, currencyName: CryptoCurrencyName?) { + this.setOnDismissListener { store.dispatch(WalletAction.HideDialog) } + this.btn_done?.setOnClickListener { store.dispatch(WalletAction.HideDialog) } + + this.tv_qr_dialog_address?.text = shareUrl + this.iv_qrcode?.setImageBitmap(qrCode) + if (currencyName == null) { + this.tv_qr_dialog_header.hide() + } else { + this.tv_qr_dialog_header.show() + this.tv_qr_dialog_header.text = context.getString( + R.string.wallet_dialog_qr_code_header, currencyName + ) + } + super.show() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/WarningDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/WarningDialog.kt new file mode 100644 index 0000000000..349ba8c45d --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/WarningDialog.kt @@ -0,0 +1,30 @@ +package com.tangem.tap.features.wallet.ui.dialogs + +import android.content.Context +import androidx.appcompat.app.AlertDialog +import com.tangem.tap.features.wallet.redux.WalletAction +import com.tangem.tap.features.wallet.redux.WarningType +import com.tangem.tap.store +import com.tangem.wallet.R + +class WarningDialog(context: Context) : AlertDialog(context) { + + private val dialog: AlertDialog = Builder(context) + .setTitle(context.getString(R.string.generic_warning)) + .setPositiveButton(context.getString(R.string.general_ok)) { _, _ -> + dismiss() + }.setOnDismissListener { + store.dispatch(WalletAction.SaveCardId) + store.dispatch(WalletAction.HideDialog) + } + .create() + + fun show(warningType: WarningType) { + val messageRes = when (warningType) { + WarningType.CardSignedHashesBefore -> R.string.warning_card_signed_transactions + WarningType.DevCard -> R.string.wallet_warning_dev_card + } + dialog.setMessage(dialog.context.getString(messageRes)) + dialog.show() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/NetworkConnectivity.kt b/app/src/main/java/com/tangem/tap/network/NetworkConnectivity.kt new file mode 100644 index 0000000000..05c2b6a6bd --- /dev/null +++ b/app/src/main/java/com/tangem/tap/network/NetworkConnectivity.kt @@ -0,0 +1,72 @@ +package com.tangem.tap.network + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.net.ConnectivityManager +import android.net.NetworkCapabilities +import android.os.Build +import org.rekotlin.Action +import org.rekotlin.Store +import java.lang.ref.WeakReference + +/** +[REDACTED_AUTHOR] + */ +class NetworkConnectivity( + private val store: Store<*>, + context: Context +) { + + private val wContext: WeakReference = WeakReference(context) + + private val receiver = object : BroadcastReceiver() { + override fun onReceive(c: Context?, intent: Intent?) { + store.dispatch(NetworkStateChanged(isOnlineOrConnecting())) + } + } + + init { + val intentFilter = IntentFilter() + intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION) + wContext.get()?.registerReceiver(receiver, intentFilter) + store.dispatch(NetworkStateChanged(isOnlineOrConnecting())) + } + + + fun isOnlineOrConnecting(): Boolean { + val connectivityManager = getConnectivityManager() ?: return false + + return if (Build.VERSION.SDK_INT >= 23) { + val capabilities = + connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork) + (capabilities != null) && + (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) || + capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) || + capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) + + } else { + val networkInfo = connectivityManager.activeNetworkInfo + networkInfo != null && networkInfo.isConnectedOrConnecting + } + } + + private fun getConnectivityManager(): ConnectivityManager? { + return wContext.get()?.applicationContext + ?.getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager + } + + companion object { + private lateinit var instance: NetworkConnectivity + + fun createInstance(store: Store<*>, context: Context): NetworkConnectivity { + instance = NetworkConnectivity(store, context) + return instance + } + + fun getInstance(): NetworkConnectivity = instance + } +} + +data class NetworkStateChanged(val isOnline: Boolean) : Action \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/Retrofit.kt b/app/src/main/java/com/tangem/tap/network/Retrofit.kt new file mode 100644 index 0000000000..169baa076b --- /dev/null +++ b/app/src/main/java/com/tangem/tap/network/Retrofit.kt @@ -0,0 +1,49 @@ +package com.tangem.tap.network + +import com.squareup.moshi.FromJson +import com.squareup.moshi.Moshi +import com.squareup.moshi.ToJson +import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory +import com.tangem.wallet.BuildConfig +import okhttp3.Interceptor +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor +import retrofit2.Converter +import retrofit2.Retrofit +import retrofit2.converter.moshi.MoshiConverterFactory +import java.math.BigDecimal + +fun createRetrofitInstance( + baseUrl: String, + interceptors: List = emptyList(), +): Retrofit { + val okHttpBuilder = OkHttpClient.Builder() + interceptors.forEach { okHttpBuilder.addInterceptor(it) } + + if (BuildConfig.DEBUG) okHttpBuilder.addInterceptor(createHttpLoggingInterceptor()) + return Retrofit.Builder() + .baseUrl(baseUrl) + .addConverterFactory(createMoshiConverterFactory()) + .client(okHttpBuilder.build()) + .build() +} + +fun createMoshiConverterFactory(): Converter.Factory = MoshiConverterFactory.create(createMoshi()) + +fun createMoshi(): Moshi = Moshi.Builder() + .add(BigDecimalAdapter) + .add(KotlinJsonAdapterFactory()).build() + +private fun createHttpLoggingInterceptor(): HttpLoggingInterceptor { + val logging = HttpLoggingInterceptor() + logging.level = HttpLoggingInterceptor.Level.BODY + return logging +} + +private object BigDecimalAdapter { + @FromJson + fun fromJson(string: String) = BigDecimal(string) + + @ToJson + fun toJson(value: BigDecimal) = value.toString() +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/coinmarketcap/CoinMarketCapApi.kt b/app/src/main/java/com/tangem/tap/network/coinmarketcap/CoinMarketCapApi.kt new file mode 100644 index 0000000000..47889ff06d --- /dev/null +++ b/app/src/main/java/com/tangem/tap/network/coinmarketcap/CoinMarketCapApi.kt @@ -0,0 +1,43 @@ +package com.tangem.tap.network.coinmarketcap + +import com.tangem.tap.TapConfig +import com.tangem.tap.network.createRetrofitInstance +import okhttp3.Interceptor +import okhttp3.Response +import retrofit2.http.GET +import retrofit2.http.Query + +interface CoinMarketCapApi { + + @GET("v1/tools/price-conversion") + suspend fun getRateInfo( + @Query("amount") amount: Int, + @Query("symbol") cryptoCurrencyName: String, + @Query("convert") fiatCurrencyName: String? = null + ): RateInfoResponse + + @GET("v1/fiat/map") + suspend fun getFiatMap(): FiatMapResponse + + + companion object { + private const val baseUrl = "https://pro-api.coinmarketcap.com/" + + fun create(): CoinMarketCapApi { + return createRetrofitInstance( + baseUrl, + listOf(createCoinMarketRequestInterceptor()), + ).create(CoinMarketCapApi::class.java) + } + } +} + +private fun createCoinMarketRequestInterceptor(): Interceptor { + return object : Interceptor { + override fun intercept(chain: Interceptor.Chain): Response { + val requestBuilder = chain.request().newBuilder() + requestBuilder.addHeader("X-CMC_PRO_API_KEY", TapConfig.coinMarketCapKey) + return chain.proceed(requestBuilder.build()) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/coinmarketcap/CoinMarketCapService.kt b/app/src/main/java/com/tangem/tap/network/coinmarketcap/CoinMarketCapService.kt new file mode 100644 index 0000000000..4dc7931623 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/network/coinmarketcap/CoinMarketCapService.kt @@ -0,0 +1,30 @@ +package com.tangem.tap.network.coinmarketcap + +import com.tangem.commands.common.network.Result +import com.tangem.commands.common.network.performRequest +import com.tangem.tap.common.redux.global.FiatCurrencyName +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import java.math.BigDecimal + +class CoinMarketCapService { + private val api: CoinMarketCapApi by lazy { CoinMarketCapApi.create() } + + suspend fun getRate( + currency: String, fiatCurrency: FiatCurrencyName? = null + ): Result = withContext(Dispatchers.IO) { + val response = performRequest { api.getRateInfo(1, currency, fiatCurrency) } + return@withContext when (response) { + is Result.Success -> Result.Success(response.data.data.getRate()) + is Result.Failure -> response + } + } + + suspend fun getFiatCurrencies(): Result> = withContext(Dispatchers.IO) { + val response = performRequest { api.getFiatMap() } + return@withContext when (response) { + is Result.Success -> Result.Success(response.data.data.sortedBy { it.name }) + is Result.Failure -> response + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/coinmarketcap/Response.kt b/app/src/main/java/com/tangem/tap/network/coinmarketcap/Response.kt new file mode 100644 index 0000000000..7320d9467c --- /dev/null +++ b/app/src/main/java/com/tangem/tap/network/coinmarketcap/Response.kt @@ -0,0 +1,51 @@ +package com.tangem.tap.network.coinmarketcap + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import java.math.BigDecimal + +@JsonClass(generateAdapter = true) +class RateInfoResponse : CoinMarketResponse() + +@JsonClass(generateAdapter = true) +data class RateData( + val quote: Map +) { + fun getRate(): BigDecimal = quote.values.first().price +} + +@JsonClass(generateAdapter = true) +data class CurrencyRate( + val price: BigDecimal +) + + +@JsonClass(generateAdapter = true) +data class Status( + val timestamp: String, + @Json(name = "error_code") + val errorCode: Int, + @Json(name = "error_message") + val errorMessage: String?, + val elapsed: Int, + @Json(name = "credit_count") + val creditCount: Int, + val notice: String? +) + +@JsonClass(generateAdapter = true) +class FiatMapResponse : CoinMarketResponse>() + +@JsonClass(generateAdapter = true) +open class CoinMarketResponse { + lateinit var status: Status + lateinit var data: T +} + +@JsonClass(generateAdapter = true) +data class FiatCurrency( + val id: Int, + val name: String, + val sign: String, + val symbol: String +) diff --git a/app/src/main/java/com/tangem/tap/network/payid/PayIdApi.kt b/app/src/main/java/com/tangem/tap/network/payid/PayIdApi.kt new file mode 100644 index 0000000000..8b00ecefd7 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/network/payid/PayIdApi.kt @@ -0,0 +1,26 @@ +package com.tangem.tap.network.payid + +import retrofit2.http.GET +import retrofit2.http.POST +import retrofit2.http.Query + +interface PayIdApi { + @GET(API_PAY_ID_TANGEM) + suspend fun getPayId( + @Query("cid") cardId: String, + @Query("key") publicKey: String + ): PayIdResponse + + @POST(API_PAY_ID_TANGEM) + suspend fun setPayId( + @Query("cid") cardId: String, + @Query("key") publicKey: String, + @Query("payid") payId: String, + @Query("address") address: String, + @Query("network") network: String + ): SetPayIdResponse + + companion object { + const val API_PAY_ID_TANGEM = "https://payid.tangem.com/" + } +} diff --git a/app/src/main/java/com/tangem/tap/network/payid/PayIdService.kt b/app/src/main/java/com/tangem/tap/network/payid/PayIdService.kt new file mode 100644 index 0000000000..2e691228ea --- /dev/null +++ b/app/src/main/java/com/tangem/tap/network/payid/PayIdService.kt @@ -0,0 +1,38 @@ +package com.tangem.tap.network.payid + +import com.squareup.moshi.JsonClass +import com.tangem.commands.common.network.Result +import com.tangem.commands.common.network.performRequest +import com.tangem.tap.network.createRetrofitInstance +import retrofit2.Retrofit + +class PayIdService { + + private val payIdApi: PayIdApi by lazy { + provideRetrofit() + .create(PayIdApi::class.java) + } + + suspend fun getPayId(cardId: String, publicKey: String): Result { + return performRequest { payIdApi.getPayId(cardId, publicKey) } + } + + suspend fun setPayId( + cardId: String, publicKey: String, payId: String, address: String, network: String + ): Result { + return performRequest { payIdApi.setPayId(cardId, publicKey, payId, address, network) } + } + + private fun provideRetrofit(): Retrofit = createRetrofitInstance("https://tangem.com/") + +} + +@JsonClass(generateAdapter = true) +data class PayIdResponse( + val payId: String +) + +@JsonClass(generateAdapter = true) +data class SetPayIdResponse( + val success: Boolean +) diff --git a/app/src/main/java/com/tangem/tap/network/payid/PayIdVerifyApi.kt b/app/src/main/java/com/tangem/tap/network/payid/PayIdVerifyApi.kt new file mode 100644 index 0000000000..e026dd8b11 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/network/payid/PayIdVerifyApi.kt @@ -0,0 +1,44 @@ +package com.tangem.tap.network.payid + +import com.squareup.moshi.JsonClass +import retrofit2.http.GET +import retrofit2.http.Header +import retrofit2.http.Path + +/** +[REDACTED_AUTHOR] + */ +interface PayIdVerifyApi { + @GET("{user}") + suspend fun verifyAddress( + @Path("user") user: String, + @Header("Accept") acceptNetworkHeader: String, + @Header("PayID-Version") payIdVersion: String = "1.0" + ): VerifyPayIdResponse +} + + +@JsonClass(generateAdapter = true) +data class VerifyPayIdResponse( + val addresses: List = mutableListOf(), + val payId: String? = null, +) { + fun getAddress(): String? { + return if (addresses.isEmpty()) null + else addresses[0].addressDetails.address + } +} + +@JsonClass(generateAdapter = true) +data class PayIdAddress( + var paymentNetwork: String, + var environment: String, + var addressDetailsType: String, + var addressDetails: PayIdAddressDetails +) + +@JsonClass(generateAdapter = true) +data class PayIdAddressDetails( + var address: String, + var tag: String? = null +) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/payid/PayIdVerifyService.kt b/app/src/main/java/com/tangem/tap/network/payid/PayIdVerifyService.kt new file mode 100644 index 0000000000..109dc5e97f --- /dev/null +++ b/app/src/main/java/com/tangem/tap/network/payid/PayIdVerifyService.kt @@ -0,0 +1,21 @@ +package com.tangem.tap.network.payid + +import com.tangem.commands.common.network.Result +import com.tangem.commands.common.network.performRequest +import com.tangem.tap.network.createRetrofitInstance + +/** +[REDACTED_AUTHOR] + */ +class PayIdVerifyService( + private val baseUrl: String +) { + + private val api = createRetrofitInstance(baseUrl).create(PayIdVerifyApi::class.java) + + suspend fun verifyAddress(user: String, network: String): Result { + return performRequest { api.verifyAddress(user, createNetworkHeader(network)) } + } + + private fun createNetworkHeader(network: String): String = "application/$network-mainnet+json" +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt b/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt new file mode 100644 index 0000000000..fac5d861fc --- /dev/null +++ b/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt @@ -0,0 +1,76 @@ +package com.tangem.tap.persistence + +import android.app.Application +import android.content.Context +import android.content.SharedPreferences +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.Moshi +import com.squareup.moshi.Types +import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory +import com.tangem.tap.common.entities.TapCurrency.Companion.DEFAULT_FIAT_CURRENCY +import com.tangem.tap.common.redux.global.FiatCurrencyName +import com.tangem.tap.network.coinmarketcap.FiatCurrency + + +class PreferencesStorage(applicationContext: Application) { + + private val preferences: SharedPreferences by lazy { + applicationContext.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE) + } + + private val fiatCurrenciesAdapter: JsonAdapter> by lazy { + val moshi = Moshi.Builder() + .add(KotlinJsonAdapterFactory()) + .build() + val type = Types.newParameterizedType(List::class.java, FiatCurrency::class.java) + moshi.adapter(type) + } + + fun getAppCurrency(): FiatCurrencyName { + return preferences.getString(APP_CURRENCY_KEY, DEFAULT_FIAT_CURRENCY) + ?: DEFAULT_FIAT_CURRENCY + } + + fun saveAppCurrency(fiatCurrencyName: FiatCurrencyName) { + return preferences.edit().putString(APP_CURRENCY_KEY, fiatCurrencyName).apply() + } + + fun getFiatCurrencies(): List? { + val json = preferences.getString(FIAT_CURRENCIES_KEY, "") + return if (json.isNullOrBlank()) null else fiatCurrenciesAdapter.fromJson(json) as List + } + + fun saveFiatCurrencies(currencies: List) { + val json: String = fiatCurrenciesAdapter.toJson(currencies) + return preferences.edit().putString(FIAT_CURRENCIES_KEY, json).apply() + } + + fun isFirstLaunch(): Boolean { + val isFirst = !preferences.contains(FIRST_LAUNCH_CHECK_KEY) + if (isFirst) preferences.edit().putInt(FIRST_LAUNCH_CHECK_KEY, System.currentTimeMillis().toInt()).apply() + return isFirst + } + + fun saveScannedCardId(cardId: String) { + val scannedCardsIds: String = restoreScannedCardIds() + if (!scannedCardsIds.contains(cardId)) { + preferences.edit().putString(SCANNED_CARDS_IDS_KEY, "$scannedCardsIds$cardId, ").apply() + } + } + + fun wasCardScannedBefore(cardId: String): Boolean { + return restoreScannedCardIds().contains(cardId) + } + + private fun restoreScannedCardIds(): String = + preferences.getString(SCANNED_CARDS_IDS_KEY, "") ?: "" + + companion object { + private const val PREFERENCES_NAME = "tapPrefs" + private const val APP_CURRENCY_KEY = "appCurrency" + private const val FIAT_CURRENCIES_KEY = "fiatCurrencies" + private const val FIRST_LAUNCH_CHECK_KEY = "firstLaunchCheck" + private const val SCANNED_CARDS_IDS_KEY = "scannedCardIds" + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/ui/activity/GlobalViewModel.kt b/app/src/main/java/com/tangem/ui/activity/GlobalViewModel.kt deleted file mode 100644 index 639a9ac7d4..0000000000 --- a/app/src/main/java/com/tangem/ui/activity/GlobalViewModel.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.tangem.ui.activity - -import androidx.lifecycle.ViewModel -import com.tangem.ui.navigation.NavigationResult - -class GlobalViewModel : ViewModel() { - var navigationResult: NavigationResult? = null -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/ui/activity/MainActivity.kt b/app/src/main/java/com/tangem/ui/activity/MainActivity.kt deleted file mode 100644 index 8c6f685715..0000000000 --- a/app/src/main/java/com/tangem/ui/activity/MainActivity.kt +++ /dev/null @@ -1,92 +0,0 @@ -package com.tangem.ui.activity - -import android.content.Context -import android.content.Intent -import android.content.pm.ActivityInfo -import android.nfc.NfcAdapter -import android.nfc.Tag -import android.os.Bundle -import android.util.Log -import androidx.appcompat.app.AppCompatActivity -import androidx.fragment.app.Fragment -import androidx.lifecycle.ViewModelProviders -import androidx.navigation.findNavController -import com.scottyab.rootbeer.RootBeer -import com.tangem.App -import com.tangem.di.ToastHelper -import com.tangem.tangem_sdk.android.nfc.NfcLifecycleObserver -import com.tangem.tangem_sdk.android.reader.NfcManager -import com.tangem.ui.dialog.RootFoundDialog -import com.tangem.ui.fragment.MainFragment -import com.tangem.wallet.BuildConfig -import com.tangem.wallet.R -import javax.inject.Inject - -class MainActivity : AppCompatActivity(), NfcAdapter.ReaderCallback { - - companion object { - val TAG: String = MainActivity::class.java.simpleName - fun callingIntent(context: Context) = Intent(context, MainActivity::class.java) - } - - @Inject - internal lateinit var toastHelper: ToastHelper - lateinit var viewModel: GlobalViewModel - lateinit var nfcManager: NfcManager - - override fun onNewIntent(intent: Intent?) { - super.onNewIntent(intent) - if (intent != null && (NfcAdapter.ACTION_TECH_DISCOVERED == intent.action || NfcAdapter.ACTION_NDEF_DISCOVERED == intent.action)) { - val tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG) - if (tag != null) { - navigateSafelyToMainFragment() - onTagDiscovered(tag) - } - } - } - - private fun navigateSafelyToMainFragment() { - if (getActiveFragment() is MainFragment) return - - try { - findNavController(R.id.nav_host_fragment).popBackStack() - } catch (e: IllegalArgumentException) { - Log.w(this::class.java.simpleName, e.message) - } catch (e: IllegalStateException) { - Log.w(this::class.java.simpleName, e.message) - } - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_main) - - viewModel = ViewModelProviders.of(this).get(GlobalViewModel::class.java) - - App.toastHelperComponent.inject(this) - - requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT - - nfcManager = NfcManager(this, this) - lifecycle.addObserver(NfcLifecycleObserver(nfcManager)) - - // check if root device - val rootBeer = RootBeer(this) - if (rootBeer.isRootedWithoutBusyBoxCheck && !BuildConfig.DEBUG) - RootFoundDialog().show(supportFragmentManager, RootFoundDialog.TAG) - } - - override fun onTagDiscovered(tag: Tag) { - val activeFragment = getActiveFragment() - if (activeFragment is NfcAdapter.ReaderCallback) { - activeFragment.onTagDiscovered(tag) - } else { - nfcManager.ignoreTag(tag) - } - } - - private fun getActiveFragment(): Fragment? { - return supportFragmentManager.findFragmentById(R.id.nav_host_fragment) - ?.childFragmentManager?.primaryNavigationFragment - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/ui/dialog/NoExtendedLengthSupportDialog.kt b/app/src/main/java/com/tangem/ui/dialog/NoExtendedLengthSupportDialog.kt deleted file mode 100644 index eb3f1c8357..0000000000 --- a/app/src/main/java/com/tangem/ui/dialog/NoExtendedLengthSupportDialog.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.tangem.ui.dialog - -import android.app.AlertDialog -import android.app.Dialog -import android.os.Bundle -import androidx.fragment.app.DialogFragment - -import com.tangem.wallet.R - -class NoExtendedLengthSupportDialog : DialogFragment() { - - companion object { - val TAG: String = NoExtendedLengthSupportDialog::class.java.simpleName - var allReadyShowed = false - var message = ""// = R.string.the_nfc_adapter_length_apdu - } - - override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - return AlertDialog.Builder(activity) - .setIcon(R.drawable.tangem_logo_small_new) - .setTitle(R.string.dialog_warning) - .setMessage(message) - .setPositiveButton(R.string.dialog_btn_got_it) { _, _ -> NoExtendedLengthSupportDialog.allReadyShowed = false } - .create() - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/ui/dialog/PINSwapWarningDialog.java b/app/src/main/java/com/tangem/ui/dialog/PINSwapWarningDialog.java deleted file mode 100644 index 1a28371634..0000000000 --- a/app/src/main/java/com/tangem/ui/dialog/PINSwapWarningDialog.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.tangem.ui.dialog; - -import android.app.AlertDialog; -import android.app.Dialog; -import android.content.DialogInterface; -import android.os.Bundle; - -import androidx.annotation.NonNull; -import androidx.fragment.app.DialogFragment; - -import com.tangem.Constant; -import com.tangem.wallet.R; - -public class PINSwapWarningDialog extends DialogFragment { - public static final String TAG = PINSwapWarningDialog.class.getSimpleName(); - - private String message = ""; - private OnPositiveButton mOnPositiveButton; - - public interface OnPositiveButton { - void onRefresh(); - } - - public void setOnRefreshPage(OnPositiveButton listener) { - mOnPositiveButton = listener; - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - if (getArguments() != null) { - message = getArguments().getString(Constant.EXTRA_MESSAGE); - } - } - - @NonNull - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - return new AlertDialog.Builder(getActivity()) - .setIcon(R.drawable.tangem_logo_small_new) - .setTitle(R.string.dialog_title_money_is_at_risk) - .setMessage(message) - .setCancelable(true) - .setNegativeButton(R.string.general_cancel, (dialog, which) -> dismiss()) - .setPositiveButton(R.string.general_continue, (dialog, whichButton) -> mOnPositiveButton.onRefresh()) - .create(); - } - - @Override - public void onCancel(@NonNull DialogInterface dialog) { - super.onCancel(dialog); - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/ui/dialog/RootFoundDialog.kt b/app/src/main/java/com/tangem/ui/dialog/RootFoundDialog.kt deleted file mode 100644 index d8650844e3..0000000000 --- a/app/src/main/java/com/tangem/ui/dialog/RootFoundDialog.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.tangem.ui.dialog - -import android.app.AlertDialog -import android.app.Dialog -import android.os.Bundle -import androidx.fragment.app.DialogFragment - -import com.tangem.wallet.R - -class RootFoundDialog : DialogFragment() { - - companion object { - val TAG: String = RootFoundDialog::class.java.simpleName - } - - override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - return AlertDialog.Builder(activity) - .setIcon(R.drawable.tangem_logo_small_new) - .setTitle(R.string.dialog_device_is_rooted) - .setCancelable(false) - .setPositiveButton(R.string.dialog_btn_got_it, null) - .create() - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/ui/dialog/ShowQRCodeDialog.java b/app/src/main/java/com/tangem/ui/dialog/ShowQRCodeDialog.java deleted file mode 100644 index 06e8627054..0000000000 --- a/app/src/main/java/com/tangem/ui/dialog/ShowQRCodeDialog.java +++ /dev/null @@ -1,80 +0,0 @@ -package com.tangem.ui.dialog; - -import android.app.AlertDialog; -import android.app.Dialog; -import android.content.DialogInterface; -import android.graphics.Bitmap; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.WindowManager; -import android.widget.ImageView; -import android.widget.TextView; - -import androidx.appcompat.app.AppCompatActivity; -import androidx.fragment.app.DialogFragment; - -import com.tangem.util.UtilHelper; -import com.tangem.wallet.R; - -/** - * Created by dvol on 06.03.2018. - */ -public class ShowQRCodeDialog extends DialogFragment { - public static final String TAG = ShowQRCodeDialog.class.getSimpleName(); - - private ImageView ivQR; - private TextView tvQRaddress; - private Bitmap bmQR; - private String addr; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - - LayoutInflater inflater = getActivity().getLayoutInflater(); - - // Inflate and set the layout for the dialog - // Pass null as the parent view because its going in the dialog layout - View v = inflater.inflate(R.layout.dialog_show_qr, null); - ivQR = v.findViewById(R.id.ivQR); - ivQR.setImageBitmap(bmQR); - tvQRaddress = v.findViewById(R.id.tvQRaddress); - tvQRaddress.setText(addr); - - return new AlertDialog.Builder(getActivity()) - .setIcon(R.drawable.tangem_logo_small_new) - .setTitle(R.string.loaded_wallet_dialog_show_qr) - .setView(v) - .setPositiveButton(R.string.general_ok, (dialog, which)->dismiss() ) - .create(); - } - - @Override - public void onCancel(DialogInterface dialog) { - super.onCancel(dialog); - } - - public void setup(String content) { - try { - bmQR = UtilHelper.INSTANCE.generateQrCode(content); - addr = content; - } catch (Exception e) { - e.printStackTrace(); - } - } - - public static void show(final AppCompatActivity activity, final String content) { - activity.runOnUiThread(() -> { - ShowQRCodeDialog instance = new ShowQRCodeDialog(); - instance.setup(content); - instance.show(activity.getSupportFragmentManager(), TAG); - }); - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/ui/dialog/WaitSecurityDelayDialog.java b/app/src/main/java/com/tangem/ui/dialog/WaitSecurityDelayDialog.java deleted file mode 100644 index 6e428897c4..0000000000 --- a/app/src/main/java/com/tangem/ui/dialog/WaitSecurityDelayDialog.java +++ /dev/null @@ -1,169 +0,0 @@ -package com.tangem.ui.dialog; - -import android.app.Activity; -import android.app.AlertDialog; -import android.app.Dialog; -import android.content.DialogInterface; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.WindowManager; -import android.widget.ProgressBar; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.DialogFragment; -import androidx.fragment.app.FragmentActivity; - -import com.tangem.tangem_card.util.Log; -import com.tangem.wallet.R; - -import java.util.Timer; -import java.util.TimerTask; - -/** - * Created by dvol on 06.03.2018. - */ -public class WaitSecurityDelayDialog extends DialogFragment { - public static final String TAG = WaitSecurityDelayDialog.class.getSimpleName(); - - private ProgressBar progressBar; - private int msTimeout = 60000, msProgress = 0; - private Timer timer; - - private static Timer timerToShowDelayDialog = null; - private static WaitSecurityDelayDialog instance = null; - - private final static int minRemainingDelayToShowDialog = 1000; - private final static int delayBeforeShowDialog = 5000; - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - LayoutInflater inflater = getActivity().getLayoutInflater(); - - View v = inflater.inflate(R.layout.dialog_wait_pin2, null); - - progressBar = v.findViewById(R.id.progressBar); - progressBar.setMax(msTimeout); - progressBar.setProgress(msProgress); - - timer = new Timer(); - timer.scheduleAtFixedRate(new TimerTask() { - @Override - public void run() { - progressBar.post(() -> { - int progress = WaitSecurityDelayDialog.this.progressBar.getProgress(); - if (progress < WaitSecurityDelayDialog.this.progressBar.getMax()) { - WaitSecurityDelayDialog.this.progressBar.setProgress(progress + 1000); - } - }); - } - }, 1000, 1000); - return new AlertDialog.Builder(getActivity()) - .setIcon(R.drawable.tangem_logo_small_new) - .setTitle(R.string.dialog_security_delay) - .setView(v) - .setCancelable(false) - .create(); - } - - @Override - public void onCancel(@NonNull DialogInterface dialog) { - super.onCancel(dialog); - } - - public static void onReadBeforeRequest(final FragmentActivity activity, final int timeout) { - if (activity == null) return; - - Log.e(TAG, "onReadBeforeRequest callback(" + timeout + ")"); - activity.runOnUiThread(() -> { - if (timerToShowDelayDialog != null || timeout < delayBeforeShowDialog + minRemainingDelayToShowDialog) - return; - timerToShowDelayDialog = new Timer(); - timerToShowDelayDialog.schedule(new TimerTask() { - @Override - public void run() { - if (WaitSecurityDelayDialog.instance != null) return; - instance = new WaitSecurityDelayDialog(); - instance.setup(timeout, delayBeforeShowDialog); - instance.setCancelable(false); - activity.getSupportFragmentManager().beginTransaction() - .add(instance, TAG).commitAllowingStateLoss(); - } - }, delayBeforeShowDialog); - }); - } - - public static void onReadAfterRequest(final Activity activity) { - if (timerToShowDelayDialog == null) return; - timerToShowDelayDialog.cancel(); - timerToShowDelayDialog = null; - } - - public static void onReadWait(final FragmentActivity activity, final int msec) { - Log.e(TAG, "onReadWait callback(" + msec + ")"); - if (timerToShowDelayDialog != null) { - timerToShowDelayDialog.cancel(); - timerToShowDelayDialog = null; - } - - if (msec == 0 && instance != null) { - try { - instance.dismissAllowingStateLoss(); - instance = null; - } catch (Exception e) { - e.printStackTrace(); - } - } - - if (activity == null) return; - - activity.runOnUiThread(() -> { - Log.e(TAG, "onReadWait on ui thread(" + msec + ")"); - - if (instance == null) { - if (msec > delayBeforeShowDialog + minRemainingDelayToShowDialog) { - instance = new WaitSecurityDelayDialog(); - // 1000ms - card delay notification interval - instance.setup(msec + 1000, 1000); - instance.setCancelable(false); - activity.getSupportFragmentManager().beginTransaction() - .add(instance, TAG).commitAllowingStateLoss(); - } - } else - instance.setRemainingTimeout(msec); - }); - } - - private void setup(int msTimeout, int msProgress) { - this.msTimeout = msTimeout; - this.msProgress = msProgress; - } - - private void setRemainingTimeout(final int msec) { - if (progressBar != null) { - progressBar.post(() -> { - int progress = WaitSecurityDelayDialog.this.progressBar.getProgress(); - if (timer != null) { - // we get delay latency from card for first time - don't change progress by timer, only by card answer - progressBar.setMax(progress + msec); - timer.cancel(); - timer = null; - } else { - int newProgress = progressBar.getMax() - msec; - if (newProgress > progress) - progressBar.setProgress(newProgress); - else - progressBar.setMax(progress + msec); - } - }); - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/ui/dialog/WaitSecurityDelayDialogNew.kt b/app/src/main/java/com/tangem/ui/dialog/WaitSecurityDelayDialogNew.kt deleted file mode 100644 index c55657a9d4..0000000000 --- a/app/src/main/java/com/tangem/ui/dialog/WaitSecurityDelayDialogNew.kt +++ /dev/null @@ -1,156 +0,0 @@ -package com.tangem.ui.dialog - -import android.annotation.SuppressLint -import android.app.AlertDialog -import android.app.Dialog -import android.os.Bundle -import android.widget.ProgressBar -import androidx.appcompat.app.AppCompatDialogFragment -import com.tangem.ui.event.ReadAfterRequest -import com.tangem.ui.event.ReadBeforeRequest -import com.tangem.ui.event.ReadWait -import com.tangem.util.LOG -import com.tangem.wallet.R -import org.greenrobot.eventbus.EventBus -import org.greenrobot.eventbus.Subscribe -import java.util.* - -class WaitSecurityDelayDialogNew : AppCompatDialogFragment() { - - companion object { - val TAG: String = WaitSecurityDelayDialogNew::class.java.simpleName - - private const val MIN_REMAINING_DELAY_TO_SHOW_DIALOG = 1000 - private const val DELAY_BEFORE_SHOW_DIALOG = 5000 - } - - private lateinit var pb: ProgressBar - - private var msTimeout = 60000 - private var msProgress = 0 - private var timer: Timer? = null - private var timerToShowDelayDialog: Timer? = null - - @SuppressLint("InflateParams") - override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - val inflater = activity!!.layoutInflater - val v = inflater.inflate(R.layout.dialog_wait_pin2, null) - - pb = v.findViewById(R.id.progressBar) - - pb.max = msTimeout - pb.progress = msProgress - - timer = Timer() - timer!!.scheduleAtFixedRate(object : TimerTask() { - override fun run() { - pb.post { - val progress = pb.progress - if (progress < pb.max) - pb.progress = progress + 1000 - } - } - }, 1000, 1000) - - return AlertDialog.Builder(activity) - .setIcon(R.drawable.tangem_logo_small_new) - .setTitle(R.string.dialog_security_delay) - .setView(v) - .setCancelable(false) - .create() - } - - override fun onStart() { - super.onStart() - EventBus.getDefault().register(this) - } - - override fun onStop() { - super.onStop() - EventBus.getDefault().unregister(this) - } - - @Subscribe - fun readBeforeRequest(readBeforeRequest: ReadBeforeRequest) { - LOG.i(TAG, "readBeforeRequest 111") - - if (timerToShowDelayDialog != null || readBeforeRequest.timeout!! < DELAY_BEFORE_SHOW_DIALOG + MIN_REMAINING_DELAY_TO_SHOW_DIALOG) - return - - timerToShowDelayDialog = Timer() - timerToShowDelayDialog!!.schedule(object : TimerTask() { - override fun run() { - setup(readBeforeRequest.timeout!!, DELAY_BEFORE_SHOW_DIALOG) - isCancelable = false - -// if (!isAdded) - show(activity!!.supportFragmentManager, TAG) - -// if (isHidden) -// activity?.supportFragmentManager?.let { show(it, TAG) } - } - }, DELAY_BEFORE_SHOW_DIALOG.toLong()) - } - - @Subscribe - fun readAfterRequest(readAfterRequest: ReadAfterRequest) { - LOG.i(TAG, "readAfterRequest 222") - - if (timerToShowDelayDialog == null) - return - - timerToShowDelayDialog!!.cancel() - timerToShowDelayDialog = null - } - - @Subscribe - fun readWait(readWait: ReadWait) { - LOG.i(TAG, "readWait 333") - - if (timerToShowDelayDialog != null) { - timerToShowDelayDialog!!.cancel() - timerToShowDelayDialog = null - } - - if (readWait.msec == 0) { - dismiss() - return - } - - if (readWait.msec!! > MIN_REMAINING_DELAY_TO_SHOW_DIALOG) { - // 1000ms - card delay notification interval - setup(readWait.msec!! + 1000, 1000) - isCancelable = false - - if (isHidden) - activity?.supportFragmentManager?.let { show(it, TAG) } - - } else - setRemainingTimeout(readWait.msec!!) - } - - private fun setup(msTimeout: Int, msProgress: Int) { - this.msTimeout = msTimeout - this.msProgress = msProgress - } - - private fun setRemainingTimeout(msec: Int) { - pb.post { - val progress = pb.progress - if (timer != null) { - - // we get delay latency from card for first time - don't change progress by timer, only by card answer - pb.max = progress + msec - timer!!.cancel() - timer = null - } else { - val newProgress = pb.max - msec - if (pb.max > progress) - pb.progress = newProgress - else - pb.max = progress + msec - } - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/ui/event/DeletingWalletFinish.kt b/app/src/main/java/com/tangem/ui/event/DeletingWalletFinish.kt deleted file mode 100644 index c9683549d5..0000000000 --- a/app/src/main/java/com/tangem/ui/event/DeletingWalletFinish.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.tangem.ui.event - -class DeletingWalletFinish \ No newline at end of file diff --git a/app/src/main/java/com/tangem/ui/event/ReadAfterRequest.kt b/app/src/main/java/com/tangem/ui/event/ReadAfterRequest.kt deleted file mode 100644 index 8c61063c75..0000000000 --- a/app/src/main/java/com/tangem/ui/event/ReadAfterRequest.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.tangem.ui.event - -class ReadAfterRequest \ No newline at end of file diff --git a/app/src/main/java/com/tangem/ui/event/ReadBeforeRequest.kt b/app/src/main/java/com/tangem/ui/event/ReadBeforeRequest.kt deleted file mode 100644 index 203b0e1edb..0000000000 --- a/app/src/main/java/com/tangem/ui/event/ReadBeforeRequest.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.tangem.ui.event - -class ReadBeforeRequest { - var timeout: Int? = null -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/ui/event/ReadWait.kt b/app/src/main/java/com/tangem/ui/event/ReadWait.kt deleted file mode 100644 index cd4e013295..0000000000 --- a/app/src/main/java/com/tangem/ui/event/ReadWait.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.tangem.ui.event - -class ReadWait { - var msec: Int? = null -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/ui/event/TransactionFinishWithError.kt b/app/src/main/java/com/tangem/ui/event/TransactionFinishWithError.kt deleted file mode 100644 index fd21ee0083..0000000000 --- a/app/src/main/java/com/tangem/ui/event/TransactionFinishWithError.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.tangem.ui.event - -class TransactionFinishWithError { - var message: String? = null -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/ui/event/TransactionFinishWithSuccess.kt b/app/src/main/java/com/tangem/ui/event/TransactionFinishWithSuccess.kt deleted file mode 100644 index 00d24a8b33..0000000000 --- a/app/src/main/java/com/tangem/ui/event/TransactionFinishWithSuccess.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.tangem.ui.event - -class TransactionFinishWithSuccess { - var message: String? = null -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/ui/fragment/BaseFragment.kt b/app/src/main/java/com/tangem/ui/fragment/BaseFragment.kt deleted file mode 100644 index ed903c4d47..0000000000 --- a/app/src/main/java/com/tangem/ui/fragment/BaseFragment.kt +++ /dev/null @@ -1,110 +0,0 @@ -package com.tangem.ui.fragment - -import android.os.Bundle -import android.util.Log -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.annotation.IdRes -import androidx.fragment.app.Fragment -import androidx.navigation.fragment.NavHostFragment.findNavController -import com.tangem.ui.activity.MainActivity -import com.tangem.ui.navigation.NavigationResult -import com.tangem.ui.navigation.NavigationResultListener - -abstract class BaseFragment : Fragment() { - - protected val requestCode: String - get() = arguments?.getString(ARGUMENT_NAVIGATION_REQUEST_CODE, REQUEST_CODE_NOT_SET) - ?: REQUEST_CODE_NOT_SET - protected val navigatedBack: Boolean - get() = (activity as? MainActivity)?.viewModel?.navigationResult != null - - protected abstract val layoutId: Int - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - Log.d("LIFECYCLE", "onCreate: ${this::class.java.simpleName}") - } - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(layoutId, container, false) - } - - override fun onStart() { - super.onStart() - Log.d("LIFECYCLE", "onStart: ${this::class.java.simpleName}") - if (this is NavigationResultListener) { - val navigationResult = (activity as? MainActivity)?.viewModel?.navigationResult - navigationResult?.run { - onNavigationResult(this.requestCode, this.resultCode, this.data) - (activity as? MainActivity)?.viewModel?.navigationResult = null - } - } - } - - override fun onPause() { - super.onPause() - Log.d("LIFECYCLE", "onPause: ${this::class.java.simpleName}") - } - - override fun onStop() { - super.onStop() - Log.d("LIFECYCLE", "onStop: ${this::class.java.simpleName}") - } - - override fun onDestroy() { - super.onDestroy() - Log.d("LIFECYCLE", "onDestroy: ${this::class.java.simpleName}") - } - - protected fun navigateUp(@IdRes destination: Int = DESTINATION_NOT_SET) { - try { - if (destination == DESTINATION_NOT_SET) { - findNavController(this).popBackStack() - } else { - findNavController(this).popBackStack(destination, false) - } - } catch (e: IllegalArgumentException) { - Log.w(this::class.java.simpleName, e.message) - } catch (e: IllegalStateException) { - Log.w(this::class.java.simpleName, e.message) - } - } - - protected fun navigateForResult(requestCode: String, @IdRes destination: Int, data: Bundle? = null) { - val argumentsWithRequestCode = (data ?: Bundle()).apply { - putString(ARGUMENT_NAVIGATION_REQUEST_CODE, requestCode) - } - navigateToDestination(destination, argumentsWithRequestCode) - } - - protected fun navigateToDestination(@IdRes destination: Int, data: Bundle? = null) { - try { - findNavController(this).navigate(destination, data) - } catch (e: IllegalArgumentException) { - Log.w(this::class.java.simpleName, e.message) - } catch (e: IllegalStateException) { - Log.w(this::class.java.simpleName, e.message) - } - } - - protected fun navigateBackWithResult(resultCode: Int, data: Bundle? = null, - @IdRes destination: Int = DESTINATION_NOT_SET) { - try { - (requireActivity() as MainActivity).viewModel.navigationResult = - NavigationResult(requestCode, resultCode, data) - return navigateUp(destination) - } catch (e: IllegalArgumentException) { - Log.w(this::class.java.simpleName, e.message) - } catch (e: IllegalStateException) { - Log.w(this::class.java.simpleName, e.message) - } - } - - companion object { - private const val ARGUMENT_NAVIGATION_REQUEST_CODE = "NAVIGATION_REQUEST_CODE" - private const val DESTINATION_NOT_SET = -1 - private const val REQUEST_CODE_NOT_SET = "REQUEST_CODE_NOT_SET" - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/ui/fragment/LogoFragment.kt b/app/src/main/java/com/tangem/ui/fragment/LogoFragment.kt deleted file mode 100644 index 080f917a41..0000000000 --- a/app/src/main/java/com/tangem/ui/fragment/LogoFragment.kt +++ /dev/null @@ -1,63 +0,0 @@ -package com.tangem.ui.fragment - -import android.content.Context -import android.content.Intent -import android.os.Bundle -import android.view.View -import com.tangem.Constant -import com.tangem.wallet.BuildConfig -import com.tangem.wallet.R -import com.tangem.wallet.TangemContext -import kotlinx.android.synthetic.main.fragment_logo.* - -class LogoFragment : BaseFragment() { - companion object { - fun callingIntent(context: Context, autoHide: Boolean): Intent { - val intent = Intent(context, LogoFragment::class.java) - intent.putExtra(Constant.EXTRA_AUTO_HIDE, autoHide) - return intent - } - } - - override val layoutId = R.layout.fragment_logo - - private val hideRunnable = Runnable { this.hide() } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - clLogoContainer.setOnClickListener { hide() } - // set beta version name - if (BuildConfig.DEBUG) - tvAppVersion.text = String.format(getString(R.string.splash_version_name_debug), BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE) - else - tvAppVersion.text = String.format(getString(R.string.splash_version_name_release), BuildConfig.VERSION_NAME) - - // set flavor app name - when (BuildConfig.FLAVOR) { - Constant.FLAVOR_TANGEM_CARDANO -> { - tvExtension.visibility = View.VISIBLE - tvExtension.text = getString(R.string.splash_cardano) - } - else -> { - tvExtension.visibility = View.GONE - } - } - - if (arguments?.getBoolean(Constant.EXTRA_AUTO_HIDE, true) != false) - ivLogo.postDelayed(hideRunnable, Constant.MILLIS_AUTO_HIDE.toLong()) - } - - private fun hide() { - when (BuildConfig.FLAVOR) { - Constant.FLAVOR_TANGEM_CARDANO -> { - navigateToDestination(R.id.action_logoFragment_to_prepareTransactionFragment, - Bundle().apply { TangemContext().saveToBundle(this) }) - } - else -> { - navigateToDestination(R.id.action_logoFragment_to_main) - } - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/ui/fragment/MainFragment.kt b/app/src/main/java/com/tangem/ui/fragment/MainFragment.kt deleted file mode 100644 index bd386f97c4..0000000000 --- a/app/src/main/java/com/tangem/ui/fragment/MainFragment.kt +++ /dev/null @@ -1,477 +0,0 @@ -package com.tangem.ui.fragment - -import android.app.Activity -import android.app.AlertDialog -import android.content.Intent -import android.net.Uri -import android.nfc.NfcAdapter -import android.nfc.Tag -import android.nfc.tech.IsoDep -import android.nfc.tech.NfcV -import android.os.Bundle -import android.text.Spannable -import android.text.SpannableString -import android.text.style.ForegroundColorSpan -import android.util.Log -import android.view.* -import android.widget.PopupMenu -import android.widget.TextView -import android.widget.Toast -import androidx.appcompat.app.AppCompatActivity -import androidx.core.os.bundleOf -import androidx.lifecycle.ViewModelProviders -import com.google.firebase.analytics.FirebaseAnalytics -import com.google.firebase.crashlytics.FirebaseCrashlytics -import com.google.firebase.perf.FirebasePerformance -import com.google.firebase.perf.metrics.Trace -import com.tangem.App -import com.tangem.Constant -import com.tangem.data.Blockchain -import com.tangem.data.Logger -import com.tangem.tangem_card.data.TangemCard -import com.tangem.tangem_card.reader.CardProtocol -import com.tangem.tangem_card.reader.TLV -import com.tangem.tangem_card.reader.TLVException -import com.tangem.tangem_card.reader.TLVList -import com.tangem.tangem_card.tasks.CustomReadCardTask -import com.tangem.tangem_card.tasks.ReadCardInfoTask -import com.tangem.tangem_sdk.android.data.PINStorage -import com.tangem.tangem_sdk.android.nfc.NfcDeviceAntennaLocation -import com.tangem.tangem_sdk.android.reader.NfcReader -import com.tangem.tangem_sdk.android.reader.NfcVReader -import com.tangem.tangem_sdk.android.reader.ReadResult -import com.tangem.tangem_sdk.android.reader.ReadSlixTagTask -import com.tangem.tangem_sdk.data.EXTRA_TANGEM_CARD -import com.tangem.tangem_sdk.data.EXTRA_TANGEM_CARD_UID -import com.tangem.tangem_sdk.data.loadFromBundle -import com.tangem.tangem_sdk.data.saveToBundle -import com.tangem.ui.activity.MainActivity -import com.tangem.ui.dialog.NoExtendedLengthSupportDialog -import com.tangem.ui.dialog.WaitSecurityDelayDialog -import com.tangem.ui.fragment.pin.PinRequestFragment -import com.tangem.ui.navigation.NavigationResultListener -import com.tangem.util.* -import com.tangem.util.extensions.colorFrom -import com.tangem.wallet.BuildConfig -import com.tangem.wallet.CoinEngineFactory -import com.tangem.wallet.R -import com.tangem.wallet.TangemContext -import com.tangem.wallet.xlmTag.XlmTagEngine -import kotlinx.android.synthetic.main.fragment_main.* -import kotlinx.android.synthetic.main.layout_touch_card.* -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.launch -import java.io.File -import kotlin.coroutines.CoroutineContext - -class MainFragment : BaseFragment(), NavigationResultListener, NfcAdapter.ReaderCallback, - CardProtocol.Notifications, androidx.appcompat.widget.PopupMenu.OnMenuItemClickListener, - PopupMenu.OnMenuItemClickListener { - - companion object { - fun newInstance() = MainFragment() - val TAG: String = MainFragment::class.java.simpleName - const val CARD_SHOP_URI = "https://shop.tangem.com/?afmc=1i&utm_campaign=1i&utm_source=leaddyno&utm_medium=affiliate" - } - - override val layoutId = R.layout.fragment_main - private lateinit var viewModel: MainViewModel - private lateinit var nfcDeviceAntenna: NfcDeviceAntennaLocation - private var tapTimerTrace: Trace? = null - private var unsuccessReadCount = 0 - private var task: CustomReadCardTask? = null - private var lastTag: Tag? = null - private var zipFile: File? = null - private var unknownBlockchain = false - - private val parentJob = Job() - private val coroutineContext: CoroutineContext - get() = parentJob + Dispatchers.IO - private val scope = CoroutineScope(coroutineContext) - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - setHasOptionsMenu(true) - return super.onCreateView(inflater, container, savedInstanceState) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - rippleBackgroundNfc.startRippleAnimation() - -// navigateToDestination(R.id.action_main_to_emptyIdFragment) - // init NFC Antenna - nfcDeviceAntenna = NfcDeviceAntennaLocation(requireContext(), ivHandCardHorizontal, ivHandCardVertical, llHand, llNfc) - nfcDeviceAntenna.init() - - // set phone name - if (nfcDeviceAntenna.fullName != "") - tvNFCHint.text = String.format(getString(R.string.main_screen_scan_card), nfcDeviceAntenna.fullName) - else - tvNFCHint.text = String.format(getString(R.string.main_screen_scan_card), getString(R.string.main_screen_phone)) - - // set listeners -// ivMenu.setOnClickListener { showMenu(it) } - - val toolbar = tb_main - (activity as? AppCompatActivity)?.setSupportActionBar(toolbar) - (activity as? AppCompatActivity)?.supportActionBar?.setDisplayShowTitleEnabled(false) - - - setCardStoreLink() - - FirebaseAnalytics.getInstance(requireActivity()) - .logEvent(AnalyticsEvent.READY_TO_SCAN.event, bundleOf()) - } - - private fun setCardStoreLink() { - val text = getString(R.string.main_screen_visit_store, getString(R.string.main_screen_store_address)) - val spannable = SpannableString(text) - spannable.setSpan( - ForegroundColorSpan(requireContext().colorFrom(R.color.colorAccent)), - text.indexOf(getString(R.string.main_screen_store_address)), - text.length, - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) - - tvBuyCards?.setText(spannable, TextView.BufferType.SPANNABLE) - llShoppingView?.setOnClickListener { - val uri = Uri.parse(CARD_SHOP_URI) - val intent = Intent(Intent.ACTION_VIEW, uri) - startActivity(intent) - } - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java) -// -// // show snackbar about new version app -// viewModel.getVersionName().observe(this, Observer { text -> -// (activity as MainActivity).toastHelper.showSnackbarUpdateVersion(requireContext(), cl, text) -// }) - - val intent = activity?.intent - if (intent != null && (NfcAdapter.ACTION_TECH_DISCOVERED == intent.action || NfcAdapter.ACTION_NDEF_DISCOVERED == intent.action)) { - val tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG) - onTagDiscovered(tag) - } - } - - override fun onResume() { - tapTimerTrace = FirebasePerformance.getInstance() - .newTrace(AnalyticsEvent.CARD_TAP_USER_TIMER.event) - tapTimerTrace?.start() - super.onResume() - nfcDeviceAntenna.animate() - } - - override fun onPause() { - task?.cancel(true) - super.onPause() - } - - override fun onStop() { - task?.cancel(true) - tapTimerTrace = null - super.onStop() - } - - override fun onTagDiscovered(tag: Tag) { - if (unknownBlockchain) { - (activity as MainActivity).nfcManager.ignoreTag(tag) - return - } - - NfcV.get(tag)?.let { onNfcVDiscovered(it, tag.id) } - - try { - // get IsoDep handle and run cardReader thread - val isoDep = IsoDep.get(tag) - if (unsuccessReadCount < 2) - isoDep.timeout = 2000 + 5000 * unsuccessReadCount - else - isoDep.timeout = 90000 - - lastTag = tag - - val terminalKeys = viewModel.getTerminalKeys() - PINStorage.setTerminalPrivateKey(terminalKeys[Constant.TERMINAL_PRIVATE_KEY]) - PINStorage.setTerminalPublicKey(terminalKeys[Constant.TERMINAL_PUBLIC_KEY]) - - task = ReadCardInfoTask(NfcReader((activity as MainActivity).nfcManager, isoDep), - App.localStorage, App.pinStorage, this) - task?.start() - } catch (e: Exception) { - e.printStackTrace() - (activity as MainActivity).nfcManager.notifyReadResult(false) - } - } - - - private fun onNfcVDiscovered(nfcV: NfcV, uid: ByteArray) { - scope.launch { - when (val readResult = ReadSlixTagTask(NfcVReader(nfcV)).read()) { - is ReadResult.Failure -> (activity as MainActivity).nfcManager.notifyReadResult(false) - is ReadResult.Success -> { - try { - val tlvs = readResult.tlvs - val cardDataTlv = TLVList.fromBytes((tlvs.getTLV(TLV.Tag.TAG_CardData)).Value) - Log.v(TAG, "\n" + tlvs.getParsedTLVs("")) - val card = TangemCard(uid.toString()) - card.batch = cardDataTlv.getTLV(TLV.Tag.TAG_Batch).asHexString - card.setIssuer(cardDataTlv.getTLV(TLV.Tag.TAG_Issuer_ID).Value.toString(), null) - card.blockchainID = Blockchain.StellarTag.id - card.walletPublicKey = tlvs.getTLV(TLV.Tag.TAG_Wallet_PublicKey).Value - card.status = TangemCard.Status.Loaded - card.tagSignature = tlvs.getTLV(TLV.Tag.TAG_Signature).Value - - val ctx = TangemContext(card) - val engineCoin = XlmTagEngine(ctx) - engineCoin.defineWallet() - launch(Dispatchers.Main) { - - val bundle = Bundle() - bundle.putParcelable(Constant.EXTRA_LAST_DISCOVERED_TAG, lastTag) - ctx.saveToBundle(bundle) - navigateForResult(Constant.REQUEST_CODE_SHOW_CARD_ACTIVITY, - R.id.action_main_to_tagFragment, bundle) - } - } catch (e: TLVException) { - e.printStackTrace() - Log.v(TAG, e.message) - } - } - } - } - } - - override fun onReadStart(cardProtocol: CardProtocol) { - rlProgressBar?.post { rlProgressBar?.visibility = View.VISIBLE } - } - - override fun onReadProgress(protocol: CardProtocol, progress: Int) { - - } - - override fun onReadFinish(cardProtocol: CardProtocol?) { - tapTimerTrace?.stop() - task = null - if (cardProtocol != null) { - if (cardProtocol.error == null) { - (activity as MainActivity).nfcManager.notifyReadResult(true) - rlProgressBar?.post { - rlProgressBar?.visibility = View.GONE - - // TODO - ??? remove save and load??? - val cardInfo = Bundle() - val bCard = Bundle() - - cardInfo.putString(EXTRA_TANGEM_CARD_UID, cardProtocol.card.uid) - cardInfo.putBundle(EXTRA_TANGEM_CARD, bCard) - cardProtocol.card.saveToBundle(bCard) - - val uid = cardInfo.getString(EXTRA_TANGEM_CARD_UID) - val card = TangemCard(uid) - cardInfo.getBundle(EXTRA_TANGEM_CARD)?.let { card.loadFromBundle(it) } - - val terminalKeys = viewModel.getTerminalKeys() - card.terminalPrivateKey = terminalKeys[Constant.TERMINAL_PRIVATE_KEY] - card.terminalPublicKey = terminalKeys[Constant.TERMINAL_PUBLIC_KEY] - - val ctx = TangemContext(card) - - FirebaseAnalytics.getInstance(requireActivity()) - .logEvent(AnalyticsEvent.CARD_IS_SCANNED.event, Analytics.setCardData(ctx)) - - when { - card.status == TangemCard.Status.Loaded -> lastTag?.let { - val engineCoin = CoinEngineFactory.create(ctx) - if (engineCoin != null) { - if (card.isIDCard) { - val bundle = Bundle() - bundle.putParcelable(Constant.EXTRA_LAST_DISCOVERED_TAG, lastTag) - ctx.saveToBundle(bundle) - navigateToDestination(R.id.action_main_to_idFragment, bundle) - } else { - engineCoin.defineWallet() - val bundle = Bundle() - bundle.putParcelable(Constant.EXTRA_LAST_DISCOVERED_TAG, lastTag) - ctx.saveToBundle(bundle) - navigateForResult(Constant.REQUEST_CODE_SHOW_CARD_ACTIVITY, - R.id.action_main_to_loadedWalletFragment, bundle) - } - } else { - showUnkownBlockchainWarning() - } - } - card.status == TangemCard.Status.Empty -> { - val engineCoin = CoinEngineFactory.create(ctx) - if (engineCoin != null) { - val bundle = Bundle().apply { ctx.saveToBundle(this) } - navigateToDestination(R.id.action_main_to_emptyWalletFragment, bundle) - } else { - showUnkownBlockchainWarning() - } - } - card.status == TangemCard.Status.Purged -> Toast.makeText(context, R.string.main_screen_erased_wallet, Toast.LENGTH_SHORT).show() - card.status == TangemCard.Status.NotPersonalized -> Toast.makeText(context, R.string.main_screen_not_personalized, Toast.LENGTH_SHORT).show() - else -> { - -// val bundle = Bundle() -// bundle.putParcelable(Constant.EXTRA_LAST_DISCOVERED_TAG, lastTag) -// ctx.saveToBundle(bundle) -// navController.navigate(R.id.loadedWallet, bundle) - -// lastTag?.let { navigator.showLoadedWallet(this, it, ctx) } - } - } - } - - } else { - FirebaseCrashlytics.getInstance().recordException(cardProtocol.error) - // remove last UIDs because of error and no card read - rlProgressBar.post { - context?.let { Toast.makeText(it, R.string.general_notification_scan_again, Toast.LENGTH_SHORT).show() } - unsuccessReadCount++ - - if (cardProtocol.error is CardProtocol.TangemException_InvalidPIN) { - val data = Bundle().apply { putString(Constant.EXTRA_MODE, PinRequestFragment.Mode.RequestPIN.toString()) } - navigateForResult(Constant.REQUEST_CODE_ENTER_PIN_ACTIVITY, - R.id.action_main_to_pinRequestFragment, data) - } else { - if (cardProtocol.error is CardProtocol.TangemException_ExtendedLengthNotSupported) - if (!NoExtendedLengthSupportDialog.allReadyShowed) - NoExtendedLengthSupportDialog().show(requireActivity().supportFragmentManager, NoExtendedLengthSupportDialog.TAG) - - lastTag = null - ReadCardInfoTask.resetLastReadInfo() - (activity as? MainActivity)?.nfcManager?.notifyReadResult(false) - } - } - } - } - - rlProgressBar?.postDelayed({ rlProgressBar?.visibility = View.GONE }, 500) - } - - private fun showUnkownBlockchainWarning() { - unknownBlockchain = true - AlertDialog.Builder(context) - .setTitle(R.string.dialog_warning) - .setMessage(R.string.alert_unknown_blockchain) - .setPositiveButton(R.string.general_ok) { _, _ -> } - .setOnDismissListener { unknownBlockchain = false } - .create() - .show() - } - - override fun onReadCancel() { - task = null - ReadCardInfoTask.resetLastReadInfo() - rlProgressBar?.postDelayed({ rlProgressBar?.visibility = View.GONE }, 500) - } - - override fun onNavigationResult(requestCode: String, resultCode: Int, data: Bundle?) { - when (requestCode) { - Constant.REQUEST_CODE_SEND_EMAIL -> { - if (zipFile != null) { - zipFile!!.delete() - zipFile = null - } - } - Constant.REQUEST_CODE_ENTER_PIN_ACTIVITY -> { - if (resultCode == Activity.RESULT_OK && lastTag != null) - onTagDiscovered(lastTag!!) - else - ReadCardInfoTask.resetLastReadInfo() - } - } - } - - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { - inflater.inflate(R.menu.menu_main, menu) - if (BuildConfig.DEBUG) { - for (i in 0 until menu.size()) menu.getItem(i).isVisible = true - } - super.onCreateOptionsMenu(menu, inflater) - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - R.id.sendLogs -> { - var f: File? = null - try { - f = Logger.collectLogs(activity) - if (f != null) { - LOG.e(TAG, String.format("Collect %d log bytes", f.length())) - CommonUtil.sendEmail(activity, zipFile, TAG, "Logs", UtilHelper.getDeviceInfo(), arrayOf(f)) - } else { - LOG.e(TAG, "Can't create temporarily log file") - } - } catch (e: Exception) { - e.printStackTrace() - } finally { - if (f != null && f.exists()) - f.delete() - } - return true - } - R.id.managePIN -> { - navigateToDestination(R.id.action_main_to_pinSaveFragment, - bundleOf(Constant.EXTRA_PIN2 to false)) - return true - } - - R.id.managePIN2 -> { - navigateToDestination(R.id.action_main_to_pinSaveFragment, - bundleOf(Constant.EXTRA_PIN2 to true)) - return true - } - - R.id.settings -> { - navigateToDestination(R.id.action_main_to_settingsFragment) - return true - } - - R.id.about -> { - val bundle = Bundle().apply { putBoolean(Constant.EXTRA_AUTO_HIDE, false) } - navigateToDestination(R.id.action_main_to_logoFragment, bundle) - return true - } - } - return super.onOptionsItemSelected(item) - } - - override fun onReadWait(msec: Int) { - WaitSecurityDelayDialog.onReadWait(requireActivity() as AppCompatActivity?, msec) - } - - override fun onReadBeforeRequest(timeout: Int) { - WaitSecurityDelayDialog.onReadBeforeRequest(requireActivity() as AppCompatActivity?, timeout) - } - - override fun onReadAfterRequest() { - WaitSecurityDelayDialog.onReadAfterRequest(requireActivity()) - } - - override fun onMenuItemClick(item: MenuItem?): Boolean { - return onOptionsItemSelected(item!!) - } - - private fun showMenu(v: View) { - val popup = PopupMenu(context, v) - val inflater = popup.menuInflater - inflater.inflate(R.menu.menu_main, popup.menu) - - if (BuildConfig.DEBUG) { - popup.menu.findItem(R.id.managePIN).isEnabled = true - popup.menu.findItem(R.id.managePIN2).isEnabled = true - popup.menu.findItem(R.id.sendLogs).isVisible = true - } - - popup.setOnMenuItemClickListener(this) - popup.show() - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/ui/fragment/MainViewModel.kt b/app/src/main/java/com/tangem/ui/fragment/MainViewModel.kt deleted file mode 100644 index ce2cc664db..0000000000 --- a/app/src/main/java/com/tangem/ui/fragment/MainViewModel.kt +++ /dev/null @@ -1,44 +0,0 @@ -package com.tangem.ui.fragment - -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import com.tangem.Constant -import com.tangem.data.dp.PrefsManager -import com.tangem.data.network.ServerApiCommon -import com.tangem.wallet.BuildConfig - -class MainViewModel : ViewModel() { - private var serverApiCommon: ServerApiCommon = ServerApiCommon() - - private var versionName = MutableLiveData() - - fun getVersionName(): LiveData { - versionName = MutableLiveData() - requestVersionName() - return versionName - } - - private fun requestVersionName() { - serverApiCommon.setLastVersionListener { response -> - try { - if (response.isNullOrEmpty()) - return@setLastVersionListener - val responseVersionName = response.trim(' ', '\n', '\r', '\t') - val responseBuildVersion = responseVersionName.split('.').last() - val appBuildVersion = BuildConfig.VERSION_NAME.split('.').last() - if (responseBuildVersion.toInt() > appBuildVersion.toInt()) - if (BuildConfig.FLAVOR.equals(Constant.FLAVOR_TANGEM_ACCESS)) - versionName.value = responseVersionName - } catch (E: Exception) { - E.printStackTrace() - } - } - serverApiCommon.requestLastVersion() - } - - fun getTerminalKeys(): Map { - return PrefsManager.getInstance().terminalKeys - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/ui/fragment/PurgeFragment.kt b/app/src/main/java/com/tangem/ui/fragment/PurgeFragment.kt deleted file mode 100644 index 2130c741a9..0000000000 --- a/app/src/main/java/com/tangem/ui/fragment/PurgeFragment.kt +++ /dev/null @@ -1,229 +0,0 @@ -package com.tangem.ui.fragment - -import android.app.Activity -import android.content.Context -import android.content.Intent -import android.content.res.ColorStateList -import android.graphics.Color -import android.nfc.NfcAdapter -import android.nfc.Tag -import android.nfc.tech.IsoDep -import android.os.Bundle -import android.view.View -import android.widget.Toast -import androidx.core.os.bundleOf -import com.google.firebase.crashlytics.FirebaseCrashlytics -import com.tangem.App -import com.tangem.Constant -import com.tangem.tangem_card.reader.CardProtocol -import com.tangem.tangem_card.tasks.PurgeTask -import com.tangem.tangem_card.util.Util -import com.tangem.tangem_sdk.android.nfc.NfcDeviceAntennaLocation -import com.tangem.tangem_sdk.android.reader.NfcReader -import com.tangem.tangem_sdk.data.EXTRA_TANGEM_CARD -import com.tangem.tangem_sdk.data.EXTRA_TANGEM_CARD_UID -import com.tangem.tangem_sdk.data.asBundle -import com.tangem.ui.activity.MainActivity -import com.tangem.ui.dialog.NoExtendedLengthSupportDialog -import com.tangem.ui.dialog.WaitSecurityDelayDialog -import com.tangem.ui.dialog.WaitSecurityDelayDialogNew -import com.tangem.wallet.R -import com.tangem.wallet.TangemContext -import kotlinx.android.synthetic.main.fragment_purge.* -import kotlinx.android.synthetic.main.layout_touch_card.* -import javax.inject.Inject - -class PurgeFragment : BaseFragment(), NfcAdapter.ReaderCallback, CardProtocol.Notifications { - companion object { - val TAG: String = PurgeFragment::class.java.simpleName - - fun callingIntent(context: Context, ctx: TangemContext): Intent { - val intent = Intent(context, PurgeFragment::class.java) - ctx.saveToIntent(intent) - return intent - } - - const val RESULT_INVALID_PIN = Activity.RESULT_FIRST_USER - } - - @Inject - internal lateinit var waitSecurityDelayDialogNew: WaitSecurityDelayDialogNew - - override val layoutId = R.layout.fragment_purge - - private lateinit var ctx: TangemContext - - private lateinit var nfcDeviceAntenna: NfcDeviceAntennaLocation - - private var purgeTask: PurgeTask? = null - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - ctx = TangemContext.loadFromBundle(context, arguments) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - // init NFC Antenna - nfcDeviceAntenna = NfcDeviceAntennaLocation(requireContext(), ivHandCardHorizontal, ivHandCardVertical, llHand, llNfc) - nfcDeviceAntenna.init() - - tvCardID.text = ctx.card.cidDescription - progressBar.progressTintList = ColorStateList.valueOf(Color.DKGRAY) - progressBar.visibility = View.INVISIBLE - } - - override fun onStop() { - purgeTask?.cancel(true) - super.onStop() - } - - override fun onTagDiscovered(tag: Tag) { - try { - // get IsoDep handle and run cardReader thread - val isoDep = IsoDep.get(tag) - val uid = tag.id - val sUID = Util.byteArrayToHexString(uid) - if (sUID == ctx.card.uid) { - isoDep.timeout = ctx.card.pauseBeforePIN2 + 65000 - purgeTask = PurgeTask(ctx.card, NfcReader((activity as MainActivity).nfcManager, isoDep), - App.localStorage, App.pinStorage, this) - purgeTask?.start() - } else { - (activity as MainActivity).nfcManager.ignoreTag(isoDep.tag) - } - } catch (e: Exception) { - e.printStackTrace() - } - } - - override fun onReadWait(msec: Int) { - activity?.let { WaitSecurityDelayDialog.onReadWait(it, msec) } - -// val readWait = ReadWait() -// readWait.msec = msec -// EventBus.getDefault().post(readWait) - } - - override fun onReadBeforeRequest(timeout: Int) { - activity?.let { WaitSecurityDelayDialog.onReadBeforeRequest(it, timeout) } - -// if (!waitSecurityDelayDialogNew.isAdded) -// waitSecurityDelayDialogNew.show(supportFragmentManager, WaitSecurityDelayDialogNew.TAG) -// -// -// val readBeforeRequest = ReadBeforeRequest() -// readBeforeRequest.timeout = timeout -// EventBus.getDefault().post(readBeforeRequest) - } - - override fun onReadAfterRequest() { - activity?.let { WaitSecurityDelayDialog.onReadAfterRequest(it) } - -// val readAfterRequest = ReadAfterRequest() -// EventBus.getDefault().post(readAfterRequest) - } - - override fun onReadStart(cardProtocol: CardProtocol) { - rlProgressBar?.post { rlProgressBar.visibility = View.VISIBLE } - - progressBar?.post { - progressBar.visibility = View.VISIBLE - progressBar.progress = 5 - } - } - - override fun onReadFinish(cardProtocol: CardProtocol?) { - purgeTask = null - - if (cardProtocol != null) { - if (cardProtocol.error == null) { - rlProgressBar?.post { rlProgressBar?.visibility = View.GONE } - - progressBar?.post { - progressBar?.progress = 100 - progressBar?.progressTintList = ColorStateList.valueOf(Color.GREEN) - - val data = Bundle() - data.putString(EXTRA_TANGEM_CARD_UID, cardProtocol.card.uid) - data.putBundle(EXTRA_TANGEM_CARD, cardProtocol.card.asBundle) - - navigateToDestination(R.id.action_purgeFragment_to_mainFragment) - } - } else { - FirebaseCrashlytics.getInstance().recordException(cardProtocol.error) - if (cardProtocol.error is CardProtocol.TangemException_InvalidPIN) { - progressBar?.post { - progressBar?.progress = 100 - progressBar?.progressTintList = ColorStateList.valueOf(Color.RED) - } - progressBar?.postDelayed({ - try { - progressBar?.progress = 0 - progressBar?.progressTintList = ColorStateList.valueOf(Color.DKGRAY) - progressBar?.visibility = View.INVISIBLE - - val data = bundleOf( - EXTRA_TANGEM_CARD_UID to cardProtocol.card.uid, - EXTRA_TANGEM_CARD to cardProtocol.card.asBundle, - Constant.EXTRA_MESSAGE to getString(R.string.nfc_error_cannot_erase_wallet) - ) - navigateBackWithResult(RESULT_INVALID_PIN, data) - return@postDelayed - } catch (e: Exception) { - e.printStackTrace() - } - }, 500) - } else { - progressBar?.post { - if (cardProtocol.error is CardProtocol.TangemException_ExtendedLengthNotSupported) { - if (!NoExtendedLengthSupportDialog.allReadyShowed) - NoExtendedLengthSupportDialog().show(requireActivity().supportFragmentManager, NoExtendedLengthSupportDialog.TAG) - } else - Toast.makeText(context, R.string.general_notification_scan_again_to_verify, Toast.LENGTH_LONG).show() - - progressBar?.progress = 100 - progressBar?.progressTintList = ColorStateList.valueOf(Color.RED) - } - } - } - } - - rlProgressBar?.postDelayed({ - try { - rlProgressBar?.visibility = View.GONE - } catch (e: Exception) { - e.printStackTrace() - } - }, 500) - - progressBar?.postDelayed({ - try { - progressBar?.progress = 0 - progressBar?.progressTintList = ColorStateList.valueOf(Color.DKGRAY) - progressBar?.visibility = View.INVISIBLE - } catch (e: Exception) { - e.printStackTrace() - } - }, 500) - } - - override fun onReadProgress(protocol: CardProtocol, progress: Int) { - progressBar?.post { progressBar?.progress = progress } - } - - override fun onReadCancel() { - purgeTask = null - progressBar?.postDelayed({ - try { - progressBar?.progress = 0 - progressBar?.progressTintList = ColorStateList.valueOf(Color.DKGRAY) - progressBar?.visibility = View.INVISIBLE - } catch (e: Exception) { - e.printStackTrace() - } - }, 500) - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/ui/fragment/SendTransactionFragment.kt b/app/src/main/java/com/tangem/ui/fragment/SendTransactionFragment.kt deleted file mode 100644 index 4e9de2f02e..0000000000 --- a/app/src/main/java/com/tangem/ui/fragment/SendTransactionFragment.kt +++ /dev/null @@ -1,98 +0,0 @@ -package com.tangem.ui.fragment - -import android.app.Activity -import android.nfc.NfcAdapter -import android.nfc.Tag -import android.os.Bundle -import android.widget.Toast -import androidx.activity.OnBackPressedCallback -import androidx.core.os.bundleOf -import com.google.firebase.analytics.FirebaseAnalytics -import com.tangem.App -import com.tangem.Constant -import com.tangem.tangem_card.util.Util -import com.tangem.ui.activity.MainActivity -import com.tangem.ui.event.TransactionFinishWithSuccess -import com.tangem.util.AnalyticsEvent -import com.tangem.util.AnalyticsParam -import com.tangem.util.UtilHelper -import com.tangem.wallet.CoinEngine -import com.tangem.wallet.CoinEngineFactory -import com.tangem.wallet.R -import com.tangem.wallet.TangemContext -import org.greenrobot.eventbus.EventBus -import java.io.IOException - -class SendTransactionFragment : BaseFragment(), NfcAdapter.ReaderCallback { - - override val layoutId = R.layout.fragment_send_transaction - - private lateinit var ctx: TangemContext - - private var tx: ByteArray? = null - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - ctx = TangemContext.loadFromBundle(context, arguments) - tx = arguments?.getByteArray(Constant.EXTRA_TX) - - val engine = CoinEngineFactory.create(ctx) - - engine!!.requestSendTransaction( - object : CoinEngine.BlockchainRequestsCallbacks { - override fun onComplete(success: Boolean) { - if (success) { - App.pendingTransactionsStorage.putTransaction(ctx.card, Util.bytesToHex(tx), engine.pendingTransactionTimeoutInSeconds()) - finishWithSuccess() - } else - finishWithError(ctx.error) - } - - override fun onProgress() { - } - - override fun allowAdvance(): Boolean { - return UtilHelper.isOnline(requireContext()) - } - }, - tx - ) - - val callback = object : OnBackPressedCallback(true) { - override fun handleOnBackPressed() { - Toast.makeText(context, R.string.send_transaction_notification_wait, Toast.LENGTH_LONG).show() - } - - } - requireActivity().onBackPressedDispatcher.addCallback(this, callback) - } - - override fun onTagDiscovered(tag: Tag) { - try { - (activity as MainActivity).nfcManager.ignoreTag(tag) - } catch (e: IOException) { - e.printStackTrace() - } - } - - private fun finishWithSuccess() { - activity?.let { - val params = bundleOf(AnalyticsParam.BLOCKCHAIN.param to ctx.blockchainName) - FirebaseAnalytics.getInstance(it).logEvent(AnalyticsEvent.TRANSACTION_IS_SENT.event, params) - } - - val transactionFinishWithSuccess = TransactionFinishWithSuccess() - EventBus.getDefault().post(transactionFinishWithSuccess) - - val data = Bundle() - data.putString(Constant.EXTRA_MESSAGE, getString(R.string.send_transaction_success)) - navigateBackWithResult(Activity.RESULT_OK, data, R.id.loadedWalletFragment) - } - - private fun finishWithError(message: String) { - val data = Bundle() - data.putString(Constant.EXTRA_MESSAGE, String.format(getString(R.string.send_transaction_error_failed_to_send), message)) - navigateBackWithResult(Activity.RESULT_CANCELED, data, R.id.loadedWalletFragment) - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/ui/fragment/SettingsFragment.kt b/app/src/main/java/com/tangem/ui/fragment/SettingsFragment.kt deleted file mode 100644 index 84aad44a88..0000000000 --- a/app/src/main/java/com/tangem/ui/fragment/SettingsFragment.kt +++ /dev/null @@ -1,71 +0,0 @@ -package com.tangem.ui.fragment - -import android.content.SharedPreferences -import android.os.Bundle -import android.view.View -import androidx.appcompat.app.AppCompatActivity -import androidx.core.os.bundleOf -import androidx.navigation.fragment.NavHostFragment -import androidx.preference.ListPreference -import androidx.preference.Preference -import androidx.preference.PreferenceCategory -import androidx.preference.PreferenceFragmentCompat -import com.google.firebase.analytics.FirebaseAnalytics -import com.tangem.util.Analytics -import com.tangem.util.AnalyticsEvent -import com.tangem.wallet.BuildConfig -import com.tangem.wallet.R -import kotlinx.android.synthetic.main.fragment_settings.* - -class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedPreferenceChangeListener { - override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { - addPreferencesFromResource(R.xml.pref_main) - - val categoryCommon = findPreference(getString(R.string.pref_category_common)) - val selectNodes = findPreference(getString(R.string.pref_select_nodes)) - val encryptionModes = findPreference(getString(R.string.pref_encryption_modes)) - - if (!BuildConfig.DEBUG) { - categoryCommon?.removePreference(selectNodes) - categoryCommon?.removePreference(encryptionModes) - } - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - initToolbar() - } - - private fun initToolbar() { - toolbar?.setTitle(R.string.settings_title) - toolbar?.setNavigationIcon(android.R.drawable.ic_menu_close_clear_cancel) - (activity as AppCompatActivity).setSupportActionBar(toolbar) - toolbar?.setNavigationOnClickListener { - NavHostFragment.findNavController(this).popBackStack() - } - } - - override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { - if (key == getString(R.string.pref_analytics)) { - activity?.let {activity -> - val analytics = FirebaseAnalytics.getInstance(activity) - if (!Analytics.isEnabled()) { - analytics.logEvent(AnalyticsEvent.ANALYTICS_TURNED_OFF.event, bundleOf()) - } - Analytics.setFirebaseEnabled(activity) - } - } - } - - override fun onResume() { - super.onResume() - preferenceScreen.sharedPreferences - .registerOnSharedPreferenceChangeListener(this); - } - - override fun onPause() { - super.onPause() - preferenceScreen.sharedPreferences - .unregisterOnSharedPreferenceChangeListener(this); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/ui/fragment/VerifyCardFragment.kt b/app/src/main/java/com/tangem/ui/fragment/VerifyCardFragment.kt deleted file mode 100644 index 3fa306f8c0..0000000000 --- a/app/src/main/java/com/tangem/ui/fragment/VerifyCardFragment.kt +++ /dev/null @@ -1,478 +0,0 @@ -package com.tangem.ui.fragment - -import android.app.Activity -import android.content.Intent -import android.nfc.NfcAdapter -import android.nfc.Tag -import android.os.Build -import android.os.Bundle -import android.text.Html -import android.text.format.DateUtils -import android.view.View -import android.widget.PopupMenu -import android.widget.Toast -import androidx.activity.OnBackPressedCallback -import androidx.core.content.ContextCompat -import com.tangem.App -import com.tangem.Constant -import com.tangem.data.Blockchain -import com.tangem.tangem_card.data.TangemCard -import com.tangem.tangem_card.reader.CardProtocol -import com.tangem.tangem_sdk.android.data.PINStorage -import com.tangem.tangem_sdk.data.EXTRA_TANGEM_CARD -import com.tangem.tangem_sdk.data.EXTRA_TANGEM_CARD_UID -import com.tangem.tangem_sdk.data.loadFromBundle -import com.tangem.ui.activity.MainActivity -import com.tangem.ui.dialog.PINSwapWarningDialog -import com.tangem.ui.event.DeletingWalletFinish -import com.tangem.ui.fragment.pin.PinRequestFragment -import com.tangem.ui.navigation.NavigationResultListener -import com.tangem.wallet.BuildConfig -import com.tangem.wallet.CoinEngineFactory -import com.tangem.wallet.R -import com.tangem.wallet.TangemContext -import kotlinx.android.synthetic.main.fr_verify_card.* -import org.greenrobot.eventbus.EventBus -import org.greenrobot.eventbus.Subscribe -import java.io.IOException -import java.util.* - -class VerifyCardFragment : BaseFragment(), NavigationResultListener, NfcAdapter.ReaderCallback { - companion object { - val TAG: String = VerifyCardFragment::class.java.simpleName - } - - override val layoutId = R.layout.fr_verify_card - - private lateinit var ctx: TangemContext - - private var requestPIN2Count = 0 - private var timerHideErrorAndMessage: Timer? = null - private var newPIN = "" - private var newPIN2 = "" - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - ctx = TangemContext.loadFromBundle(context, arguments) - - val callback = object : OnBackPressedCallback(true) { - override fun handleOnBackPressed() { - val data = prepareResultIntent() - data.putExtra(Constant.EXTRA_MODIFICATION, getString(R.string.main_screen_btn_update)) - navigateUp() - } - } - requireActivity().onBackPressedDispatcher.addCallback(this, callback) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - updateViews() - - val card = ctx.card - if (!card.allowSwapPIN() && !card.allowSwapPIN2() && card.forbidPurgeWallet()) { - fabMenu.hide() - } - - // set listeners - fabMenu.setOnClickListener { showMenu(fabMenu) } - - btnOk.setOnClickListener { - val data = prepareResultIntent() - data.putExtra(Constant.EXTRA_MODIFICATION, getString(R.string.main_screen_btn_update)) - navigateUp() - } - } - - override fun onStart() { - super.onStart() - if (!EventBus.getDefault().isRegistered(this)) - EventBus.getDefault().register(this) - } - - override fun onDestroy() { - EventBus.getDefault().unregister(this) - super.onDestroy() - } - - @Subscribe - fun onDeleteWalletFinish(deletingWalletFinish: DeletingWalletFinish) { - activity?.finish() - } - - override fun onNavigationResult(requestCode: String, resultCode: Int, data: Bundle?) { - when (requestCode) { - Constant.REQUEST_CODE_ENTER_NEW_PIN -> if (resultCode == Activity.RESULT_OK) { - if (data != null) { - if (data.containsKey(Constant.EXTRA_CONFIRM_PIN)) { - val bundle = Bundle() - bundle.putString(Constant.EXTRA_MODE, PinRequestFragment.Mode.RequestPIN2.toString()) - ctx.saveToBundle(bundle) - newPIN = data.getString(Constant.EXTRA_NEW_PIN) - navigateForResult(Constant.REQUEST_CODE_REQUEST_PIN2_FOR_SWAP_PIN, - R.id.action_verifyCard_to_pinRequestFragment, bundle) - } else { - val bundle = Bundle() - bundle.putString(Constant.EXTRA_NEW_PIN, data.getString(Constant.EXTRA_NEW_PIN)) - bundle.putString(Constant.EXTRA_MODE, PinRequestFragment.Mode.ConfirmNewPIN.toString()) - navigateForResult(Constant.REQUEST_CODE_ENTER_NEW_PIN, - R.id.action_verifyCard_to_pinRequestFragment, bundle) - - } - } - } - - Constant.REQUEST_CODE_ENTER_NEW_PIN2 -> if (resultCode == Activity.RESULT_OK) { - if (data != null) { - if (data.containsKey(Constant.EXTRA_CONFIRM_PIN_2)) { - val bundle = Bundle() - bundle.putString(Constant.EXTRA_MODE, PinRequestFragment.Mode.RequestPIN2.toString()) - ctx.saveToBundle(bundle) - newPIN2 = data.getString(Constant.EXTRA_NEW_PIN_2) - navigateForResult(Constant.REQUEST_CODE_REQUEST_PIN2_FOR_SWAP_PIN, - R.id.action_verifyCard_to_pinRequestFragment, bundle) - - } else { - val bundle = Bundle() - bundle.putString(Constant.EXTRA_NEW_PIN_2, data.getString(Constant.EXTRA_NEW_PIN_2)) - bundle.putString(Constant.EXTRA_MODE, PinRequestFragment.Mode.ConfirmNewPIN2.toString()) - navigateForResult(Constant.REQUEST_CODE_ENTER_NEW_PIN2, - R.id.action_verifyCard_to_pinRequestFragment, bundle) - } - } - } - - Constant.REQUEST_CODE_REQUEST_PIN2_FOR_SWAP_PIN -> if (resultCode == Activity.RESULT_OK) { - if (newPIN == "") newPIN = ctx.card!!.pin - - if (newPIN2 == "") newPIN2 = App.pinStorage.piN2 - - val pinSwapWarningDialog = PINSwapWarningDialog() - pinSwapWarningDialog.setOnRefreshPage { - val bundle = Bundle() - bundle.putString(Constant.EXTRA_NEW_PIN, newPIN) - bundle.putString(Constant.EXTRA_NEW_PIN_2, newPIN2) - navigateForResult(Constant.REQUEST_CODE_SWAP_PIN, - R.id.action_verifyCard_to_pinSwapFragment, bundle) - } - val bundle = Bundle() - if (!CardProtocol.isDefaultPIN(newPIN) || !CardProtocol.isDefaultPIN2(newPIN2)) - bundle.putString(Constant.EXTRA_MESSAGE, getString(R.string.loaded_wallet_warning_dont_forget_pin)) - else - bundle.putString(Constant.EXTRA_MESSAGE, getString(R.string.loaded_wallet_warning_default_pin)) - pinSwapWarningDialog.arguments = bundle - activity?.supportFragmentManager?.let { pinSwapWarningDialog.show(it, PINSwapWarningDialog.TAG) } - } - - Constant.REQUEST_CODE_SWAP_PIN -> if (resultCode == Activity.RESULT_OK) { - if (data == null) { - val newData = Bundle() - ctx.saveToBundle(newData) - newData.putString(Constant.EXTRA_MODIFICATION, Constant.EXTRA_MODIFICATION_DELETE) - navigateBackWithResult(Activity.RESULT_OK, newData) - } else { - data.putString(Constant.EXTRA_MODIFICATION, Constant.EXTRA_MODIFICATION_UPDATE) - navigateBackWithResult(Activity.RESULT_OK, data) - } - } else { - if (data != null && data.containsKey(EXTRA_TANGEM_CARD_UID) && data.containsKey(EXTRA_TANGEM_CARD)) { - val updatedCard = TangemCard(data.getString(EXTRA_TANGEM_CARD_UID)) - updatedCard.loadFromBundle(data.getBundle(EXTRA_TANGEM_CARD)) - ctx.card = updatedCard - } - if (resultCode == Constant.RESULT_INVALID_PIN && requestPIN2Count < 2) { - requestPIN2Count++ - val bundle = Bundle() - bundle.putString(Constant.EXTRA_MODE, PinRequestFragment.Mode.RequestPIN2.toString()) - ctx.saveToBundle(bundle) - navigateForResult(Constant.REQUEST_CODE_REQUEST_PIN2_FOR_SWAP_PIN, - R.id.action_verifyCard_to_pinRequestFragment, bundle) - return - } else { - if (data != null && data.containsKey(Constant.EXTRA_MESSAGE)) { - ctx.error = data.getString(Constant.EXTRA_MESSAGE) - } - } - } - - Constant.REQUEST_CODE_REQUEST_PIN2_FOR_PURGE -> if (resultCode == Activity.RESULT_OK) { - val bundle = Bundle().apply { ctx.saveToBundle(this) } - navigateForResult(Constant.REQUEST_CODE_PURGE, - R.id.action_verifyCard_to_purgeFragment, bundle) - } else { - if (data != null && data.containsKey(EXTRA_TANGEM_CARD_UID) && data.containsKey(EXTRA_TANGEM_CARD)) { - val updatedCard = TangemCard(data.getString(EXTRA_TANGEM_CARD_UID)) - updatedCard.loadFromBundle(data.getBundle(EXTRA_TANGEM_CARD)) - ctx.card = updatedCard - } - if (resultCode == Constant.RESULT_INVALID_PIN && requestPIN2Count < 2) { - requestPIN2Count++ - val bundle = Bundle() - bundle.putString(Constant.EXTRA_MODE, PinRequestFragment.Mode.RequestPIN2.toString()) - ctx.saveToBundle(bundle) - navigateForResult(Constant.REQUEST_CODE_REQUEST_PIN2_FOR_PURGE, - R.id.action_verifyCard_to_pinRequestFragment, data) - return - } else { - if (data != null && data.containsKey(Constant.EXTRA_MESSAGE)) { - ctx.error = data.getString(Constant.EXTRA_MESSAGE) - } - } - updateViews() - } - } - } - - override fun onTagDiscovered(tag: Tag?) { - try { - (activity as MainActivity).nfcManager.ignoreTag(tag!!) - } catch (e: IOException) { - e.printStackTrace() - } - } - - private fun updateViews() { - try { - if (timerHideErrorAndMessage != null) { - timerHideErrorAndMessage!!.cancel() - timerHideErrorAndMessage = null - } - tvCardID.text = ctx.card!!.cidDescription - - if (ctx.error == null || ctx.error.isEmpty()) { - tvError.visibility = View.GONE - tvError.text = "" - } else { - tvError.visibility = View.VISIBLE - tvError.text = ctx.error - } - if (ctx.message == null || ctx.message.isEmpty()) { - tvMessage.visibility = View.GONE - tvMessage.text = "" - } else { - tvMessage.visibility = View.VISIBLE - tvMessage.text = ctx.message - } - - tvManufacturerInfo.text = ctx.card!!.manufacturer.officialName - - if (ctx.card!!.isManufacturerConfirmed && ctx.card!!.isCardPublicKeyValid) { - tvCardIdentity.setText(R.string.details_attested) - tvCardIdentity.setTextColor(ContextCompat.getColor(context!!, R.color.confirmed)) - - } else { - tvCardIdentity.setText(R.string.details_not_confirmed) - tvCardIdentity.setTextColor(ContextCompat.getColor(context!!, R.color.not_confirmed)) - } - - tvIssuer.text = ctx.card!!.issuerDescription - - tvCardRegistredDate.text = DateUtils.formatDateTime(null, ctx.card!!.personalizationDateTime.time, DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_NUMERIC_DATE or DateUtils.FORMAT_SHOW_YEAR) - - @Suppress("DEPRECATION") val html = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) - Html.fromHtml(ctx.blockchainName, Html.FROM_HTML_MODE_LEGACY) - else - Html.fromHtml(ctx.blockchainName) - tvBlockchain.text = html - - tvValidationNode.text = ctx.coinData!!.validationNodeDescription - - val engine = CoinEngineFactory.create(ctx) - - tvInputs.text = engine!!.unspentInputsDescription - - ivBlockchain.setImageResource(ctx.blockchain.getLogoImageResource(ctx.card!!.tokenSymbol)) - - var s = "" - for (signingM in ctx.card!!.allowedSigningMethod) { - s += if (signingM == ctx.card!!.signingMethod) { - ", ${signingM.description}" - } else { - ", ${signingM.description}" - } - } - s = s.substring(2, s.length) - @Suppress("DEPRECATION") - tvSigningMethod.text = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) - Html.fromHtml(s, Html.FROM_HTML_MODE_LEGACY) - else - Html.fromHtml(s) - - - if (ctx.card!!.status == TangemCard.Status.Loaded || ctx.card!!.status == TangemCard.Status.Purged) { - - when { - ctx.card!!.remainingSignatures == 0 -> { - tvRemainingSignatures.setTextColor(ContextCompat.getColor(context!!, R.color.not_confirmed)) - tvRemainingSignatures.setText(R.string.details_none) - } - ctx.card!!.remainingSignatures == 1 -> { - tvRemainingSignatures.setTextColor(ContextCompat.getColor(context!!, R.color.not_confirmed)) - tvRemainingSignatures.setText(R.string.details_last_one) - } - ctx.card!!.remainingSignatures > 1000 -> { - tvRemainingSignatures.setTextColor(ContextCompat.getColor(context!!, R.color.confirmed)) - tvRemainingSignatures.setText(R.string.details_unlimited) - } - else -> { - tvRemainingSignatures.setTextColor(ContextCompat.getColor(context!!, R.color.confirmed)) - tvRemainingSignatures.text = ctx.card!!.remainingSignatures.toString() - } - } - tvSignedTx.text = (ctx.card!!.maxSignatures - ctx.card!!.remainingSignatures).toString() - } else { - tvLastSigned.text = "" - tvRemainingSignatures.text = "" - tvSignedTx.text = "" - } - - tvFirmware.text = ctx.card!!.firmwareVersion - - val textIsLinked = if (ctx.card.terminalIsLinked) { - getString(R.string.details_linked_card_to_phone) - } else { - getString(R.string.general_no) - } - tvIsLinked.text = textIsLinked - - if (ctx.card!!.useDevelopersFirmware()!!) { - imgDeveloperVersion.setImageResource(R.drawable.ic_developer_version) - imgDeveloperVersion.visibility = View.VISIBLE - imgDeveloperVersion.setOnClickListener { Toast.makeText(context, R.string.details_unlocked_card, Toast.LENGTH_LONG).show() } - } else - imgDeveloperVersion.visibility = View.INVISIBLE - - if (ctx.card!!.status == TangemCard.Status.Loaded) { - tvWallet.text = ctx.coinData!!.shortWalletString - if (ctx.card!!.isWalletPublicKeyValid) { - tvWalletIdentity.setText(R.string.details_possession_proved) - tvWalletIdentity.setTextColor(ContextCompat.getColor(context!!, R.color.confirmed)) - } else { - tvWalletIdentity.setText(R.string.details_possession_not_proved) - tvWalletIdentity.setTextColor(ContextCompat.getColor(context!!, R.color.not_confirmed)) - } - } else { - tvWallet!!.setText(R.string.details_not_available) - tvWalletIdentity.setText(R.string.no_data_string) - } - } catch (e: Exception) { - e.printStackTrace() - } - } - - private fun doSetPin() { - requestPIN2Count = 0 - val data = Bundle() - data.putString(Constant.EXTRA_MODE, PinRequestFragment.Mode.RequestNewPIN.toString()) - newPIN = "" - newPIN2 = "" - navigateForResult(Constant.REQUEST_CODE_ENTER_NEW_PIN, - R.id.action_verifyCard_to_pinRequestFragment, data) - } - - private fun doResetPin() { - requestPIN2Count = 0 - val data = Bundle() - data.putString(Constant.EXTRA_MODE, PinRequestFragment.Mode.RequestPIN2.toString()) - ctx.saveToBundle(data) - newPIN = PINStorage.getDefaultPIN() - newPIN2 = "" - navigateForResult(Constant.REQUEST_CODE_REQUEST_PIN2_FOR_SWAP_PIN, - R.id.action_verifyCard_to_pinRequestFragment, data) - } - - private fun doResetPin2() { - requestPIN2Count = 0 - val data = Bundle() - data.putString(Constant.EXTRA_MODE, PinRequestFragment.Mode.RequestPIN2.toString()) - ctx.saveToBundle(data) - newPIN = "" - newPIN2 = PINStorage.getDefaultPIN2() - navigateForResult(Constant.REQUEST_CODE_REQUEST_PIN2_FOR_SWAP_PIN, - R.id.action_verifyCard_to_pinRequestFragment, data) - } - - private fun doResetPins() { - requestPIN2Count = 0 - val data = Bundle() - data.putString(Constant.EXTRA_MODE, PinRequestFragment.Mode.RequestPIN2.toString()) - ctx.saveToBundle(data) - newPIN = PINStorage.getDefaultPIN() - newPIN2 = PINStorage.getDefaultPIN2() - navigateForResult(Constant.REQUEST_CODE_REQUEST_PIN2_FOR_SWAP_PIN, - R.id.action_verifyCard_to_pinRequestFragment, data) - } - - private fun doSetPin2() { - requestPIN2Count = 0 - val data = Bundle() - data.putString(Constant.EXTRA_MODE, PinRequestFragment.Mode.RequestNewPIN2.toString()) - newPIN = "" - newPIN2 = "" - navigateForResult(Constant.REQUEST_CODE_ENTER_NEW_PIN2, - R.id.action_verifyCard_to_pinRequestFragment, data) - } - - private fun doPurge() { - requestPIN2Count = 0 - val engine = CoinEngineFactory.create(ctx) ?: return - if (!engine.hasBalanceInfo()) { - Toast.makeText(context, R.string.general_error_cannot_erase_wallet_with_non_zero_balance, Toast.LENGTH_LONG).show() - return - } - if (engine.isBalanceNotZero) { - Toast.makeText(context, R.string.general_error_cannot_erase_wallet_with_non_zero_balance, Toast.LENGTH_LONG).show() - return - } else if (engine.awaitingConfirmation()) { - Toast.makeText(context, R.string.general_error_cannot_erase_wallet_with_non_zero_balance, Toast.LENGTH_LONG).show() - return - } - - val data = Bundle() - data.putString(Constant.EXTRA_MODE, PinRequestFragment.Mode.RequestPIN2.toString()) - ctx.saveToBundle(data) - navigateForResult(Constant.REQUEST_CODE_REQUEST_PIN2_FOR_PURGE, - R.id.action_verifyCard_to_pinRequestFragment, data) - } - - fun prepareResultIntent(): Intent { - val data = Intent() - ctx.saveToIntent(data) - return data - } - - private fun showMenu(v: View) { - val popup = PopupMenu(activity, v) - val inflater = popup.menuInflater - inflater.inflate(R.menu.menu_loaded_wallet, popup.menu) - - popup.menu.findItem(R.id.action_set_PIN1).isVisible = ctx.card!!.allowSwapPIN()!! - popup.menu.findItem(R.id.action_reset_PIN1).isVisible = ctx.card!!.allowSwapPIN()!! && !ctx.card!!.useDefaultPIN1() - popup.menu.findItem(R.id.action_set_PIN2).isVisible = ctx.card!!.allowSwapPIN2()!! - popup.menu.findItem(R.id.action_reset_PIN2).isVisible = ctx.card!!.allowSwapPIN2()!! && !ctx.card!!.useDefaultPIN2() - popup.menu.findItem(R.id.action_reset_PINs).isVisible = ctx.card!!.allowSwapPIN()!! && ctx.card!!.allowSwapPIN2()!! && !ctx.card!!.useDefaultPIN1() && !ctx.card!!.useDefaultPIN2() - if (!ctx.card!!.isReusable || ctx.card!!.status != TangemCard.Status.Loaded) - popup.menu.findItem(R.id.action_purge).isVisible = false - - popup.setOnMenuItemClickListener { item -> - val id = item.itemId - when (id) { - R.id.action_set_PIN1 -> doSetPin() - R.id.action_reset_PIN1 -> doResetPin() - R.id.action_set_PIN2 -> doSetPin2() - R.id.action_reset_PIN2 -> doResetPin2() - R.id.action_reset_PINs -> doResetPins() - R.id.action_purge -> doPurge() - } - false - } - - if (BuildConfig.DEBUG) { - popup.menu.findItem(R.id.action_set_PIN2).isEnabled = true - popup.menu.findItem(R.id.action_reset_PIN2).isEnabled = true - } - - popup.show() - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/ui/fragment/additional/PrepareCryptonitOtherApiWithdrawalFragment.kt b/app/src/main/java/com/tangem/ui/fragment/additional/PrepareCryptonitOtherApiWithdrawalFragment.kt deleted file mode 100644 index f9c5e38416..0000000000 --- a/app/src/main/java/com/tangem/ui/fragment/additional/PrepareCryptonitOtherApiWithdrawalFragment.kt +++ /dev/null @@ -1,174 +0,0 @@ -package com.tangem.ui.fragment.additional - -import android.app.Activity -import android.graphics.Color -import android.nfc.NfcAdapter -import android.nfc.Tag -import android.os.Bundle -import android.view.View -import com.tangem.Constant -import com.tangem.data.Blockchain -import com.tangem.data.network.CryptonitOtherApi -import com.tangem.ui.activity.MainActivity -import com.tangem.ui.fragment.BaseFragment -import com.tangem.ui.fragment.qr.CameraPermissionManager -import com.tangem.ui.navigation.NavigationResultListener -import com.tangem.wallet.CoinEngineFactory -import com.tangem.wallet.R -import com.tangem.wallet.TangemContext -import kotlinx.android.synthetic.main.fragment_prepare_cryptonit_other_api_withdrawal.* -import java.io.IOException - -class PrepareCryptonitOtherApiWithdrawalFragment : BaseFragment(), NavigationResultListener, - NfcAdapter.ReaderCallback { - - companion object { - val TAG: String = PrepareCryptonitOtherApiWithdrawalFragment::class.java.simpleName - } - - override val layoutId = R.layout.fragment_prepare_cryptonit_other_api_withdrawal - - private val cameraPermissionManager: CameraPermissionManager by lazy { CameraPermissionManager(this) } - private val ctx: TangemContext by lazy { TangemContext.loadFromBundle(context, arguments) } - private val cryptonit: CryptonitOtherApi by lazy { CryptonitOtherApi(context) } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - tvKey.text = cryptonit.key - tvUserID.text = cryptonit.userId - tvSecret.text = cryptonit.secretDescription - - tvCardID.text = ctx.card!!.cidDescription - tvWallet.text = ctx.coinData!!.wallet - val engine = CoinEngineFactory.create(ctx) - - tvCurrency.text = engine!!.balanceCurrency - - etAmount.setText(engine.convertToAmount(engine.convertToInternalAmount(ctx.card!!.denomination)).toValueString()) - etAmount.filters = engine.amountInputFilters - - // set listeners - btnLoad.setOnClickListener { - try { - val strAmount: String = etAmount.text.toString().replace(",", ".") -// if (!engine.checkAmount(card, strAmount)) -// etAmount.error = getString(R.string.unknown_amount_format) - val dblAmount: Double = strAmount.toDouble() - - rlProgressBar.visibility = View.VISIBLE - tvProgressDescription.text = getString(R.string.cryptonit_request_withdrawal) - - cryptonit.requestCryptoWithdrawal(ctx.blockchain.currency, dblAmount.toString(), ctx.coinData!!.wallet) - } catch (e: Exception) { - etAmount.error = getString(R.string.prepare_transaction_error_unknown_amount_format) - } - - //Toast.makeText(this, strAmount, Toast.LENGTH_LONG).show() -// val balance = engine.getBalanceLong(card)!! / (card!!.blockchain.multiplier / 1000.0) -// if (etAmount.text.toString().replace(",", ".").toDouble() > balance) { -// etAmount.error = getString(R.string.not_enough_funds_on_your_account) -// return@setOnClickListener -// } - - } - ivCameraKey.setOnClickListener { checkPermissionsAndRunCamera() } - - ivCameraSecret.setOnClickListener { checkPermissionsAndRunCamera() } - - ivCameraUserId.setOnClickListener { checkPermissionsAndRunCamera() } - - ivRefreshBalance.setOnClickListener { doRequestBalance() } - - cryptonit.setBalanceListener { response -> - when (ctx.blockchain) { - Blockchain.Ethereum, Blockchain.EthereumTestNet -> { - tvBalance.text = response.eth_available - } - Blockchain.Bitcoin, Blockchain.BitcoinTestNet, Blockchain.BitcoinCash -> { - tvBalance.text = response.btc_available - } - else -> { - } - } - - tvBalanceCurrency.text = ctx.blockchain.currency - tvBalance.setTextColor(Color.BLACK) - rlProgressBar.visibility = View.INVISIBLE - btnLoad.isActivated = true - } - cryptonit.setErrorListener { throwable -> - throwable.printStackTrace() - rlProgressBar.visibility = View.INVISIBLE - tvError.visibility = View.VISIBLE - tvError.text = throwable.message - } - cryptonit.setWithdrawalListener { response -> - rlProgressBar.visibility = View.INVISIBLE - if (response.success != null && response.success!!) { - navigateUp() - } else { - tvError.visibility = View.VISIBLE - tvError.text = response.reason!!.toString() - } - } - btnLoad.isActivated = false - doRequestBalance() - } - - private fun checkPermissionsAndRunCamera() { - if (cameraPermissionManager.isPermissionGranted()) { - navigateForResult(Constant.REQUEST_CODE_SCAN_QR, R.id.action_prepareCryptonitOtherApiWithdrawalFragment_to_qrScanFragment) - } else { - cameraPermissionManager.requirePermission() - } - } - - override fun onNavigationResult(requestCode: String, resultCode: Int, data: Bundle?) { - if (resultCode == Activity.RESULT_OK && data != null && data.containsKey("QRCode")) { - when (requestCode) { - Constant.REQUEST_CODE_SCAN_QR_KEY -> { - cryptonit.key = data.getString("QRCode") - tvKey?.text = cryptonit.key - } - Constant.REQUEST_CODE_SCAN_QR_SECRET -> { - cryptonit.secret = data.getString("QRCode") - tvSecret?.text = cryptonit.secretDescription - } - Constant.REQUEST_CODE_SCAN_QR_USER_ID -> { - cryptonit.userId = data.getString("QRCode") - tvUserID?.text = cryptonit.userId - } - } - cryptonit.saveAccountInfo() - doRequestBalance() - } - } - - override fun onTagDiscovered(tag: Tag) { - try { - (activity as MainActivity).nfcManager.ignoreTag(tag) - } catch (e: IOException) { - e.printStackTrace() - } - } - - private fun doRequestBalance() { - if (cryptonit.havaAccountInfo()) { - rlProgressBar.visibility = View.VISIBLE - tvProgressDescription.text = getString(R.string.cryptonit_request_balance) - tvError.visibility = View.INVISIBLE - cryptonit.requestBalance(ctx.blockchain.currency, "USD") - } else { - tvError.visibility = View.VISIBLE - tvError.text = getString(R.string.cryptonit_not_enough_account_data) - } - } - - override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults) - cameraPermissionManager.handleRequestPermissionResult(requestCode, grantResults) { - navigateForResult(Constant.REQUEST_CODE_SCAN_QR, R.id.action_prepareCryptonitOtherApiWithdrawalFragment_to_qrScanFragment) - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/ui/fragment/additional/PrepareCryptonitWithdrawalFragment.kt b/app/src/main/java/com/tangem/ui/fragment/additional/PrepareCryptonitWithdrawalFragment.kt deleted file mode 100644 index 3ff76912db..0000000000 --- a/app/src/main/java/com/tangem/ui/fragment/additional/PrepareCryptonitWithdrawalFragment.kt +++ /dev/null @@ -1,149 +0,0 @@ -package com.tangem.ui.fragment.additional - -import android.content.Context -import android.graphics.Color -import android.nfc.NfcAdapter -import android.nfc.Tag -import android.os.Bundle -import android.text.InputFilter -import android.view.View -import android.view.inputmethod.EditorInfo -import android.view.inputmethod.InputMethodManager -import android.widget.Toast -import com.tangem.data.Blockchain -import com.tangem.data.network.Cryptonit -import com.tangem.ui.activity.MainActivity -import com.tangem.ui.fragment.BaseFragment -import com.tangem.util.DecimalDigitsInputFilter -import com.tangem.wallet.CoinEngineFactory -import com.tangem.wallet.R -import com.tangem.wallet.TangemContext -import kotlinx.android.synthetic.main.fragment_prepare_cryptonit_withdrawal.* -import java.io.IOException - -class PrepareCryptonitWithdrawalFragment : BaseFragment(), NfcAdapter.ReaderCallback { - - companion object { - val TAG: String = PrepareCryptonitWithdrawalFragment::class.java.simpleName - } - - override val layoutId = R.layout.fragment_prepare_cryptonit_withdrawal - - private val ctx: TangemContext by lazy { TangemContext.loadFromBundle(context, arguments) } - private val cryptonit: Cryptonit by lazy { Cryptonit(context) } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - etUsername.setText(cryptonit.username) - etPassword.setText(cryptonit.password) - etFee.setText(cryptonit.fee) - - tvCardID.text = ctx.card!!.cidDescription - tvWallet.text = ctx.coinData!!.wallet - val engine = CoinEngineFactory.create(ctx) - - tvCurrency.text = engine!!.balanceCurrency - tvFeeCurrency.text = engine.feeCurrency - - etAmount.setText(engine.convertToAmount(engine.convertToInternalAmount(ctx.card!!.denomination)).toValueString()) - etAmount.filters = engine.amountInputFilters - - etAmount.setOnEditorActionListener { lv, actionId, _ -> - if (actionId == EditorInfo.IME_ACTION_DONE) { - val imm = lv.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager - imm.hideSoftInputFromWindow(lv.windowToken, 0) - true - } else - false - } - - when (ctx.blockchain) { - Blockchain.Bitcoin -> { - etAmount.filters = arrayOf(DecimalDigitsInputFilter(5)) - etFee.filters = arrayOf(DecimalDigitsInputFilter(5)) - } - Blockchain.BitcoinCash -> { - etAmount.filters = arrayOf(DecimalDigitsInputFilter(8)) - etFee.filters = arrayOf(DecimalDigitsInputFilter(8)) - } - Blockchain.Ethereum -> { - etAmount.filters = arrayOf(DecimalDigitsInputFilter(18)) - etFee.filters = arrayOf(DecimalDigitsInputFilter(18)) - } - else -> { - } - } - - // set listeners - btnLoad.setOnClickListener { - try { - val strAmount: String = etAmount.text.toString().replace(",", ".") - val strFee: String = etFee.text.toString().replace(",", ".") - val dblAmount: Double = strAmount.toDouble() - cryptonit.fee = strFee - - rlProgressBar.visibility = View.VISIBLE - tvProgressDescription.text = getString(R.string.cryptonit_request_withdrawal) - - cryptonit.requestWithdrawCoins(ctx.blockchain.currency, dblAmount, ctx.coinData!!.wallet) - } catch (e: Exception) { - etAmount.error = getString(R.string.prepare_transaction_error_unknown_amount_format) - } - } - - ivRefreshBalance.setOnClickListener { - cryptonit.username = etUsername.text.toString() - cryptonit.password = etPassword.text.toString() - cryptonit.saveAccountInfo() - doRequestBalance() - } - - cryptonit.setBalanceListener { response -> - tvBalance.text = response.results[0].balance.toString() - tvBalanceCurrency.text = response.results[0].currency // card!!.blockchain.currency - tvBalance.setTextColor(Color.BLACK) - rlProgressBar.visibility = View.INVISIBLE - btnLoad.visibility = View.VISIBLE - } - cryptonit.setErrorListener { throwable -> - throwable.printStackTrace() - rlProgressBar.visibility = View.INVISIBLE - tvError.visibility = View.VISIBLE - tvError.text = throwable.message - } - cryptonit.setWithdrawalListener { response -> - rlProgressBar.visibility = View.INVISIBLE - if (response.success != null && response.success!!) { - Toast.makeText(context, R.string.withdrawal_successful, Toast.LENGTH_LONG).show(); - navigateUp() - } else { - tvError.visibility = View.VISIBLE - tvError.text = response.errors.toString() - } - } - btnLoad.visibility = View.INVISIBLE - doRequestBalance() - } - - override fun onTagDiscovered(tag: Tag) { - try { - (activity as MainActivity).nfcManager.ignoreTag(tag) - } catch (e: IOException) { - e.printStackTrace() - } - } - - private fun doRequestBalance() { - if (cryptonit.haveAccountInfo()) { - rlProgressBar.visibility = View.VISIBLE - tvProgressDescription.text = getString(R.string.cryptonit_request_balance) - tvError.visibility = View.INVISIBLE - cryptonit.requestBalance(ctx.blockchain.currency) - } else { - tvError.visibility = View.VISIBLE - tvError.text = getString(R.string.cryptonit_not_enough_account_data) - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/ui/fragment/additional/PrepareKrakenWithdrawalFragment.kt b/app/src/main/java/com/tangem/ui/fragment/additional/PrepareKrakenWithdrawalFragment.kt deleted file mode 100644 index 00685a48df..0000000000 --- a/app/src/main/java/com/tangem/ui/fragment/additional/PrepareKrakenWithdrawalFragment.kt +++ /dev/null @@ -1,240 +0,0 @@ -package com.tangem.ui.fragment.additional - -import android.app.Activity -import android.app.AlertDialog -import android.content.Context -import android.content.DialogInterface -import android.graphics.Color -import android.nfc.NfcAdapter -import android.nfc.Tag -import android.os.Bundle -import android.view.View -import android.view.inputmethod.EditorInfo -import android.view.inputmethod.InputMethodManager -import android.widget.Toast -import com.tangem.Constant -import com.tangem.data.Blockchain -import com.tangem.data.network.Kraken -import com.tangem.ui.activity.MainActivity -import com.tangem.ui.fragment.BaseFragment -import com.tangem.ui.navigation.NavigationResultListener -import com.tangem.wallet.CoinEngineFactory -import com.tangem.wallet.R -import com.tangem.wallet.TangemContext -import kotlinx.android.synthetic.main.fragment_prepare_kraken_withdrawal.* -import java.io.IOException -import java.math.BigDecimal -import java.net.URI -import java.util.* - -class PrepareKrakenWithdrawalFragment : BaseFragment(), NavigationResultListener, - NfcAdapter.ReaderCallback { - - companion object { - val TAG: String = PrepareKrakenWithdrawalFragment::class.java.simpleName - } - - override val layoutId = R.layout.fragment_prepare_kraken_withdrawal - - private val ctx: TangemContext by lazy { TangemContext.loadFromBundle(context, arguments) } - private val kraken: Kraken by lazy { Kraken(context) } - - private var fee: BigDecimal? = null - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - tvKey.text = kraken.key - tvSecret.text = kraken.secretDescription - - tvCardID.text = ctx.card!!.cidDescription - tvWallet.text = ctx.coinData!!.wallet - val engine = CoinEngineFactory.create(ctx) - - tvCurrency.text = engine!!.balanceCurrency - - etAmount.setText(engine.convertToAmount(engine.convertToInternalAmount(ctx.card!!.denomination)).toValueString()) - etAmount.filters = engine.amountInputFilters - - etAmount.setOnEditorActionListener { lv, actionId, event -> - if (actionId == EditorInfo.IME_ACTION_DONE) { - val imm = lv.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager - imm.hideSoftInputFromWindow(lv.windowToken, 0) - lv.clearFocus() - true - } else { - false - } - } - - // set listeners - btnLoad.setOnClickListener { - try { - val strAmount: String = etAmount.text.toString().replace(",", ".") - - var dblAmount: Double = strAmount.toDouble() - rlProgressBar.visibility = View.VISIBLE - tvProgressDescription.text = getString(R.string.kraken_request_withdrawal) - - kraken.requestWithdrawInfo(ctx.blockchain.currency, dblAmount.toString(), ctx.coinData!!.wallet) - } catch (e: Exception) { - etAmount.error = getString(R.string.prepare_transaction_error_unknown_amount_format) - } - - } - -// ivCamera.setOnClickListener { navigator.showQrScanActivity(this, Constant.REQUEST_CODE_SCAN_QR) } - - ivRefreshBalance.setOnClickListener { doRequestBalance() } - - kraken.setBalanceListener { response -> - if (response.error != null && response.error.isNotEmpty()) { - tvError.visibility = View.VISIBLE - tvError.text = Arrays.toString(response.error) - } else { - when (ctx.blockchain) { - Blockchain.Ethereum -> { - tvBalance.text = response.result.XETH.trimEnd('0') - } - Blockchain.Bitcoin -> { - tvBalance.text = response.result.XXBT.trimEnd('0') - } - Blockchain.BitcoinCash -> { - tvBalance.text = response.result.BCH.trimEnd('0') - } - else -> { - tvBalance.text = "???" - } - } - tvBalanceCurrency.text = ctx.blockchain.currency - tvBalance.setTextColor(Color.BLACK) - btnLoad.visibility = View.VISIBLE - } - rlProgressBar.visibility = View.INVISIBLE - } - kraken.setErrorListener { throwable -> - throwable.printStackTrace() - rlProgressBar.visibility = View.INVISIBLE - tvError.visibility = View.VISIBLE - tvError.text = throwable.message - } - kraken.setWithdrawalListener { response -> - rlProgressBar.visibility = View.INVISIBLE - if (response.error != null && response.error.isNotEmpty()) { - tvError.visibility = View.VISIBLE - tvError.text = Arrays.toString(response.error) - } else { - Toast.makeText(context, "Withdrawal successful!", Toast.LENGTH_LONG).show() - navigateUp() - } - } - kraken.setWithdrawalInfoListener { response -> - rlProgressBar.visibility = View.INVISIBLE - if (response.error != null && response.error.isNotEmpty()) { - tvError.visibility = View.VISIBLE - tvError.text = Arrays.toString(response.error) - } else { - fee = BigDecimal(response.result.fee) - showConfirmDialog() - } - } - - btnLoad.visibility = View.INVISIBLE - doRequestBalance() - } - - // Method to show an alert dialog with yes, no and cancel button - private fun showConfirmDialog() { - // Late initialize an alert dialog object - lateinit var dialog: AlertDialog - - - // Initialize a new instance of alert dialog builder object - val builder = AlertDialog.Builder(context) - - // Set a title for alert dialog - builder.setTitle(R.string.kraken_please_confirm_withdraw) - - // Set a message for alert dialog - builder.setMessage(String.format("Continue with fee %s %s?", fee!!.toString().trimEnd('0'), ctx.blockchain.currency)) - - // On click listener for dialog buttons - val dialogClickListener = DialogInterface.OnClickListener { _, which -> - when (which) { - DialogInterface.BUTTON_POSITIVE -> { - try { - val strAmount: String = etAmount.text.toString().replace(",", ".") - - var dblAmount: Double = strAmount.toDouble() - - dblAmount += fee!!.toDouble() - - rlProgressBar.visibility = View.VISIBLE - tvProgressDescription.text = getString(R.string.kraken_request_withdrawal) - - kraken.requestWithdraw(ctx.blockchain.currency, dblAmount.toString(), ctx.coinData!!.wallet) - } catch (e: Exception) { - etAmount.error = getString(R.string.prepare_transaction_error_unknown_amount_format) - } - } - DialogInterface.BUTTON_NEGATIVE -> { - Toast.makeText(context, R.string.kraken_operation_canceled, Toast.LENGTH_LONG).show() - } - } - } - - // Set the alert dialog positive/yes button - builder.setPositiveButton(R.string.general_yes, dialogClickListener) - - // Set the alert dialog negative/no button - builder.setNegativeButton(R.string.general_no, dialogClickListener) - - - // Initialize the AlertDialog using builder object - dialog = builder.create() - - // Finally, display the alert dialog - dialog.show() - } - - private fun doRequestBalance() { - if (kraken!!.haveAccountInfo()) { - rlProgressBar.visibility = View.VISIBLE - tvProgressDescription.text = getString(R.string.kraken_request_balance) - tvError.visibility = View.INVISIBLE - kraken!!.requestBalance() - } else { - tvError.visibility = View.VISIBLE - tvError.text = getString(R.string.kraken_not_enough_account_data) - } - } - - override fun onNavigationResult(requestCode: String, resultCode: Int, data: Bundle?) { - if (resultCode == Activity.RESULT_OK && data != null && data.containsKey("QRCode")) { - when (requestCode) { - Constant.REQUEST_CODE_SCAN_QR -> { - val uri = URI(data.getString("QRCode")) - val query = uri.query - val params = query.split("&") - for (param in params) { - if (param.startsWith("key=")) kraken.key = param.substring(4) - else if (param.startsWith("secret=")) kraken.secret = param.substring(7) - } - tvKey!!.text = kraken.key - tvSecret!!.text = kraken.secretDescription - } - } - kraken.saveAccountInfo() - doRequestBalance() - } - } - - override fun onTagDiscovered(tag: Tag) { - try { - (activity as MainActivity).nfcManager.ignoreTag(tag) - } catch (e: IOException) { - e.printStackTrace() - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/ui/fragment/additional/TagFragment.kt b/app/src/main/java/com/tangem/ui/fragment/additional/TagFragment.kt deleted file mode 100644 index 8514ccf37c..0000000000 --- a/app/src/main/java/com/tangem/ui/fragment/additional/TagFragment.kt +++ /dev/null @@ -1,212 +0,0 @@ -package com.tangem.ui.fragment.additional - -import android.app.Activity -import android.content.ClipData -import android.content.ClipboardManager -import android.content.Context -import android.content.Intent -import android.os.Build -import android.os.Bundle -import android.text.Html -import android.view.View -import android.widget.Toast -import androidx.core.content.ContextCompat -import com.tangem.data.network.ServerApiCommon -import com.tangem.server_android.ServerApiTangem -import com.tangem.ui.fragment.BaseFragment -import com.tangem.ui.fragment.wallet.LoadedWalletFragment -import com.tangem.ui.fragment.wallet.LoadedWalletViewModel -import com.tangem.ui.navigation.NavigationResultListener -import com.tangem.util.LOG -import com.tangem.util.UtilHelper -import com.tangem.wallet.* -import com.tangem.wallet.xlmTag.XlmTagEngine -import kotlinx.android.synthetic.main.fr_loaded_wallet.* -import kotlinx.android.synthetic.main.layout_btn_details.* -import kotlinx.android.synthetic.main.layout_tangem_card.* - - -class TagFragment : BaseFragment(), NavigationResultListener { - override val layoutId = R.layout.fragment_tag - private lateinit var viewModel: LoadedWalletViewModel - private lateinit var ctx: TangemContext - private var serverApiCommon: ServerApiCommon = ServerApiCommon() - private var serverApiTangem: ServerApiTangem = ServerApiTangem() - - private var requestCounter: Int = 0 - set(value) { - field = value - LOG.i(LoadedWalletFragment.TAG, "requestCounter, set $field") - if (field <= 0) { - LOG.e(LoadedWalletFragment.TAG, "+++++++++++ FINISH REFRESH") - if (srl != null && srl.isRefreshing) - srl.isRefreshing = false - } else if (srl != null && !srl.isRefreshing) - srl.isRefreshing = true - } - - override fun onNavigationResult(requestCode: String, resultCode: Int, data: Bundle?) { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - ctx = TangemContext.loadFromBundle(context, arguments) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - val engine = XlmTagEngine(ctx) - - - btnLoad.visibility = View.GONE - btnDetails.visibility = View.GONE - btnExtract.text = getString(R.string.tag_claim) - btnExtract.isEnabled = false //TODO: enable when we implement extraction - btnExtract.backgroundTintList = - ContextCompat.getColorStateList(requireContext(), R.color.btn_dark) - - ivTangemCard.setImageResource(R.drawable.card_tgslix) - - tvBalance.setSingleLine(!engine.needMultipleLinesForBalance()) - tvWallet.text = ctx.coinData.wallet - tvWallet.setOnClickListener { shareWallet() } - btnExplore.setOnClickListener { startActivity(Intent(Intent.ACTION_VIEW, engine.walletExplorerUri)) } - btnCopy.setOnClickListener { shareWallet() } - btnNewScan.setOnClickListener { navigateUp() } - srl?.setOnRefreshListener { refresh(true) } - - requestBalanceAndUnspentTransactions() - } - - - private fun shareWallet() { - val txtShare = ctx.coinData.wallet - val clipboard = activity?.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager - clipboard.primaryClip = ClipData.newPlainText(txtShare, txtShare) - Toast.makeText(activity, R.string.loaded_wallet_toast_copied, Toast.LENGTH_LONG).show() - } - - - private fun update() { - ctx.coinData.setIsBalanceEqual(true) - - if (srl.isRefreshing) { - tvBalanceLine1.setTextColor(resources.getColor(R.color.primary)) - tvBalanceLine1.text = getString(R.string.loaded_wallet_verifying_in_blockchain) - tvBalanceLine2.text = "" - tvBalance.text = "" - tvBalanceEquivalent.text = "" - } else { - val validator = BalanceValidator() - validator.check(ctx, false) - context?.let { ContextCompat.getColor(it, validator.color) }?.let { tvBalanceLine1?.setTextColor(it) } - tvBalanceLine1?.text = getString(validator.firstLine) - tvBalanceLine2?.text = getString(validator.getSecondLine(false)) - } - - val engine = CoinEngineFactory.create(ctx) - when { - engine!!.hasBalanceInfo() -> { - @Suppress("DEPRECATION") val html = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) - Html.fromHtml(engine.balanceHTML, Html.FROM_HTML_MODE_LEGACY) - else - Html.fromHtml(engine.balanceHTML) - tvBalance.text = html - tvBalanceEquivalent.text = engine.balanceEquivalent - } - - ctx.card?.offlineBalance != null -> { - @Suppress("DEPRECATION") val html = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) - Html.fromHtml(engine.offlineBalanceHTML, Html.FROM_HTML_MODE_LEGACY) - else - Html.fromHtml(engine.offlineBalanceHTML) - tvBalance.text = html - } - - else -> tvBalance.text = "" - } - - - @Suppress("DEPRECATION") val html = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) - Html.fromHtml(ctx.blockchainName, Html.FROM_HTML_MODE_LEGACY) - else - Html.fromHtml(ctx.blockchainName) - tvBlockchain.text = html - - - } - - private fun requestBalanceAndUnspentTransactions() { - if (UtilHelper.isOnline(context as Activity)) { - val coinEngine = CoinEngineFactory.create(ctx) - requestCounter++ - coinEngine!!.requestBalanceAndUnspentTransactions( - object : CoinEngine.BlockchainRequestsCallbacks { - - override fun onComplete(success: Boolean) { - LOG.i(TAG, "requestBalanceAndUnspentTransactions onComplete: $success, request counter $requestCounter") - if (activity == null) return - requestCounter-- - if (!success) { - LOG.e(TAG, "requestBalanceAndUnspentTransactions ctx.error: " + ctx.error) - } - update() - } - - override fun onProgress() { - if (activity == null) return - LOG.i(TAG, "requestBalanceAndUnspentTransactions onProgress") -// update() - } - - override fun allowAdvance(): Boolean { - return try { - context?.let { UtilHelper.isOnline(it) }!! - } catch (e: KotlinNullPointerException) { - e.printStackTrace() - false - } - } - } - ) - } else { - ctx.error = getString(R.string.general_error_no_connection) - update() - } - } - - private fun refresh(clearData: Boolean = true) { - if (ctx.card == null) return - - // clear all card data and request again - ctx.coinData.clearInfo() - - if (clearData) { - ctx.error = null - ctx.message = null - } - - LOG.w(TAG, "============= START REFRESH") - requestCounter = 0 - srl?.isRefreshing = true - - update() - - ctx.coinData.setIsBalanceEqual(true) - - - requestBalanceAndUnspentTransactions() - - if (requestCounter == 0) { - // if no connection and no requests posted - srl?.isRefreshing = false - update() - } - } - - companion object { - val TAG: String = TagFragment::class.java.simpleName - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/ui/fragment/id/CameraFragment.kt b/app/src/main/java/com/tangem/ui/fragment/id/CameraFragment.kt deleted file mode 100644 index 24c6143377..0000000000 --- a/app/src/main/java/com/tangem/ui/fragment/id/CameraFragment.kt +++ /dev/null @@ -1,46 +0,0 @@ -package com.tangem.ui.fragment.id - -import android.app.Activity -import android.os.Bundle -import android.view.View -import androidx.core.os.bundleOf -import com.otaliastudios.cameraview.CameraListener -import com.otaliastudios.cameraview.PictureResult -import com.otaliastudios.cameraview.controls.Facing -import com.tangem.ui.fragment.BaseFragment -import com.tangem.wallet.R -import kotlinx.android.synthetic.main.fragment_camera.* - - -class CameraFragment : BaseFragment() { - override val layoutId = R.layout.fragment_camera - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - cvCamera?.setLifecycleOwner(viewLifecycleOwner) - - cvCamera?.addCameraListener(object : CameraListener() { - override fun onPictureTaken(result: PictureResult) { // Picture was taken! - - result.toBitmap(200, 200) { - navigateBackWithResult(Activity.RESULT_OK, bundleOf(PHOTO_KEY to it)) - } - } - }) - ivTakePicture?.setOnClickListener { cvCamera?.takePictureSnapshot() } - ivFlipCamera?.setOnClickListener { flipCamera() } - } - - private fun flipCamera() { - if (cvCamera?.facing == Facing.BACK) { - cvCamera?.facing = Facing.FRONT - } else { - cvCamera?.facing = Facing.BACK - } - } - - companion object{ - const val PHOTO_KEY = "photo" - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/ui/fragment/id/IdFragment.kt b/app/src/main/java/com/tangem/ui/fragment/id/IdFragment.kt deleted file mode 100644 index f37118f1ba..0000000000 --- a/app/src/main/java/com/tangem/ui/fragment/id/IdFragment.kt +++ /dev/null @@ -1,467 +0,0 @@ -package com.tangem.ui.fragment.id - -import android.app.Activity -import android.content.SharedPreferences -import android.graphics.BitmapFactory -import android.media.MediaPlayer -import android.nfc.NfcAdapter -import android.nfc.Tag -import android.nfc.tech.IsoDep -import android.os.Bundle -import android.util.Log -import android.view.View -import android.widget.Toast -import androidx.appcompat.app.AppCompatActivity -import androidx.core.content.ContextCompat -import com.google.firebase.crashlytics.FirebaseCrashlytics -import com.tangem.App -import com.tangem.Constant -import com.tangem.data.Blockchain -import com.tangem.data.network.ServerApiCommon -import com.tangem.server_android.ServerApiTangem -import com.tangem.server_android.model.CardVerifyAndGetInfo -import com.tangem.tangem_card.reader.CardProtocol -import com.tangem.tangem_card.tasks.VerifyCardTask -import com.tangem.tangem_card.util.Util -import com.tangem.tangem_sdk.android.reader.NfcReader -import com.tangem.ui.activity.MainActivity -import com.tangem.ui.dialog.NoExtendedLengthSupportDialog -import com.tangem.ui.dialog.WaitSecurityDelayDialog -import com.tangem.ui.fragment.BaseFragment -import com.tangem.util.LOG -import com.tangem.util.UtilHelper -import com.tangem.wallet.* -import kotlinx.android.synthetic.main.fragment_id.* -import kotlinx.android.synthetic.main.layout_btn_details.* -import kotlinx.android.synthetic.main.layout_id.* -import kotlinx.android.synthetic.main.layout_tangem_card.* -import kotlinx.android.synthetic.main.layout_tangem_card.rlProgressBar -import net.i2p.crypto.eddsa.Utils -import java.util.* -import kotlin.concurrent.timerTask - -class IdFragment : BaseFragment(), NfcAdapter.ReaderCallback, - CardProtocol.Notifications, SharedPreferences.OnSharedPreferenceChangeListener { - companion object { - val TAG: String = IdFragment::class.java.simpleName - } - - override val layoutId = R.layout.fragment_id - - private lateinit var ctx: TangemContext - private lateinit var mpSecondScanSound: MediaPlayer - private var serverApiCommon: ServerApiCommon = ServerApiCommon() - private var serverApiTangem: ServerApiTangem = ServerApiTangem() - private var lastTag: Tag? = null - private var lastReadSuccess = true - private var verifyCardTask: VerifyCardTask? = null - private var requestPIN2Count = 0 - private var timerHideErrorAndMessage: Timer? = null - private var newPIN = "" - private var newPIN2 = "" - private var photo: ByteArray? = null - private var cardProtocol: CardProtocol? = null - private var refreshAction: Runnable? = null - private var hasIdInfo: Boolean? = null - private var toast: Toast? = null - - private var requestCounter: Int = 0 - set(value) { - field = value - LOG.i(TAG, "requestCounter, set $field") - if (field <= 0) { - LOG.e(TAG, "+++++++++++ FINISH REFRESH") - if (srl != null && srl.isRefreshing) - srl.isRefreshing = false - } else if (srl != null && !srl.isRefreshing) - srl.isRefreshing = true - } - - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - ctx = TangemContext.loadFromBundle(context, arguments) - - lastTag = activity?.intent?.getParcelableExtra(Constant.EXTRA_LAST_DISCOVERED_TAG) - - if (arguments?.containsKey(NfcAdapter.EXTRA_TAG) == true) { - val tag = arguments!!.getParcelable(NfcAdapter.EXTRA_TAG) - if (tag != null) onTagDiscovered(tag) - } - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - mpSecondScanSound = MediaPlayer.create(activity, R.raw.scan_card_sound) - - tvIdNumber?.text = "ID # ${Utils.bytesToHex(ctx.card.cid)}" - - if (hasIdInfo == false) rlToolButtons?.visibility = View.VISIBLE - - // set listeners - srl.setOnRefreshListener { refresh() } - - btnDetails.setOnClickListener { - if (cardProtocol != null) { - val bundle = Bundle().apply { ctx.saveToBundle(this) } - navigateForResult(Constant.REQUEST_CODE_VERIFY_CARD, - R.id.action_idFragment_to_verifyCard, bundle) - } else { - (activity as MainActivity).toastHelper - .showSingleToast(context, getString(R.string.general_notification_scan_again_to_verify)) - } - } - - btnIssueNewId?.setOnClickListener { - val bundle = Bundle().apply { ctx.saveToBundle(this) } - navigateToDestination(R.id.action_idFragment_to_issueNewIdFragment, bundle) - } - - if (hasIdInfo == null) tvBalanceLine1?.text = "Reading... Hold the card firmly" - - btnNewScan.setOnClickListener { - navigateUp() - } - - // request card verify and get info listener - val cardVerifyAndGetInfoListener: ServerApiTangem.CardVerifyAndGetInfoListener = object : ServerApiTangem.CardVerifyAndGetInfoListener { - override fun onSuccess(cardVerifyAndGetArtworkResponse: CardVerifyAndGetInfo.Response?) { - LOG.i(TAG, "cardVerifyAndGetInfoListener onSuccess") - if (activity == null || !UtilHelper.isOnline(activity!!)) return - - val result = cardVerifyAndGetArtworkResponse?.results!![0] - if (result.error != null) { - ctx.card!!.isOnlineVerified = false - return - } - ctx.card!!.isOnlineVerified = result.passed - - requestCounter-- - updateViews() - - if (!result.passed) return - - if (App.localStorage.checkBatchInfoChanged(ctx.card!!, result)) { - LOG.w(TAG, "Batch ${result.batch} info changed to '$result'") - ivTangemCard.setImageBitmap(App.localStorage.getCardArtworkBitmap(ctx.card!!)) - App.localStorage.applySubstitution(ctx.card!!) - refresh() - } - } - - override fun onFail(message: String?) { - LOG.i(TAG, "cardVerifyAndGetInfoListener onFail") - if (activity == null || !UtilHelper.isOnline(activity!!)) return - requestCounter-- - updateViews() - } - } - serverApiTangem.setCardVerifyAndGetInfoListener(cardVerifyAndGetInfoListener) - refresh() - startVerify(lastTag) - } - - override fun onPause() { - super.onPause() - if (timerHideErrorAndMessage != null) { - timerHideErrorAndMessage!!.cancel() - timerHideErrorAndMessage = null - } - } - - override fun onStop() { - srl?.removeCallbacks(refreshAction) - toast?.cancel() - super.onStop() - } - - - override fun onTagDiscovered(tag: Tag) { - startVerify(tag) - } - - override fun onReadStart(cardProtocol: CardProtocol) { - rlProgressBar?.post { rlProgressBar?.visibility = View.VISIBLE } - } - - override fun onReadProgress(protocol: CardProtocol, progress: Int) { - - } - - override fun onReadFinish(cardProtocol: CardProtocol?) { - verifyCardTask = null - if (cardProtocol != null) { - if (cardProtocol.error == null) { - rlProgressBar?.post { - rlProgressBar?.visibility = View.GONE - this.cardProtocol = cardProtocol - if (!cardProtocol.card.isWalletPublicKeyValid) - refresh() - else - updateViews() - - if (cardProtocol.card.isIDCard) { - if (cardProtocol.card.hasIDCardData()) { - - ctx.card = cardProtocol.card - - tvBalanceLine1?.visibility = View.VISIBLE - - val idCardData = ctx.card.idCardData - tvFullName.text = idCardData.fullName - tvBirthDate.text = idCardData.birthday - tvGender.text = "Sex: ${idCardData.gender}" - ivPhoto.setImageBitmap(BitmapFactory.decodeByteArray(idCardData.photo, 0, idCardData.photo.size)) - - val inputs = "${idCardData.fullName};${idCardData.birthday}${idCardData.gender}" - val info = inputs.toByteArray() + idCardData.photo - ctx.card.idHash = Util.calculateSHA256(info) - - refresh() - - } else { - rlToolButtons?.visibility = View.VISIBLE - tvBalanceLine1?.text = resources.getString(R.string.id_empty).toUpperCase(Locale.ENGLISH) - tvBalanceLine1?.setTextSize(18F) - tvBalanceLine1?.visibility = View.VISIBLE - hasIdInfo = false - } - } - - mpSecondScanSound.start() - } - } else { - Log.d(this.javaClass.simpleName, - "Error while reading ID card: ${cardProtocol.error.localizedMessage}") - // remove last UIDs because of error and no card read - FirebaseCrashlytics.getInstance().recordException(cardProtocol.error) - rlProgressBar?.post { - lastReadSuccess = false - if (cardProtocol.error is CardProtocol.TangemException_ExtendedLengthNotSupported) - if (!NoExtendedLengthSupportDialog.allReadyShowed) - activity?.supportFragmentManager?.let { NoExtendedLengthSupportDialog().show(it, NoExtendedLengthSupportDialog.TAG) } - else { - toast = Toast.makeText(activity, R.string.general_notification_scan_again, Toast.LENGTH_SHORT) - toast?.show() - } - } - } - } - - rlProgressBar?.postDelayed({ - try { - rlProgressBar?.visibility = View.GONE - } catch (e: Exception) { - e.printStackTrace() - } - }, 500) - } - - override fun onReadCancel() { - verifyCardTask = null - rlProgressBar?.postDelayed({ - try { - rlProgressBar?.visibility = View.GONE - } catch (e: Exception) { - e.printStackTrace() - } - }, 500) - } - - override fun onReadWait(msec: Int) { - WaitSecurityDelayDialog.onReadWait(activity as AppCompatActivity?, msec) - } - - override fun onReadBeforeRequest(timeout: Int) { - WaitSecurityDelayDialog.onReadBeforeRequest(activity as AppCompatActivity?, timeout) - } - - override fun onReadAfterRequest() { - WaitSecurityDelayDialog.onReadAfterRequest(activity) - } - - override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { - - } - - fun updateViews() { - if (activity == null || this.view == null) return - - if (timerHideErrorAndMessage != null) { - timerHideErrorAndMessage!!.cancel() - timerHideErrorAndMessage = null - } - - if (ctx.card?.idCardData?.photo != null && (photo == null)) { - photo = ctx.card.idCardData.photo - ivPhoto.setImageBitmap(BitmapFactory.decodeByteArray(photo, 0, photo!!.size)) - - } - - if (ctx.hasError()) { - tvError?.visibility = View.VISIBLE - tvError?.text = ctx.error - } else { - tvError?.visibility = View.GONE - tvError?.text = "" - } - - if (ctx.message == null || ctx.message.isEmpty()) { - tvMessage?.text = "" - tvMessage?.visibility = View.GONE - } else { - tvMessage?.text = ctx.message - tvMessage?.visibility = View.VISIBLE - } - - if (tvError?.visibility == View.VISIBLE || tvMessage?.visibility == View.VISIBLE) { - timerHideErrorAndMessage = Timer() - timerHideErrorAndMessage?.schedule( - timerTask { - activity?.runOnUiThread { - tvMessage?.visibility = View.GONE - tvError?.visibility = View.GONE - // clear only already viewed messages - if (tvMessage?.text == ctx.message) ctx.message = null - if (tvError?.text == ctx.error) ctx.error = null - } - }, - 5000) - } - - if (srl.isRefreshing && ctx.card?.hasIDCardData() != false) { - tvBalanceLine1.setTextColor(resources.getColor(R.color.primary)) - tvBalanceLine1.text = getString(R.string.loaded_wallet_verifying_in_blockchain) - tvBalanceLine2.text = "" - } else if (ctx.card?.hasIDCardData() != false) { - - val validator = BalanceValidator() - // TODO why attest=false? - validator.check(ctx, false) - context?.let { ContextCompat.getColor(it, validator.color) }?.let { tvBalanceLine1?.setTextColor(it) } - tvBalanceLine1?.text = getString(validator.firstLine) - tvBalanceLine2?.text = getString(validator.getSecondLine(false)).format(ctx.card.idCardData.trustedAddress) - } - - if (ctx.card.hasIDCardData()) { - val idCardData = ctx.card.idCardData - tvFullName.text = idCardData.fullName - tvBirthDate.text = idCardData.birthday - tvGender.text = "Sex: ${idCardData.gender}" - ivPhoto.setImageBitmap(BitmapFactory.decodeByteArray(idCardData.photo, 0, idCardData.photo.size)) - rlToolButtons?.visibility = View.GONE - } - } - - private fun refresh() { - if (ctx.card == null) return - - // clear all card data and request again - ctx.coinData.clearInfo() - ctx.error = null - ctx.message = null - - LOG.w(TAG, "============= START REFRESH") - requestCounter = 0 - srl?.isRefreshing = true - - updateViews() - - // Bitcoin, Litecoin, BitcoinCash, Stellar - if (ctx.blockchain == Blockchain.Bitcoin || ctx.blockchain == Blockchain.BitcoinTestNet || ctx.blockchain == Blockchain.BitcoinDual || - ctx.blockchain == Blockchain.Litecoin || ctx.blockchain == Blockchain.BitcoinCash || - ctx.blockchain == Blockchain.Stellar || ctx.blockchain == Blockchain.StellarTestNet || ctx.blockchain == Blockchain.StellarAsset) { - ctx.coinData.setIsBalanceEqual(true) - } - - requestVerifyAndGetInfo() - - if (ctx.card.hasIDCardData()) requestBalanceAndUnspentTransactions() - - if (requestCounter == 0) { - // if no connection and no requests posted - srl?.isRefreshing = false - updateViews() - } - } - - private fun requestBalanceAndUnspentTransactions() { - if (UtilHelper.isOnline(context as Activity)) { - val coinEngine = CoinEngineFactory.create(ctx) - coinEngine?.defineWallet() - requestCounter++ - coinEngine!!.requestBalanceAndUnspentTransactions( - object : CoinEngine.BlockchainRequestsCallbacks { - - - override fun onComplete(success: Boolean) { - LOG.i(TAG, "requestBalanceAndUnspentTransactions onComplete: $success, request counter $requestCounter") - if (activity == null) return - requestCounter-- - if (!success) { - LOG.e(TAG, "requestBalanceAndUnspentTransactions ctx.error: " + ctx.error) - } - updateViews() - - } - - override fun onProgress() { - if (activity == null) return - LOG.i(TAG, "requestBalanceAndUnspentTransactions onProgress") - updateViews() - } - - override fun allowAdvance(): Boolean { - return try { - context?.let { UtilHelper.isOnline(it) }!! - } catch (e: KotlinNullPointerException) { - e.printStackTrace() - false - } - } - } - ) - } else { - ctx.error = getString(R.string.general_error_no_connection) - updateViews() - } - } - - private fun requestVerifyAndGetInfo() { - if (UtilHelper.isOnline(context as Activity)) { - if ((ctx.card!!.isOnlineVerified == null || !ctx.card!!.isOnlineVerified)) { - LOG.i(TAG, "requestVerifyAndGetInfo") - requestCounter++ - serverApiTangem.cardVerifyAndGetInfo(ctx.card) - } - } else { - ctx.error = getString(R.string.general_error_no_connection) - updateViews() - } - } - - private fun startVerify(tag: Tag?) { - try { - val isoDep = IsoDep.get(tag) - val uid = tag?.id - val sUID = Util.byteArrayToHexString(uid) - if (ctx.card.uid != sUID || cardProtocol != null) { - (activity as MainActivity).nfcManager.ignoreTag(isoDep.tag) - return - } - - if (lastReadSuccess) - isoDep.timeout = 1000 - else - isoDep.timeout = 65000 - - verifyCardTask = VerifyCardTask(ctx.card, NfcReader((activity as MainActivity).nfcManager, isoDep), - App.localStorage, App.pinStorage, App.firmwaresStorage, this) - verifyCardTask?.start() - } catch (e: Exception) { - e.printStackTrace() - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/ui/fragment/id/IssueNewIdFragment.kt b/app/src/main/java/com/tangem/ui/fragment/id/IssueNewIdFragment.kt deleted file mode 100644 index 8418037d86..0000000000 --- a/app/src/main/java/com/tangem/ui/fragment/id/IssueNewIdFragment.kt +++ /dev/null @@ -1,258 +0,0 @@ -package com.tangem.ui.fragment.id - -import android.app.Activity -import android.graphics.Bitmap -import android.os.Bundle -import android.os.Parcelable -import android.text.Editable -import android.text.TextWatcher -import android.util.Log -import android.view.View -import android.widget.EditText -import android.widget.RadioButton -import android.widget.Toast -import androidx.core.content.ContextCompat -import androidx.core.os.bundleOf -import com.tangem.tangem_card.util.Util -import com.tangem.ui.fragment.BaseFragment -import com.tangem.ui.navigation.NavigationResultListener -import com.tangem.wallet.CoinEngineFactory -import com.tangem.wallet.R -import com.tangem.wallet.TangemContext -import com.tangem.wallet.ethID.EthIdEngine -import kotlinx.android.parcel.Parcelize -import kotlinx.android.synthetic.main.fragment_issue_new_id.* -import java.io.ByteArrayOutputStream -import java.text.SimpleDateFormat -import java.util.* - - -class IssueNewIdFragment : BaseFragment(), NavigationResultListener { - lateinit var ctx: TangemContext - private var photo: Bitmap? = null - private var photoInBytes: ByteArray? = null - private var gender: Gender? = null - private var userIdData: UserIdData? = null - - override val layoutId = R.layout.fragment_issue_new_id - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - ctx = TangemContext.loadFromBundle(context, arguments) - - ivPhoto?.setOnClickListener { - navigateForResult(GET_PHOTO_REQUEST_CODE, R.id.action_issueNewIdFragment_to_cameraFragment) - } - - if (photo != null) ivPhoto?.setImageBitmap(photo) - - btnConfirm?.setOnClickListener { - if (!checkIfInfoProvided()) { - Toast.makeText(context, "Fill in all data first", Toast.LENGTH_LONG).show() - return@setOnClickListener - } - try { - processData() - } catch (exception: Exception) { - Log.e(this.javaClass.simpleName, - "Exception while processing data: ${exception.localizedMessage}") - Toast.makeText(context, "Check for correctness of data", Toast.LENGTH_LONG).show() - return@setOnClickListener - } - val bundle = bundleOf(USER_ID_DATA to userIdData) - ctx.saveToBundle(bundle) - navigateToDestination(R.id.action_issueNewIdFragment_to_validateIdFragment, bundle) - } - - rbFemale?.setOnClickListener { onRadioButtonClicked(it) } - rbMale?.setOnClickListener { onRadioButtonClicked(it) } - - DateInputMask(etBirthDate).listen() -// etBirthDate?.addTextChangedListener(MaskWatcher("##/##/####")) - - } - - private fun onRadioButtonClicked(view: View) { - if (view is RadioButton) { - when (view.getId()) { - R.id.rbFemale -> - if (view.isChecked) { - gender = Gender.F - } - R.id.rbMale -> - if (view.isChecked) { - gender = Gender.M - } - } - } - } - - override fun onNavigationResult(requestCode: String, resultCode: Int, data: Bundle?) { - if (requestCode == GET_PHOTO_REQUEST_CODE) { - if (resultCode == Activity.RESULT_OK) { - photo = data?.getParcelable(CameraFragment.PHOTO_KEY) - ivPhoto?.setImageBitmap(photo) - val out = ByteArrayOutputStream() - photo!!.compress(Bitmap.CompressFormat.JPEG, 100, out) - photoInBytes = out.toByteArray() - } - } - } - - - private fun checkIfInfoProvided(): Boolean { - return (etName.text.isNotBlank() && etSurname.text.isNotBlank() && etBirthDate.text.isNotBlank() && - (photo != null) && (gender != null)) - } - - private fun processData() { - val name = etName.text.toString() - val lastName = etSurname.text.toString() - val birthDate = convertDate(etBirthDate.text) - userIdData = UserIdData(name, lastName, birthDate, gender.toString(), photo!!) - val issuerExpireDate = processIssueExpireDate() - - ctx.card.setTlvIDCardData( - "$name $lastName", - birthDate, - gender.toString(), - photoInBytes, - issuerExpireDate.issueDate, - issuerExpireDate.expireDate, - (CoinEngineFactory.create(ctx) as? EthIdEngine)?.coinData?.approvalAddress - ) - - val inputs = "$name $lastName;$birthDate${gender.toString()}" - val info = inputs.toByteArray() + photoInBytes!! - ctx.card.idHash = Util.calculateSHA256(info) - - Log.d(this.javaClass.simpleName, - "ID info provided: $inputs,\n photo of size ${photoInBytes?.size}") - } - - private fun convertDate(inputDate: CharSequence): String { - val calendar = Calendar.getInstance() - calendar.clear() - val sdf = SimpleDateFormat("dd/MM/yyyy", Locale.ENGLISH) - try { - calendar.time = sdf.parse(inputDate.toString()) - } catch (exception: Error) { - etBirthDate.setTextColor(ContextCompat.getColor(requireContext(), R.color.bg_err)) - } - calendar.set(Calendar.HOUR_OF_DAY, 0) - calendar.timeZone = SimpleTimeZone(0, "UTC") - val dateFormat = SimpleDateFormat("yyyy.MM.dd", Locale.ENGLISH) - dateFormat.timeZone = SimpleTimeZone(0, "UTC") - return dateFormat.format(calendar.time) - } - - private fun processIssueExpireDate(): IssuerExpireDate { - val dt = Date() - val issueDate = SimpleDateFormat("yyyy-MM-dd", Locale.US).format(dt) - dt.year = dt.year + 10 - val expireDate = SimpleDateFormat("yyyy-MM-dd", Locale.US).format(dt) - return IssuerExpireDate(issueDate, expireDate) - } - - data class IssuerExpireDate(val issueDate: String, val expireDate: String) - - companion object { - const val SEPARATOR = ";" - const val COMPUTED_PUB_KEY = "ComputedPubKey" - const val GET_PHOTO_REQUEST_CODE = "GetPhoto" - const val USER_ID_DATA = "UserIdData" - } -} - - -enum class Gender { M, F } - -@Parcelize -data class UserIdData( - val firstName: String, - val lastName: String, - val birthDate: String, - val gender: String, - val photo: Bitmap -) : Parcelable - -class DateInputMask(val input: EditText) { - fun listen() { - input.addTextChangedListener(dateEntryWatcher) - } - - private val dateEntryWatcher = object : TextWatcher { - - var edited = false - val dividerCharacter = "/" - - override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { - if (edited) { - edited = false - return - } - - var working = getEditText() - - working = manageDateDivider(working, 2, start, before) - working = manageDateDivider(working, 5, start, before) - - edited = true - input.setText(working) - input.setSelection(input.text.length) - } - - private fun manageDateDivider(working: String, position: Int, start: Int, before: Int): String { - if (working.length == position) { - return if (before <= position && start < position) - working + dividerCharacter - else - working.dropLast(1) - } - return working - } - - private fun getEditText(): String { - return if (input.text.length >= 10) - input.text.toString().substring(0, 10) - else - input.text.toString() - } - - override fun afterTextChanged(s: Editable) {} - override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {} - } -} - -class MaskWatcher(private val mask: String) : TextWatcher { - private var isRunning = false - private var isDeleting = false - override fun beforeTextChanged(charSequence: CharSequence, start: Int, count: Int, after: Int) { - isDeleting = count > after - } - - override fun onTextChanged(charSequence: CharSequence, start: Int, before: Int, count: Int) {} - override fun afterTextChanged(editable: Editable) { - if (isRunning || isDeleting) { - return - } - isRunning = true - val editableLength = editable.length - if (editableLength < mask.length) { - if (mask[editableLength] != '#') { - editable.append(mask[editableLength]) - } else if (mask[editableLength - 1] != '#') { - editable.insert(editableLength - 1, mask, editableLength - 1, editableLength) - } - } - if (editableLength > mask.length) editable.delete(editable.lastIndex, editableLength) - isRunning = false - } - - companion object { - fun buildCpf(): MaskWatcher { - return MaskWatcher("###.###.###-##") - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/ui/fragment/id/ValidateIdFragment.kt b/app/src/main/java/com/tangem/ui/fragment/id/ValidateIdFragment.kt deleted file mode 100644 index ebbc5c2191..0000000000 --- a/app/src/main/java/com/tangem/ui/fragment/id/ValidateIdFragment.kt +++ /dev/null @@ -1,329 +0,0 @@ -package com.tangem.ui.fragment.id - -import android.app.Activity -import android.content.res.ColorStateList -import android.graphics.Color -import android.media.MediaPlayer -import android.nfc.NfcAdapter -import android.nfc.Tag -import android.nfc.tech.IsoDep -import android.os.Bundle -import android.util.Log -import android.view.View -import androidx.activity.OnBackPressedCallback -import com.google.firebase.crashlytics.FirebaseCrashlytics -import com.tangem.App -import com.tangem.Constant -import com.tangem.tangem_card.data.TangemCard -import com.tangem.tangem_card.reader.CardProtocol -import com.tangem.tangem_card.tasks.OneTouchSignTask -import com.tangem.tangem_card.util.Util -import com.tangem.tangem_sdk.android.data.PINStorage -import com.tangem.tangem_sdk.android.nfc.NfcDeviceAntennaLocation -import com.tangem.tangem_sdk.android.reader.NfcReader -import com.tangem.tangem_sdk.data.EXTRA_TANGEM_CARD -import com.tangem.tangem_sdk.data.EXTRA_TANGEM_CARD_UID -import com.tangem.tangem_sdk.data.asBundle -import com.tangem.ui.activity.MainActivity -import com.tangem.ui.dialog.NoExtendedLengthSupportDialog -import com.tangem.ui.dialog.WaitSecurityDelayDialog -import com.tangem.ui.fragment.BaseFragment -import com.tangem.ui.navigation.NavigationResultListener -import com.tangem.util.LOG -import com.tangem.wallet.CoinEngine -import com.tangem.wallet.CoinEngineFactory -import com.tangem.wallet.R -import com.tangem.wallet.TangemContext -import kotlinx.android.synthetic.main.layout_progress_horizontal.* -import kotlinx.android.synthetic.main.layout_touch_card.* - -class ValidateIdFragment : BaseFragment(), NavigationResultListener, - NfcAdapter.ReaderCallback, CardProtocol.Notifications { - - companion object { - val TAG: String = ValidateIdFragment::class.java.simpleName - } - - override val layoutId = R.layout.fragment_validate_id - - private lateinit var ctx: TangemContext - private lateinit var mpFinishSignSound: MediaPlayer - - private lateinit var nfcDeviceAntenna: NfcDeviceAntennaLocation - - private var signTransactionTask: OneTouchSignTask? = null - - private lateinit var amount: CoinEngine.Amount - private lateinit var fee: CoinEngine.Amount - private var isIncludeFee = true - private var outAddressStr: String? = null - private var lastReadSuccess = true - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - ctx = TangemContext.loadFromBundle(context, arguments) - - val callback = object : OnBackPressedCallback(true) { - override fun handleOnBackPressed() { - navigateBackWithResult(Activity.RESULT_CANCELED) - } - } - requireActivity().onBackPressedDispatcher.addCallback(this, callback) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - mpFinishSignSound = MediaPlayer.create(context, R.raw.scan_card_sound) - - // init NFC Antenna - nfcDeviceAntenna = NfcDeviceAntennaLocation(requireContext(), ivHandCardHorizontal, ivHandCardVertical, llHand, llNfc) - nfcDeviceAntenna.init() - - progressBar.progressTintList = ColorStateList.valueOf(Color.DKGRAY) - progressBar.visibility = View.INVISIBLE - } - - override fun onPause() { - signTransactionTask?.cancel(true) - super.onPause() - } - - override fun onNavigationResult(requestCode: String, resultCode: Int, data: Bundle?) { - if (requestCode == Constant.REQUEST_CODE_SEND_TRANSACTION_) { - navigateBackWithResult(resultCode, data) - } - } - - override fun onTagDiscovered(tag: Tag) { - try { - // get IsoDep handle and run cardReader thread - val isoDep = IsoDep.get(tag) - val uid = tag.id - val sUID = Util.byteArrayToHexString(uid) - - if (lastReadSuccess) - isoDep.timeout = ctx.card.pauseBeforePIN2 + 5000 - else - isoDep.timeout = ctx.card.pauseBeforePIN2 + 65000 - - val coinEngine = CoinEngineFactory.create(ctx) - coinEngine?.defineWallet() - - coinEngine?.setOnNeedSendTransaction { tx -> - if (tx != null) { - val data = Bundle() - ctx.saveToBundle(data) - data.putByteArray(Constant.EXTRA_TX, tx) - - Log.d(this.javaClass.simpleName, - "Transaction is ready to be sent.") - - navigateForResult( - Constant.REQUEST_CODE_SEND_TRANSACTION_, - R.id.action_validateIdFragment_to_writeIdFragment, - data) - } - } - - coinEngine?.requestBalanceAndUnspentTransactions(object : CoinEngine.BlockchainRequestsCallbacks { - override fun onComplete(success: Boolean?) { - val transactionToSign = coinEngine.constructTransaction(null, null, true, null) - - val transaction = object : OneTouchSignTask.TransactionToSign{ - override fun isIssuerCanSignTransaction(card: TangemCard?): Boolean { - return false - } - - override fun isIssuerCanSignData(card: TangemCard?): Boolean { - return false - } - - override fun getHashAlgToSign(card: TangemCard?): String { - return transactionToSign.hashAlgToSign - } - - override fun getRawDataToSign(card: TangemCard?): ByteArray { - return transactionToSign.rawDataToSign - } - - override fun isSigningOnCardSupported(card: TangemCard?): Boolean { - return true - } - - override fun onSignCompleted(card: TangemCard?, signature: ByteArray?) { - transactionToSign.onSignCompleted(signature) - } - - override fun getHashesToSign(card: TangemCard?): Array { - return transactionToSign.hashesToSign - } - - override fun getIssuerTransactionSignature(card: TangemCard?, dataToSignByIssuer: ByteArray?): ByteArray { - return transactionToSign.getIssuerTransactionSignature(dataToSignByIssuer) - } - - } - - PINStorage.setPIN2(PINStorage.getDefaultPIN2()) - Log.d(this.javaClass.simpleName, - "Ready to sing transaction, launching OneTouchSignTask") - signTransactionTask = OneTouchSignTask(NfcReader((activity as MainActivity).nfcManager, isoDep), - App.localStorage, App.pinStorage, this@ValidateIdFragment, transaction) - signTransactionTask?.start() - } - - override fun onProgress() { - } - - override fun allowAdvance(): Boolean { - return true - } - }) - - - } catch (e: CardProtocol.TangemException_WrongAmount) { - try { - val data = Bundle() - data.putString(Constant.EXTRA_MESSAGE, getString(R.string.send_transaction_error_wrong_amount)) - data.putString(EXTRA_TANGEM_CARD_UID, ctx.card.uid) - data.putBundle(EXTRA_TANGEM_CARD, ctx.card.asBundle) - navigateBackWithResult(Activity.RESULT_CANCELED, data) - } catch (e: Exception) { - e.printStackTrace() - } - } catch (e: Exception) { - e.printStackTrace() - } - } - - override fun onReadStart(cardProtocol: CardProtocol) { - rlProgressBar?.post { rlProgressBar.visibility = View.VISIBLE } - - progressBar?.post { - progressBar?.visibility = View.VISIBLE - progressBar?.progress = 5 - } - } - - override fun onReadProgress(protocol: CardProtocol, progress: Int) { - progressBar?.post { progressBar?.progress = progress } - } - - override fun onReadFinish(cardProtocol: CardProtocol?) { - signTransactionTask = null - if (cardProtocol != null) { - if (cardProtocol.error == null) { - rlProgressBar?.post { rlProgressBar?.visibility = View.GONE } - - progressBar?.post { - progressBar?.progress = 100 - progressBar?.progressTintList = ColorStateList.valueOf(Color.GREEN) - } - - mpFinishSignSound.start() - } else { - Log.d(this.javaClass.simpleName, - "Error while signing with ID Issuer card: ${cardProtocol.error.localizedMessage}") - FirebaseCrashlytics.getInstance().recordException(cardProtocol.error) - lastReadSuccess = false - if (cardProtocol.error.javaClass == CardProtocol.TangemException_InvalidPIN::class.java) { - progressBar?.post { - progressBar?.progress = 100 - progressBar?.progressTintList = ColorStateList.valueOf(Color.RED) - } - progressBar?.postDelayed({ - try { - progressBar?.progress = 0 - progressBar?.progressTintList = ColorStateList.valueOf(Color.DKGRAY) - progressBar?.visibility = View.INVISIBLE - val data = Bundle() - data.putString(Constant.EXTRA_MESSAGE, getString(R.string.send_transaction_error_cannot_sign)) - data.putString(EXTRA_TANGEM_CARD_UID, cardProtocol.card.uid) - data.putBundle(EXTRA_TANGEM_CARD, cardProtocol.card.asBundle) - navigateBackWithResult(Constant.RESULT_INVALID_PIN_, data) - } catch (e: Exception) { - e.printStackTrace() - } - }, 500) - } else { - if (cardProtocol.error is CardProtocol.TangemException_WrongAmount) { - try { - val data = Bundle() - data.putString(Constant.EXTRA_MESSAGE, getString(R.string.send_transaction_error_wrong_amount)) - data.putString(EXTRA_TANGEM_CARD_UID, cardProtocol.card.uid) - data.putBundle(EXTRA_TANGEM_CARD, cardProtocol.card.asBundle) - navigateBackWithResult(Activity.RESULT_CANCELED, data) - } catch (e: Exception) { - e.printStackTrace() - } - } - progressBar?.post { - if (cardProtocol.error is CardProtocol.TangemException_ExtendedLengthNotSupported) { - if (!NoExtendedLengthSupportDialog.allReadyShowed) { - NoExtendedLengthSupportDialog.message = getText(R.string.dialog_the_nfc_adapter_length_apdu).toString() + "\n" + getText(R.string.dialog_the_nfc_adapter_length_apdu_advice).toString() - NoExtendedLengthSupportDialog().show(requireFragmentManager(), NoExtendedLengthSupportDialog.TAG) - } - } else { - (activity as? MainActivity)?.toastHelper?.showSingleToast( - context, - getString(R.string.general_notification_scan_again) - ) - } - progressBar?.progress = 100 - progressBar?.progressTintList = ColorStateList.valueOf(Color.RED) - } - } - } - } - - rlProgressBar?.postDelayed({ - try { - rlProgressBar?.visibility = View.GONE - } catch (e: Exception) { - e.printStackTrace() - } - }, 500) - - progressBar?.postDelayed({ - try { - progressBar?.progress = 0 - progressBar?.progressTintList = ColorStateList.valueOf(Color.DKGRAY) - progressBar?.visibility = View.INVISIBLE - } catch (e: Exception) { - e.printStackTrace() - } - }, 500) - } - - override fun onReadCancel() { - signTransactionTask = null - - progressBar?.postDelayed({ - try { - progressBar?.progress = 0 - progressBar?.progressTintList = ColorStateList.valueOf(Color.DKGRAY) - progressBar?.visibility = View.INVISIBLE - } catch (e: Exception) { - e.printStackTrace() - } - }, 500) - } - - - override fun onReadBeforeRequest(timeout: Int) { - LOG.i(TAG, "onReadBeforeRequest timeout $timeout") - activity?.let { WaitSecurityDelayDialog.onReadBeforeRequest(it, timeout) } - } - - override fun onReadAfterRequest() { - LOG.i(TAG, "onReadAfterRequest") - activity?.let { WaitSecurityDelayDialog.onReadAfterRequest(it) } - } - - override fun onReadWait(msec: Int) { - LOG.i(TAG, "onReadWait msec $msec") - activity?.let { WaitSecurityDelayDialog.onReadWait(it, msec) } - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/ui/fragment/id/WriteIdFragment.kt b/app/src/main/java/com/tangem/ui/fragment/id/WriteIdFragment.kt deleted file mode 100644 index e0baa7b79d..0000000000 --- a/app/src/main/java/com/tangem/ui/fragment/id/WriteIdFragment.kt +++ /dev/null @@ -1,295 +0,0 @@ -package com.tangem.ui.fragment.id - -import android.app.Activity -import android.content.res.ColorStateList -import android.graphics.Color -import android.media.MediaPlayer -import android.nfc.NfcAdapter -import android.nfc.Tag -import android.nfc.tech.IsoDep -import android.os.Bundle -import android.util.Log -import android.view.View -import androidx.activity.OnBackPressedCallback -import com.google.firebase.crashlytics.FirebaseCrashlytics -import com.tangem.App -import com.tangem.Constant -import com.tangem.tangem_card.reader.CardProtocol -import com.tangem.tangem_card.tasks.WriteIDCardTask -import com.tangem.tangem_card.util.Util -import com.tangem.tangem_sdk.android.nfc.NfcDeviceAntennaLocation -import com.tangem.tangem_sdk.android.reader.NfcReader -import com.tangem.tangem_sdk.data.EXTRA_TANGEM_CARD -import com.tangem.tangem_sdk.data.EXTRA_TANGEM_CARD_UID -import com.tangem.tangem_sdk.data.asBundle -import com.tangem.ui.activity.MainActivity -import com.tangem.ui.dialog.NoExtendedLengthSupportDialog -import com.tangem.ui.dialog.WaitSecurityDelayDialog -import com.tangem.ui.fragment.BaseFragment -import com.tangem.ui.navigation.NavigationResultListener -import com.tangem.util.LOG -import com.tangem.util.UtilHelper -import com.tangem.wallet.CoinEngine -import com.tangem.wallet.CoinEngineFactory -import com.tangem.wallet.R -import com.tangem.wallet.TangemContext -import kotlinx.android.synthetic.main.layout_progress_horizontal.* -import kotlinx.android.synthetic.main.layout_touch_card.* -import kotlinx.android.synthetic.tangemAccess.fragment_sign_transaction.* - -class WriteIdFragment : BaseFragment(), NavigationResultListener, - NfcAdapter.ReaderCallback, CardProtocol.Notifications { - - companion object { - val TAG: String = WriteIdFragment::class.java.simpleName - } - - override val layoutId = R.layout.fragment_write_id - - private lateinit var ctx: TangemContext - private lateinit var tx: ByteArray - private lateinit var mpFinishSignSound: MediaPlayer - - private var idWasWritten = false - - private lateinit var nfcDeviceAntenna: NfcDeviceAntennaLocation - - private var writeIDCardTask: WriteIDCardTask? = null - - private var lastReadSuccess = true - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - ctx = TangemContext.loadFromBundle(context, arguments) - tx = arguments?.getByteArray(Constant.EXTRA_TX)!! - - val callback = object : OnBackPressedCallback(true) { - override fun handleOnBackPressed() { - navigateBackWithResult(Activity.RESULT_CANCELED) - } - } - requireActivity().onBackPressedDispatcher.addCallback(this, callback) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - mpFinishSignSound = MediaPlayer.create(context, R.raw.scan_card_sound) - - // init NFC Antenna - nfcDeviceAntenna = NfcDeviceAntennaLocation(requireContext(), ivHandCardHorizontal, ivHandCardVertical, llHand, llNfc) - nfcDeviceAntenna.init() - - tvCardID.text = ctx.card!!.cidDescription - progressBar.progressTintList = ColorStateList.valueOf(Color.DKGRAY) - progressBar.visibility = View.INVISIBLE - tvProgressBar?.text = "Writing..." - } - - override fun onPause() { - writeIDCardTask?.cancel(true) - super.onPause() - } - - override fun onStop() { - writeIDCardTask?.cancel(true) - super.onStop() - } - - override fun onNavigationResult(requestCode: String, resultCode: Int, data: Bundle?) { - if (requestCode == Constant.REQUEST_CODE_SEND_TRANSACTION_) { - navigateBackWithResult(resultCode, data) - } - } - - override fun onTagDiscovered(tag: Tag) { - try { - // get IsoDep handle and run cardReader thread - val isoDep = IsoDep.get(tag) - val uid = tag.id - val sUID = Util.byteArrayToHexString(uid) - - if (sUID == ctx.card.uid || !idWasWritten) { - if (lastReadSuccess) - isoDep.timeout = ctx.card.pauseBeforePIN2 + 5000 - else - isoDep.timeout = ctx.card.pauseBeforePIN2 + 65000 - - writeIDCardTask = WriteIDCardTask(ctx.card, NfcReader((activity as MainActivity).nfcManager, isoDep), - App.localStorage, App.pinStorage, ctx.card.idCardData, ctx.card.issuer.privateDataKey, this) - writeIDCardTask?.start() - } else - (activity as MainActivity).nfcManager.ignoreTag(isoDep.tag) - - } catch (e: CardProtocol.TangemException_WrongAmount) { - try { - val data = Bundle() - data.putString(Constant.EXTRA_MESSAGE, getString(R.string.send_transaction_error_wrong_amount)) - data.putString(EXTRA_TANGEM_CARD_UID, ctx.card.uid) - data.putBundle(EXTRA_TANGEM_CARD, ctx.card.asBundle) - navigateBackWithResult(Activity.RESULT_CANCELED, data) - } catch (e: Exception) { - e.printStackTrace() - } - } catch (e: Exception) { - e.printStackTrace() - } - } - - override fun onReadStart(cardProtocol: CardProtocol) { - rlProgressBar?.post { rlProgressBar.visibility = View.VISIBLE } - - progressBar?.post { - progressBar?.visibility = View.VISIBLE - progressBar?.progress = 5 - } - } - - override fun onReadProgress(protocol: CardProtocol, progress: Int) { - progressBar?.post { progressBar?.progress = progress } - } - - override fun onReadFinish(cardProtocol: CardProtocol?) { - writeIDCardTask = null - if (cardProtocol != null) { - if (cardProtocol.error == null) { - rlProgressBar?.post { rlProgressBar?.visibility = View.GONE } - - progressBar?.post { - progressBar?.progress = 100 - progressBar?.progressTintList = ColorStateList.valueOf(Color.GREEN) - } - - mpFinishSignSound.start() - - idWasWritten = true - - val coinEngine = CoinEngineFactory.create(ctx) - - coinEngine!!.requestSendTransaction( - object : CoinEngine.BlockchainRequestsCallbacks { - override fun onComplete(success: Boolean) { - if (success) { - navigateUp(R.id.main) - } else { - navigateUp(R.id.issueNewIdFragment) - } - } - - override fun onProgress() { - } - - override fun allowAdvance(): Boolean { - return UtilHelper.isOnline(requireContext()) - } - }, - tx - ) - - } else { - Log.d(this.javaClass.simpleName, - "Error while writing on ID card: ${cardProtocol.error}") - lastReadSuccess = false - FirebaseCrashlytics.getInstance().recordException(cardProtocol.error) - if (cardProtocol.error.javaClass == CardProtocol.TangemException_InvalidPIN::class.java) { - progressBar?.post { - progressBar?.progress = 100 - progressBar?.progressTintList = ColorStateList.valueOf(Color.RED) - } - progressBar?.postDelayed({ - try { - progressBar?.progress = 0 - progressBar?.progressTintList = ColorStateList.valueOf(Color.DKGRAY) - progressBar?.visibility = View.INVISIBLE - val data = Bundle() - data.putString(Constant.EXTRA_MESSAGE, getString(R.string.send_transaction_error_cannot_sign)) - data.putString(EXTRA_TANGEM_CARD_UID, cardProtocol.card.uid) - data.putBundle(EXTRA_TANGEM_CARD, cardProtocol.card.asBundle) - navigateBackWithResult(Constant.RESULT_INVALID_PIN_, data) - } catch (e: Exception) { - e.printStackTrace() - } - }, 500) - } else { - if (cardProtocol.error is CardProtocol.TangemException_WrongAmount) { - try { - val data = Bundle() - data.putString(Constant.EXTRA_MESSAGE, getString(R.string.send_transaction_error_wrong_amount)) - data.putString(EXTRA_TANGEM_CARD_UID, cardProtocol.card.uid) - data.putBundle(EXTRA_TANGEM_CARD, cardProtocol.card.asBundle) - navigateBackWithResult(Activity.RESULT_CANCELED, data) - } catch (e: Exception) { - e.printStackTrace() - } - } - progressBar?.post { - if (cardProtocol.error is CardProtocol.TangemException_ExtendedLengthNotSupported) { - if (!NoExtendedLengthSupportDialog.allReadyShowed) { - NoExtendedLengthSupportDialog.message = getText(R.string.dialog_the_nfc_adapter_length_apdu).toString() + "\n" + getText(R.string.dialog_the_nfc_adapter_length_apdu_advice).toString() - NoExtendedLengthSupportDialog().show(requireFragmentManager(), NoExtendedLengthSupportDialog.TAG) - } - } else { - if (!idWasWritten) { - (activity as MainActivity).toastHelper.showSingleToast( - context, - getString(R.string.general_notification_scan_again) - ) - } - } - progressBar?.progress = 100 - progressBar?.progressTintList = ColorStateList.valueOf(Color.RED) - } - } - } - } - - rlProgressBar?.postDelayed({ - try { - rlProgressBar?.visibility = View.GONE - } catch (e: Exception) { - e.printStackTrace() - } - }, 500) - - progressBar?.postDelayed({ - try { - progressBar?.progress = 0 - progressBar?.progressTintList = ColorStateList.valueOf(Color.DKGRAY) - progressBar?.visibility = View.INVISIBLE - } catch (e: Exception) { - e.printStackTrace() - } - }, 500) - } - - override fun onReadCancel() { - writeIDCardTask = null - - progressBar?.postDelayed({ - try { - progressBar?.progress = 0 - progressBar?.progressTintList = ColorStateList.valueOf(Color.DKGRAY) - progressBar?.visibility = View.INVISIBLE - } catch (e: Exception) { - e.printStackTrace() - } - }, 500) - } - - - override fun onReadBeforeRequest(timeout: Int) { - LOG.i(TAG, "onReadBeforeRequest timeout $timeout") - activity?.let { WaitSecurityDelayDialog.onReadBeforeRequest(it, timeout) } - } - - override fun onReadAfterRequest() { - LOG.i(TAG, "onReadAfterRequest") - activity?.let { WaitSecurityDelayDialog.onReadAfterRequest(it) } - } - - override fun onReadWait(msec: Int) { - LOG.i(TAG, "onReadWait msec $msec") - activity?.let { WaitSecurityDelayDialog.onReadWait(it, msec) } - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/ui/fragment/pin/PinRequestFragment.kt b/app/src/main/java/com/tangem/ui/fragment/pin/PinRequestFragment.kt deleted file mode 100644 index dc4206c2a1..0000000000 --- a/app/src/main/java/com/tangem/ui/fragment/pin/PinRequestFragment.kt +++ /dev/null @@ -1,336 +0,0 @@ -package com.tangem.ui.fragment.pin - -import android.Manifest -import android.annotation.SuppressLint -import android.annotation.TargetApi -import android.app.Activity -import android.app.KeyguardManager -import android.content.Context -import android.content.pm.PackageManager -import android.hardware.fingerprint.FingerprintManager -import android.nfc.NfcAdapter -import android.nfc.Tag -import android.os.Build -import android.os.Bundle -import android.text.TextUtils -import android.util.Log -import android.view.View -import android.widget.Button -import androidx.core.app.ActivityCompat -import com.tangem.Constant -import com.tangem.data.fingerprint.FingerprintHelper -import com.tangem.data.fingerprint.StartFingerprintReaderTask -import com.tangem.tangem_card.data.TangemCard -import com.tangem.tangem_sdk.android.data.PINStorage -import com.tangem.tangem_sdk.data.EXTRA_TANGEM_CARD -import com.tangem.tangem_sdk.data.EXTRA_TANGEM_CARD_UID -import com.tangem.tangem_sdk.data.loadFromBundle -import com.tangem.ui.activity.MainActivity -import com.tangem.ui.fragment.BaseFragment -import com.tangem.util.LOG -import com.tangem.wallet.R -import com.tangem.wallet.TangemContext -import kotlinx.android.synthetic.main.fragment_pin_request.* -import kotlinx.android.synthetic.main.layout_pin_buttons.* -import java.io.IOException - -class PinRequestFragment : BaseFragment(), NfcAdapter.ReaderCallback, FingerprintHelper.FingerprintHelperListener { - companion object { - val TAG: String = PinRequestFragment::class.java.simpleName - - fun callingIntent(mode: String): Bundle { - val bundle = Bundle() - bundle.putString(Constant.EXTRA_MODE, mode) - return bundle - } - - fun callingIntentRequestPin(mode: String, ctx: TangemContext, newPIN: String): Bundle { - val bundle = Bundle() - bundle.putString(Constant.EXTRA_MODE, mode) - bundle.putString(Constant.EXTRA_NEW_PIN, newPIN) - ctx.saveToBundle(bundle) - return bundle - } - - fun callingIntentRequestPin2(mode: String, ctx: TangemContext, newPIN2: String): Bundle { - val bundle = Bundle() - bundle.putString(Constant.EXTRA_MODE, mode) - bundle.putString(Constant.EXTRA_NEW_PIN_2, newPIN2) - ctx.saveToBundle(bundle) - return bundle - } - - fun callingIntentRequestPin2(mode: String, ctx: TangemContext): Bundle { - val bundle = Bundle() - bundle.putString(Constant.EXTRA_MODE, mode) - ctx.saveToBundle(bundle) - return bundle - } - - fun callingIntentConfirmPin(mode: String, newPIN: String): Bundle { - val bundle = Bundle() - bundle.putString(Constant.EXTRA_MODE, mode) - bundle.putString(Constant.EXTRA_NEW_PIN, newPIN) - return bundle - } - - fun callingIntentConfirmPin2(mode: String, newPIN2: String): Bundle { - val bundle = Bundle() - bundle.putString(Constant.EXTRA_MODE, mode) - bundle.putString(Constant.EXTRA_NEW_PIN_2, newPIN2) - return bundle - } - } - - override val layoutId = R.layout.fragment_pin_request - - val mode: Mode by lazy { Mode.valueOf(arguments?.getString(Constant.EXTRA_MODE) ?: "") } - - private var allowFingerprint = false - var startFingerprintReaderTask: StartFingerprintReaderTask? = null - private var fingerprintManager: FingerprintManager? = null - private var fingerprintHelper: FingerprintHelper? = null - - enum class Mode { - RequestPIN, RequestPIN2, RequestNewPIN, RequestNewPIN2, ConfirmNewPIN, ConfirmNewPIN2 - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - if (mode == Mode.RequestNewPIN) - if (PINStorage.haveEncryptedPIN()) { - allowFingerprint = true - tvPinPrompt.setText(R.string.pin_request_enter_new_pin_or_use_fingerprint_scanner) - } else - tvPinPrompt.setText(R.string.pin_request_enter_new_pin) - else if (mode == Mode.ConfirmNewPIN) - tvPinPrompt.setText(R.string.pin_request_confirm_new_pin) - else if (mode == Mode.RequestPIN) - if (PINStorage.haveEncryptedPIN()) { - allowFingerprint = true - tvPinPrompt.setText(R.string.pin_request_enter_pin_or_use_fingerprint_scanner) - } else - tvPinPrompt.setText(R.string.pin_request_enter_pin) - else if (mode == Mode.RequestNewPIN2) - if (PINStorage.haveEncryptedPIN2()) { - allowFingerprint = true - tvPinPrompt.setText(R.string.pin_request_prompt_new_pin_2_or_fingerprint) - } else - tvPinPrompt.setText(R.string.pin_request_new_pin_2) - else if (mode == Mode.ConfirmNewPIN2) - tvPinPrompt.setText(R.string.pin_request_confirm_new_pin_2) - else if (mode == Mode.RequestPIN2) { - val uid = arguments?.getString(EXTRA_TANGEM_CARD_UID) - val card = TangemCard(uid) - card.loadFromBundle(arguments?.getBundle(EXTRA_TANGEM_CARD) ?: Bundle()) - - if (card.PIN2 == TangemCard.PIN2_Mode.DefaultPIN2 || card.PIN2 == TangemCard.PIN2_Mode.Unchecked) { - // if we know PIN2 or not try default previously - use it - PINStorage.setPIN2(PINStorage.getDefaultPIN2()) - navigateBackWithResult(Activity.RESULT_OK) - return - } - - if (PINStorage.haveEncryptedPIN2()) { - allowFingerprint = true - tvPinPrompt.setText(R.string.pin_request_enter_pin_2_or_use_fingerprint_scanner) - } else - tvPinPrompt.setText(R.string.pin_request_prompt_enter_pin_2) - } - - if (!allowFingerprint) - tvPinPrompt.visibility = View.GONE - else - tvPinPrompt.visibility = View.VISIBLE - - // set listeners - btn0.setOnClickListener { buttonClick(btn0) } - btn1.setOnClickListener { buttonClick(btn1) } - btn2.setOnClickListener { buttonClick(btn2) } - btn3.setOnClickListener { buttonClick(btn3) } - btn4.setOnClickListener { buttonClick(btn4) } - btn5.setOnClickListener { buttonClick(btn5) } - btn6.setOnClickListener { buttonClick(btn6) } - btn7.setOnClickListener { buttonClick(btn7) } - btn8.setOnClickListener { buttonClick(btn8) } - btn9.setOnClickListener { buttonClick(btn9) } - btnBackspace.setOnClickListener { - val s = tvPin!!.text.toString() - if (s.isNotEmpty()) - tvPin!!.text = s.substring(0, s.length - 1) - } - btnContinue.setOnClickListener { doContinue() } - } - - override fun onPause() { - super.onPause() - fingerprintHelper?.cancel() - - if (startFingerprintReaderTask != null) { - startFingerprintReaderTask!!.cancel(true) - startFingerprintReaderTask = null - } - } - - override fun onStop() { - super.onStop() - fingerprintHelper?.cancel() - - if (startFingerprintReaderTask != null) { - startFingerprintReaderTask!!.cancel(true) - startFingerprintReaderTask = null - } - } - - override fun onResume() { - super.onResume() - if (allowFingerprint) - startFingerprintReader() - } - - override fun onTagDiscovered(tag: Tag) { - try { - Log.w(javaClass.name, "Ignore discovered tag!") - (activity as MainActivity).nfcManager.ignoreTag(tag) - } catch (e: IOException) { - e.printStackTrace() - } - } - - override fun authenticationFailed(error: String) { - LOG.w(TAG, error) - } - - @TargetApi(Build.VERSION_CODES.M) - override fun authenticationSucceeded(result: FingerprintManager.AuthenticationResult) { - LOG.i(TAG, "Authentication succeeded!") - val cipher = result.cryptoObject.cipher - - if (mode == Mode.RequestNewPIN || mode == Mode.ConfirmNewPIN) { - val resultData = Bundle() - val pin = PINStorage.loadEncryptedPIN(cipher) - resultData.putString(Constant.EXTRA_NEW_PIN, pin) - resultData.putString(Constant.EXTRA_CONFIRM_PIN, pin) - navigateBackWithResult(Activity.RESULT_OK, resultData) - } else if (mode == Mode.RequestNewPIN2 || mode == Mode.ConfirmNewPIN2) { - val resultData = Bundle() - val pin = PINStorage.loadEncryptedPIN2(cipher) - resultData.putString(Constant.EXTRA_NEW_PIN_2, pin) - resultData.putString(Constant.EXTRA_CONFIRM_PIN_2, pin) - navigateBackWithResult(Activity.RESULT_OK, resultData) - } else if (mode == Mode.RequestPIN) { - PINStorage.loadEncryptedPIN(cipher) - navigateBackWithResult(Activity.RESULT_OK) - } else if (mode == Mode.RequestPIN2) { - PINStorage.loadEncryptedPIN2(cipher) - navigateBackWithResult(Activity.RESULT_OK) - } - } - - private fun buttonClick(button: Button) { - tvPin.text = tvPin.text.toString() + button.text.toString() - } - - @SuppressLint("NewApi") - private fun testFingerPrintSettings(): Boolean { - LOG.i(TAG, "Testing Fingerprint SettingsFragment") - - val keyguardManager = context?.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager - fingerprintManager = context?.getSystemService(Context.FINGERPRINT_SERVICE) as FingerprintManager - - if (!keyguardManager.isKeyguardSecure) { - LOG.i(TAG, "User hasn't enabled Lock Screen") - return false - } - - if (ActivityCompat.checkSelfPermission(context!!, Manifest.permission.USE_FINGERPRINT) != PackageManager.PERMISSION_GRANTED) { - LOG.i(TAG, "User hasn't granted permission to use Fingerprint") - return false - } - - if (!fingerprintManager!!.hasEnrolledFingerprints()) { - LOG.i(TAG, "User hasn't registered any fingerprints") - return false - } - - LOG.i(TAG, "Fingerprint authentication is set.\n") - - return true - } - - private fun startFingerprintReader() { - if (!testFingerPrintSettings()) - return - - if (!allowFingerprint) - return - - fingerprintHelper = FingerprintHelper(this@PinRequestFragment) - startFingerprintReaderTask = StartFingerprintReaderTask( - this@PinRequestFragment, fingerprintManager, fingerprintHelper) - startFingerprintReaderTask!!.execute(null as Void?) - } - - private fun doContinue() { - if (startFingerprintReaderTask != null) - return - - // reset errors. - tvPin!!.error = null - - // store values at the time of the login attempt. - val pin = tvPin!!.text.toString() - - var cancel = false - var focusView: View? = null - - if (mode == Mode.ConfirmNewPIN) { - if (pin != arguments?.getString(Constant.EXTRA_NEW_PIN)) { - tvPin!!.error = getString(R.string.pin_request_error_pin_confirmation_failed) - focusView = tvPin - cancel = true - } - } else if (mode == Mode.ConfirmNewPIN2) { - if (pin != arguments?.getString(Constant.EXTRA_NEW_PIN_2)) { - tvPin!!.error = getString(R.string.pin_request_error_pin_confirmation_failed) - focusView = tvPin - cancel = true - } - } else { - if (TextUtils.isEmpty(pin)) { - tvPin!!.error = getString(R.string.error_empty_pin) - focusView = tvPin - cancel = true - } - } - - if (cancel) - focusView!!.requestFocus() - else { - if (mode == Mode.RequestNewPIN || mode == Mode.ConfirmNewPIN) { - val resultData = Bundle() - resultData.putString(Constant.EXTRA_NEW_PIN, pin) - if (mode == Mode.ConfirmNewPIN) - resultData.putString(Constant.EXTRA_CONFIRM_PIN, pin) - - navigateBackWithResult(Activity.RESULT_OK, resultData) - - } else if (mode == Mode.RequestNewPIN2 || mode == Mode.ConfirmNewPIN2) { - val resultData = Bundle() - resultData.putString(Constant.EXTRA_NEW_PIN_2, pin) - if (mode == Mode.ConfirmNewPIN2) - resultData.putString(Constant.EXTRA_CONFIRM_PIN_2, pin) - navigateBackWithResult(Activity.RESULT_OK, resultData) - - } else if (mode == Mode.RequestPIN) { - PINStorage.setUserPIN(pin) - navigateBackWithResult(Activity.RESULT_OK) - - } else if (mode == Mode.RequestPIN2) { - PINStorage.setPIN2(pin) - navigateBackWithResult(Activity.RESULT_OK) - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/ui/fragment/pin/PinSaveFragment.kt b/app/src/main/java/com/tangem/ui/fragment/pin/PinSaveFragment.kt deleted file mode 100644 index b1a5fd70d8..0000000000 --- a/app/src/main/java/com/tangem/ui/fragment/pin/PinSaveFragment.kt +++ /dev/null @@ -1,372 +0,0 @@ -package com.tangem.ui.fragment.pin - -import android.Manifest -import android.annotation.SuppressLint -import android.annotation.TargetApi -import android.app.Dialog -import android.app.KeyguardManager -import android.content.Context -import android.content.pm.PackageManager -import android.hardware.fingerprint.FingerprintManager -import android.os.Build -import android.os.Bundle -import android.security.keystore.KeyGenParameterSpec -import android.security.keystore.KeyPermanentlyInvalidatedException -import android.security.keystore.KeyProperties -import android.text.TextUtils -import android.view.View -import android.widget.Button -import android.widget.Toast -import androidx.core.app.ActivityCompat -import com.tangem.Constant -import com.tangem.data.fingerprint.ConfirmWithFingerprintTask -import com.tangem.data.fingerprint.FingerprintHelper -import com.tangem.tangem_sdk.android.data.PINStorage -import com.tangem.ui.fragment.BaseFragment -import com.tangem.wallet.R -import kotlinx.android.synthetic.main.fragment_pin_save.* -import kotlinx.android.synthetic.main.layout_pin_buttons.* -import java.io.IOException -import java.security.KeyStore -import java.security.KeyStoreException -import java.security.NoSuchAlgorithmException -import java.security.cert.CertificateException -import javax.crypto.Cipher -import javax.crypto.KeyGenerator -import javax.crypto.NoSuchPaddingException -import javax.crypto.SecretKey -import javax.crypto.spec.IvParameterSpec - -class PinSaveFragment : BaseFragment(), FingerprintHelper.FingerprintHelperListener { - companion object { - val TAG: String = PinSaveFragment::class.java.simpleName - } - - override val layoutId = R.layout.fragment_pin_save - - private var confirmWithFingerprintTask: ConfirmWithFingerprintTask? = null - private var keyStore: KeyStore? = null - private var cipher: Cipher? = null - var fingerprintManager: FingerprintManager? = null - var cryptoObject: FingerprintManager.CryptoObject? = null - var fingerprintHelper: FingerprintHelper? = null - private var usePIN2 = false - private var onConfirmAction: OnConfirmAction? = null - var dFingerPrintConfirmation: Dialog? = null - - private enum class OnConfirmAction { - Save, DeleteEncryptedAndSave, Delete - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - usePIN2 = arguments?.getBoolean(Constant.EXTRA_PIN2, false) ?: false - - if (usePIN2) - tvPinPrompt.text = getString(R.string.pin_save_title_enter_pin2_and_save) - else - tvPinPrompt.text = getString(R.string.pin_save_title_enter_pin_and_save) - - if (usePIN2) { - cbUseFingerprint.isChecked = true - cbUseFingerprint.isEnabled = false - } else { - cbUseFingerprint.isChecked = PINStorage.haveEncryptedPIN() - cbUseFingerprint.isEnabled = true - } - - // set listeners - btn0.setOnClickListener { buttonClick(btn0) } - btn1.setOnClickListener { buttonClick(btn1) } - btn2.setOnClickListener { buttonClick(btn2) } - btn3.setOnClickListener { buttonClick(btn3) } - btn4.setOnClickListener { buttonClick(btn4) } - btn5.setOnClickListener { buttonClick(btn5) } - btn6.setOnClickListener { buttonClick(btn6) } - btn7.setOnClickListener { buttonClick(btn7) } - btn8.setOnClickListener { buttonClick(btn8) } - btn9.setOnClickListener { buttonClick(btn9) } - btnBackspace.setOnClickListener { - val s = tvPin.text.toString() - if (s.isNotEmpty()) - tvPin.text = s.substring(0, s.length - 1) - } - btnSavePIN.setOnClickListener { doSavePIN() } - btnDeletePIN.setOnClickListener { doDeletePIN() } - } - - override fun onPause() { - super.onPause() - if (fingerprintHelper != null) - fingerprintHelper!!.cancel() - - if (confirmWithFingerprintTask != null) - confirmWithFingerprintTask!!.cancel(true) - } - - override fun onStop() { - super.onStop() - if (fingerprintHelper != null) - fingerprintHelper!!.cancel() - - if (confirmWithFingerprintTask != null) - confirmWithFingerprintTask!!.cancel(true) - } - - override fun authenticationFailed(error: String) { - - } - - @TargetApi(Build.VERSION_CODES.M) - override fun authenticationSucceeded(result: FingerprintManager.AuthenticationResult) { - - cipher = result.cryptoObject.cipher - - when (onConfirmAction) { - OnConfirmAction.Save -> { - val textToEncrypt = tvPin.text.toString() - if (usePIN2) { - PINStorage.saveEncryptedPIN2(cipher, textToEncrypt) - } else { - PINStorage.saveEncryptedPIN(cipher, textToEncrypt) - } - } - - OnConfirmAction.Delete -> { - if (usePIN2) { - PINStorage.deleteEncryptedPIN2() - } else { - PINStorage.deleteEncryptedPIN() - PINStorage.deletePIN() - } - tvPin.text = "" - } - - OnConfirmAction.DeleteEncryptedAndSave -> if (usePIN2) { - PINStorage.deleteEncryptedPIN2() - PINStorage.saveEncryptedPIN2(cipher, tvPin.text.toString()) - } else { - PINStorage.deleteEncryptedPIN() - PINStorage.savePIN(tvPin.text.toString()) - } - } - - dFingerPrintConfirmation!!.dismiss() - navigateUp() - } - - fun getKeyStore(): Boolean { - try { - keyStore = KeyStore.getInstance(Constant.KEYSTORE) - // create empty keystore - keyStore!!.load(null) - return true - } catch (e: KeyStoreException) { - e.printStackTrace() - } catch (e: CertificateException) { - e.printStackTrace() - } catch (e: NoSuchAlgorithmException) { - e.printStackTrace() - } catch (e: IOException) { - e.printStackTrace() - } - - return false - } - - @TargetApi(Build.VERSION_CODES.M) - fun createNewKey(forceCreate: Boolean): Boolean { - try { - if (forceCreate) - keyStore!!.deleteEntry(Constant.KEY_ALIAS) - - if (!keyStore!!.containsAlias(Constant.KEY_ALIAS)) { - val generator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, Constant.KEYSTORE) - - generator.init(KeyGenParameterSpec.Builder(Constant.KEY_ALIAS, - KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT) - .setBlockModes(KeyProperties.BLOCK_MODE_CBC) - .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7) - .setUserAuthenticationRequired(true) - .build() - ) - - generator.generateKey() - } - return true - } catch (e: Exception) { - e.printStackTrace() - } - - return false - } - - @SuppressLint("InlinedApi") - fun getCipher(): Boolean { - try { - cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_CBC + "/" + KeyProperties.ENCRYPTION_PADDING_PKCS7) - return true - } catch (e: NoSuchAlgorithmException) { - e.printStackTrace() - } catch (e: NoSuchPaddingException) { - e.printStackTrace() - } - - return false - } - - @TargetApi(Build.VERSION_CODES.M) - fun initCipher(mode: Int): Boolean { - try { - keyStore!!.load(null) - val keySpec = keyStore!!.getKey(Constant.KEY_ALIAS, null) as SecretKey - - if (mode == Cipher.ENCRYPT_MODE) { - cipher!!.init(mode, keySpec) - } else { - val iv = PINStorage.loadEncryptedIV() - val ivSpec = IvParameterSpec(iv) - cipher!!.init(mode, keySpec, ivSpec) - } - - return true - } catch (e: KeyPermanentlyInvalidatedException) { - e.printStackTrace() - // retry after clearing entry - createNewKey(true) - } catch (e: Exception) { - e.printStackTrace() - } - - return false - } - - @TargetApi(Build.VERSION_CODES.M) - fun initCryptObject(): Boolean { - try { - cryptoObject = FingerprintManager.CryptoObject(cipher!!) - return true - } catch (e: Exception) { - e.printStackTrace() - } - - return false - } - - @SuppressLint("SetTextI18n") - private fun buttonClick(button: Button) { - tvPin.text = String.format("%s%s", tvPin.text, button.text) - } - - private fun doSavePIN() { - if (confirmWithFingerprintTask != null) { - return - } - - tvPin.error = null - - val pin = tvPin.text.toString() - - var cancel = false - var focusView: View? = null - - if (TextUtils.isEmpty(pin)) { - tvPin.error = getString(R.string.error_empty_pin) - focusView = tvPin - cancel = true - } - - if (cancel) { - focusView!!.requestFocus() - } else { - if (usePIN2) { - if (!testFingerPrintSettings()) { - tvPin.postDelayed({ navigateUp() }, 2000) - return - } - - onConfirmAction = OnConfirmAction.Save - - confirmWithFingerprintTask = ConfirmWithFingerprintTask(this@PinSaveFragment) - confirmWithFingerprintTask!!.execute(null as Void?) - - } else { - if (cbUseFingerprint.isChecked || PINStorage.haveEncryptedPIN()) { - if (!testFingerPrintSettings()) { - tvPin.postDelayed({ navigateUp() }, 2000) - return - } - - onConfirmAction = if (cbUseFingerprint.isChecked) { - OnConfirmAction.Save - } else { - OnConfirmAction.DeleteEncryptedAndSave - } - - // show a progress spinner, and kick off a background task to perform the user login attempt - confirmWithFingerprintTask = ConfirmWithFingerprintTask(this@PinSaveFragment) - confirmWithFingerprintTask!!.execute(null as Void?) - } else { - PINStorage.savePIN(tvPin.text.toString()) - navigateUp() - } - } - } - } - - private fun doDeletePIN() { - if (usePIN2) { - if (PINStorage.haveEncryptedPIN2()) { - if (!testFingerPrintSettings()) { - tvPin.postDelayed({ navigateUp() }, 2000) - return - } - onConfirmAction = OnConfirmAction.Delete - confirmWithFingerprintTask = ConfirmWithFingerprintTask(this@PinSaveFragment) - confirmWithFingerprintTask!!.execute(null as Void?) - } else { - tvPin.text = "" - navigateUp() - } - } else { - if (cbUseFingerprint.isChecked || PINStorage.haveEncryptedPIN()) { - if (!testFingerPrintSettings()) { - tvPin.postDelayed({ navigateUp() }, 2000) - return - } - onConfirmAction = OnConfirmAction.Delete - confirmWithFingerprintTask = ConfirmWithFingerprintTask(this@PinSaveFragment) - confirmWithFingerprintTask!!.execute(null as Void?) - } else { - tvPin.text = "" - PINStorage.deletePIN() - navigateUp() - } - } - } - - @SuppressLint("NewApi") - private fun testFingerPrintSettings(): Boolean { - val keyguardManager = activity?.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager - fingerprintManager = activity?.getSystemService(Context.FINGERPRINT_SERVICE) as FingerprintManager - - if (!keyguardManager.isKeyguardSecure) { - Toast.makeText(context, R.string.pin_save_toast_lock_screen_not_enabled, Toast.LENGTH_LONG).show() - return false - } - - if (ActivityCompat.checkSelfPermission(requireContext(), Manifest.permission.USE_FINGERPRINT) != PackageManager.PERMISSION_GRANTED) { - Toast.makeText(context, R.string.pin_save_toast_no_permission_to_use_fingerprint, Toast.LENGTH_LONG).show() - return false - } - - if (!fingerprintManager!!.hasEnrolledFingerprints()) { - Toast.makeText(context, R.string.pin_save_toast_no_fingerprints_registered, Toast.LENGTH_LONG).show() - return false - } - - return true - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/ui/fragment/pin/PinSwapFragment.kt b/app/src/main/java/com/tangem/ui/fragment/pin/PinSwapFragment.kt deleted file mode 100644 index ee846c13ca..0000000000 --- a/app/src/main/java/com/tangem/ui/fragment/pin/PinSwapFragment.kt +++ /dev/null @@ -1,202 +0,0 @@ -package com.tangem.ui.fragment.pin - -import android.app.Activity -import android.content.res.ColorStateList -import android.graphics.Color -import android.nfc.NfcAdapter -import android.nfc.Tag -import android.nfc.tech.IsoDep -import android.os.Bundle -import android.view.View -import android.widget.ProgressBar -import android.widget.Toast -import com.google.firebase.crashlytics.FirebaseCrashlytics -import com.tangem.App -import com.tangem.Constant -import com.tangem.tangem_card.data.TangemCard -import com.tangem.tangem_card.reader.CardProtocol -import com.tangem.tangem_card.tasks.SwapPINTask -import com.tangem.tangem_card.util.Util -import com.tangem.tangem_sdk.android.reader.NfcReader -import com.tangem.tangem_sdk.data.EXTRA_TANGEM_CARD -import com.tangem.tangem_sdk.data.EXTRA_TANGEM_CARD_UID -import com.tangem.tangem_sdk.data.asBundle -import com.tangem.tangem_sdk.data.loadFromBundle -import com.tangem.ui.activity.MainActivity -import com.tangem.ui.dialog.NoExtendedLengthSupportDialog -import com.tangem.ui.dialog.WaitSecurityDelayDialog -import com.tangem.ui.fragment.BaseFragment -import com.tangem.wallet.R -import kotlinx.android.synthetic.main.fragment_pin_swap.* - -class PinSwapFragment : BaseFragment(), NfcAdapter.ReaderCallback, CardProtocol.Notifications { - companion object { - val TAG: String = PinSwapFragment::class.java.simpleName - - fun callingIntent(newPIN: String, newPIN2: String): Bundle { - val bundle = Bundle() - bundle.putString(Constant.EXTRA_NEW_PIN, newPIN) - bundle.putString(Constant.EXTRA_NEW_PIN_2, newPIN2) - return bundle - } - - const val RESULT_INVALID_PIN = Activity.RESULT_FIRST_USER - } - - override val layoutId = R.layout.fragment_pin_swap - - private lateinit var card: TangemCard; - private var newPIN: String? = null - private var newPIN2: String? = null - - private var progressBar: ProgressBar? = null - - private var swapPinTask: SwapPINTask? = null - - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - card = TangemCard(arguments?.getString(EXTRA_TANGEM_CARD_UID)) - card.loadFromBundle(arguments?.getBundle(EXTRA_TANGEM_CARD) ?: Bundle()) - - newPIN = arguments?.getString(Constant.EXTRA_NEW_PIN) - newPIN2 = arguments?.getString(Constant.EXTRA_NEW_PIN_2) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - tvCardID.text = card.cidDescription - - progressBar?.progressTintList = ColorStateList.valueOf(Color.DKGRAY) - progressBar?.visibility = View.INVISIBLE - } - - override fun onTagDiscovered(tag: Tag) { - try { - // get IsoDep handle and run cardReader thread - val isoDep = IsoDep.get(tag) - val uid = tag.id - val sUID = Util.byteArrayToHexString(uid) - if (sUID == card.uid) { - isoDep.timeout = card.pauseBeforePIN2 + 65000 - swapPinTask = SwapPINTask(card, NfcReader((activity as MainActivity).nfcManager, isoDep), - App.localStorage, App.pinStorage, this, newPIN, newPIN2) - swapPinTask?.start() - } else { - (activity as MainActivity).nfcManager.ignoreTag(isoDep.tag) - } - } catch (e: Exception) { - e.printStackTrace() - } - } - - public override fun onPause() { - swapPinTask?.cancel(true) - super.onPause() - } - - public override fun onStop() { - swapPinTask?.cancel(true) - super.onStop() - } - - override fun onReadStart(cardProtocol: CardProtocol) { - progressBar?.post { - progressBar?.visibility = View.VISIBLE - progressBar?.progress = 5 - } - } - - override fun onReadFinish(cardProtocol: CardProtocol?) { - swapPinTask = null - - if (cardProtocol != null) { - cardProtocol.error?.let { FirebaseCrashlytics.getInstance().recordException(it) } - when { - cardProtocol.error == null -> progressBar!!.post { - progressBar!!.progress = 100 - progressBar!!.progressTintList = ColorStateList.valueOf(Color.GREEN) - val data = Bundle() - data.putString(EXTRA_TANGEM_CARD_UID, cardProtocol.card.uid) - data.putBundle(EXTRA_TANGEM_CARD, cardProtocol.card.asBundle) - navigateBackWithResult(Activity.RESULT_OK, data) - } - cardProtocol.error is CardProtocol.TangemException_InvalidPIN -> { - progressBar!!.post { - progressBar!!.progress = 100 - progressBar!!.progressTintList = ColorStateList.valueOf(Color.RED) - } - progressBar!!.postDelayed({ - try { - progressBar!!.progress = 0 - progressBar!!.progressTintList = ColorStateList.valueOf(Color.DKGRAY) - progressBar!!.visibility = View.INVISIBLE - val data = Bundle() - data.putString(Constant.EXTRA_MESSAGE, "Cannot change PIN(s). Make sure you enter correct PIN2!") - data.putString(EXTRA_TANGEM_CARD_UID, cardProtocol.card.uid) - data.putBundle(EXTRA_TANGEM_CARD, cardProtocol.card.asBundle) - navigateBackWithResult(RESULT_INVALID_PIN, data) - } catch (e: Exception) { - e.printStackTrace() - } - }, 500) - } - else -> progressBar!!.post { - if (cardProtocol.error is CardProtocol.TangemException_ExtendedLengthNotSupported) { - if (!NoExtendedLengthSupportDialog.allReadyShowed) { - NoExtendedLengthSupportDialog().show(activity!!.supportFragmentManager, - NoExtendedLengthSupportDialog.TAG) - } - } else { - Toast.makeText(context, R.string.general_notification_scan_again, Toast.LENGTH_SHORT).show() - } - progressBar!!.progress = 100 - progressBar!!.progressTintList = ColorStateList.valueOf(Color.RED) - } - } - - progressBar?.postDelayed({ - try { - progressBar?.progress = 0 - progressBar?.progressTintList = ColorStateList.valueOf(Color.DKGRAY) - progressBar?.visibility = View.INVISIBLE - } catch (e: Exception) { - e.printStackTrace() - } - }, 500) - } - } - - override fun onReadProgress(protocol: CardProtocol, progress: Int) { - progressBar!!.post { progressBar!!.progress = progress } - } - - override fun onReadCancel() { - swapPinTask = null - - progressBar!!.postDelayed({ - try { - progressBar!!.progress = 0 - progressBar!!.progressTintList = ColorStateList.valueOf(Color.DKGRAY) - progressBar!!.visibility = View.INVISIBLE - } catch (e: Exception) { - e.printStackTrace() - } - }, 500) - } - - override fun onReadWait(msec: Int) { - WaitSecurityDelayDialog.onReadWait(activity, msec) - } - - override fun onReadBeforeRequest(timeout: Int) { - WaitSecurityDelayDialog.onReadBeforeRequest(activity, timeout) - } - - override fun onReadAfterRequest() { - WaitSecurityDelayDialog.onReadAfterRequest(activity) - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/ui/fragment/qr/CameraPermissionManager.kt b/app/src/main/java/com/tangem/ui/fragment/qr/CameraPermissionManager.kt deleted file mode 100644 index 250370d40a..0000000000 --- a/app/src/main/java/com/tangem/ui/fragment/qr/CameraPermissionManager.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.tangem.ui.fragment.qr - -import android.Manifest -import android.content.pm.PackageManager -import android.widget.Toast -import androidx.core.content.ContextCompat -import androidx.core.content.PermissionChecker -import com.tangem.ui.fragment.BaseFragment -import com.tangem.wallet.R - -class CameraPermissionManager(val fragment: BaseFragment) { - - fun isPermissionGranted(): Boolean { - return ContextCompat.checkSelfPermission(fragment.requireContext(), Manifest.permission.CAMERA) == - PermissionChecker.PERMISSION_GRANTED - } - - fun handleRequestPermissionResult(requestCode: Int, grantResults: IntArray, action: () -> Unit) { - when (requestCode) { - 1 -> { - if (grantResults.isEmpty() || grantResults[0] != PackageManager.PERMISSION_GRANTED) { - Toast.makeText(fragment.requireContext(), R.string.general_toast_no_permission, Toast.LENGTH_LONG).show() - } else { - action.invoke() - } - } - } - } - - fun requirePermission() { - fragment.requestPermissions(arrayOf(Manifest.permission.CAMERA), 1) - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/ui/fragment/qr/QrScanFragment.kt b/app/src/main/java/com/tangem/ui/fragment/qr/QrScanFragment.kt deleted file mode 100644 index 9ee01f8cd9..0000000000 --- a/app/src/main/java/com/tangem/ui/fragment/qr/QrScanFragment.kt +++ /dev/null @@ -1,40 +0,0 @@ -package com.tangem.ui.fragment.qr - -import android.app.Activity -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import com.google.zxing.Result -import com.tangem.Constant -import com.tangem.ui.fragment.BaseFragment -import me.dm7.barcodescanner.zxing.ZXingScannerView - -class QrScanFragment : BaseFragment(), ZXingScannerView.ResultHandler { - - override val layoutId = 0 - private var scannerView: ZXingScannerView? = null - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - scannerView = ZXingScannerView(activity) - return scannerView - } - - override fun onPause() { - super.onPause() - scannerView?.stopCamera() - } - - override fun onResume() { - super.onResume() - scannerView?.setResultHandler(this) - scannerView?.startCamera() - } - - override fun handleResult(result: Result) { - val data = Bundle() - data.putString(Constant.EXTRA_QR_CODE, result.text) - navigateBackWithResult(Activity.RESULT_OK, data) - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/ui/fragment/wallet/CreateNewWalletFragment.kt b/app/src/main/java/com/tangem/ui/fragment/wallet/CreateNewWalletFragment.kt deleted file mode 100644 index 51e9ef4544..0000000000 --- a/app/src/main/java/com/tangem/ui/fragment/wallet/CreateNewWalletFragment.kt +++ /dev/null @@ -1,178 +0,0 @@ -package com.tangem.ui.fragment.wallet - -import android.app.Activity -import android.content.res.ColorStateList -import android.graphics.Color -import android.nfc.NfcAdapter -import android.nfc.Tag -import android.nfc.tech.IsoDep -import android.os.Bundle -import android.view.View -import android.widget.Toast -import com.google.firebase.crashlytics.FirebaseCrashlytics -import com.tangem.App -import com.tangem.Constant -import com.tangem.tangem_card.reader.CardProtocol -import com.tangem.tangem_card.tasks.CreateNewWalletTask -import com.tangem.tangem_card.util.Util -import com.tangem.tangem_sdk.android.nfc.NfcDeviceAntennaLocation -import com.tangem.tangem_sdk.android.reader.NfcReader -import com.tangem.tangem_sdk.data.EXTRA_TANGEM_CARD -import com.tangem.tangem_sdk.data.EXTRA_TANGEM_CARD_UID -import com.tangem.tangem_sdk.data.asBundle -import com.tangem.ui.activity.MainActivity -import com.tangem.ui.dialog.NoExtendedLengthSupportDialog -import com.tangem.ui.dialog.WaitSecurityDelayDialog -import com.tangem.ui.fragment.BaseFragment -import com.tangem.wallet.R -import com.tangem.wallet.TangemContext -import kotlinx.android.synthetic.main.fragment_create_new_wallet.* -import kotlinx.android.synthetic.main.layout_progress_horizontal.* -import kotlinx.android.synthetic.main.layout_touch_card.* - -class CreateNewWalletFragment : BaseFragment(), NfcAdapter.ReaderCallback, CardProtocol.Notifications { - - override val layoutId = R.layout.fragment_create_new_wallet - - private val ctx: TangemContext by lazy { TangemContext.loadFromBundle(context, arguments) } - - private lateinit var nfcDeviceAntenna: NfcDeviceAntennaLocation - - private var createNewWalletTask: CreateNewWalletTask? = null - private var lastReadSuccess = true - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - // init NFC Antenna - nfcDeviceAntenna = NfcDeviceAntennaLocation(requireContext(), ivHandCardHorizontal, ivHandCardVertical, llHand, llNfc) - nfcDeviceAntenna.init() - - tvCardId.text = ctx.card!!.cidDescription - progressBar.progressTintList = ColorStateList.valueOf(Color.DKGRAY) - progressBar.visibility = View.INVISIBLE - } - - override fun onTagDiscovered(tag: Tag) { - // get IsoDep handle and run cardReader thread - val isoDep = IsoDep.get(tag) - val uid = tag.id - val sUID = Util.byteArrayToHexString(uid) - - if (sUID == ctx.card.uid) { - if (lastReadSuccess) - isoDep.timeout = ctx.card.pauseBeforePIN2 + 5000 - else - isoDep.timeout = ctx.card.pauseBeforePIN2 + 65000 - - createNewWalletTask = CreateNewWalletTask(ctx.card, NfcReader((activity as MainActivity).nfcManager, isoDep), - App.localStorage, App.pinStorage, this) - createNewWalletTask?.start() - } else - (activity as MainActivity).nfcManager.ignoreTag(isoDep.tag) - } - - override fun onPause() { - createNewWalletTask?.cancel(true) - super.onPause() - } - - override fun onReadStart(cardProtocol: CardProtocol) { - rlProgressBar?.post { rlProgressBar.visibility = View.VISIBLE } - - progressBar?.post { - progressBar?.visibility = View.VISIBLE - progressBar?.progress = 5 - } - } - - override fun onReadFinish(cardProtocol: CardProtocol?) { - createNewWalletTask = null - if (cardProtocol != null) { - if (cardProtocol.error == null) { - rlProgressBar.post { rlProgressBar.visibility = View.GONE } - - progressBar?.post { - progressBar?.progress = 100 - progressBar?.progressTintList = ColorStateList.valueOf(Color.GREEN) - val data = Bundle() - data.putString(EXTRA_TANGEM_CARD_UID, cardProtocol.card.uid) - data.putBundle(EXTRA_TANGEM_CARD, cardProtocol.card.asBundle) - navigateBackWithResult(Activity.RESULT_OK, data) - return@post - } - } else { - FirebaseCrashlytics.getInstance().recordException(cardProtocol.error) - lastReadSuccess = false - if (cardProtocol.error is CardProtocol.TangemException_InvalidPIN) { - progressBar?.post { - progressBar?.progress = 100 - progressBar?.progressTintList = ColorStateList.valueOf(Color.RED) - } - progressBar?.postDelayed({ - try { - progressBar?.progress = 0 - progressBar?.progressTintList = ColorStateList.valueOf(Color.DKGRAY) - progressBar?.visibility = View.INVISIBLE - val data = Bundle() - data.putString(Constant.EXTRA_MESSAGE, getString(R.string.nfc_error_cannot_create_wallet)) - data.putString(EXTRA_TANGEM_CARD_UID, cardProtocol.card.uid) - data.putBundle(EXTRA_TANGEM_CARD, cardProtocol.card!!.asBundle) - navigateBackWithResult(Constant.RESULT_INVALID_PIN, data) - return@postDelayed - } catch (e: Exception) { - e.printStackTrace() - } - }, 500) - } else { - progressBar?.post { - if (cardProtocol.error is CardProtocol.TangemException_ExtendedLengthNotSupported) { - if (!NoExtendedLengthSupportDialog.allReadyShowed) { - NoExtendedLengthSupportDialog().show(activity!!.supportFragmentManager, NoExtendedLengthSupportDialog.TAG) - } - } else - Toast.makeText(context, R.string.general_notification_scan_again, Toast.LENGTH_SHORT).show() - - progressBar?.progress = 100 - progressBar?.progressTintList = ColorStateList.valueOf(Color.RED) - } - } - } - } - - rlProgressBar.postDelayed({ rlProgressBar?.visibility = View.GONE }, 500) - - progressBar?.postDelayed({ - progressBar?.progress = 0 - progressBar?.progressTintList = ColorStateList.valueOf(Color.DKGRAY) - progressBar?.visibility = View.INVISIBLE - }, 500) - } - - override fun onReadProgress(protocol: CardProtocol, progress: Int) { - progressBar?.post { progressBar?.progress = progress } - } - - override fun onReadCancel() { - createNewWalletTask = null - - progressBar?.postDelayed({ - progressBar?.progress = 0 - progressBar?.progressTintList = ColorStateList.valueOf(Color.DKGRAY) - progressBar?.visibility = View.INVISIBLE - }, 500) - } - - override fun onReadWait(msec: Int) { - WaitSecurityDelayDialog.onReadWait(activity, msec) - } - - override fun onReadBeforeRequest(timeout: Int) { - WaitSecurityDelayDialog.onReadBeforeRequest(activity, timeout) - } - - override fun onReadAfterRequest() { - WaitSecurityDelayDialog.onReadAfterRequest(activity) - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/ui/fragment/wallet/EmptyWalletFragment.kt b/app/src/main/java/com/tangem/ui/fragment/wallet/EmptyWalletFragment.kt deleted file mode 100644 index e8a4cc7542..0000000000 --- a/app/src/main/java/com/tangem/ui/fragment/wallet/EmptyWalletFragment.kt +++ /dev/null @@ -1,258 +0,0 @@ -package com.tangem.ui.fragment.wallet - -import android.app.Activity -import android.content.Context -import android.content.Intent -import android.content.res.ColorStateList -import android.graphics.Color -import android.nfc.NfcAdapter -import android.nfc.Tag -import android.nfc.tech.IsoDep -import android.os.Build -import android.os.Bundle -import android.text.Html -import android.view.View -import android.widget.Toast -import com.google.firebase.crashlytics.FirebaseCrashlytics -import com.tangem.App -import com.tangem.Constant -import com.tangem.tangem_card.data.TangemCard -import com.tangem.tangem_card.reader.CardProtocol -import com.tangem.tangem_card.tasks.VerifyCardTask -import com.tangem.tangem_card.util.Util -import com.tangem.tangem_sdk.android.reader.NfcReader -import com.tangem.tangem_sdk.data.EXTRA_TANGEM_CARD -import com.tangem.tangem_sdk.data.EXTRA_TANGEM_CARD_UID -import com.tangem.tangem_sdk.data.asBundle -import com.tangem.tangem_sdk.data.loadFromBundle -import com.tangem.ui.activity.MainActivity -import com.tangem.ui.dialog.NoExtendedLengthSupportDialog -import com.tangem.ui.dialog.WaitSecurityDelayDialog -import com.tangem.ui.fragment.BaseFragment -import com.tangem.ui.fragment.pin.PinRequestFragment -import com.tangem.ui.navigation.NavigationResultListener -import com.tangem.wallet.R -import com.tangem.wallet.TangemContext -import kotlinx.android.synthetic.main.fragment_empty_wallet.* -import kotlinx.android.synthetic.main.layout_tangem_card.* - -class EmptyWalletFragment : BaseFragment(), NavigationResultListener, - NfcAdapter.ReaderCallback, CardProtocol.Notifications { - companion object { - val TAG: String = EmptyWalletFragment::class.java.simpleName - fun callingIntent(context: Context, ctx: TangemContext): Intent { - val intent = Intent(context, EmptyWalletFragment::class.java) - ctx.saveToIntent(intent) - return intent - } - } - - override val layoutId = R.layout.fragment_empty_wallet - - private val ctx: TangemContext by lazy { TangemContext.loadFromBundle(context, arguments) } - - private var lastReadSuccess = true - private var verifyCardTask: VerifyCardTask? = null - private var requestPIN2Count = 0 - - private var cardProtocol: CardProtocol? = null - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - tvIssuer.text = ctx.card!!.issuerDescription - - if (ctx.card!!.tokenSymbol.length > 1) { - @Suppress("DEPRECATION") val html = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) - Html.fromHtml(ctx.blockchainName, Html.FROM_HTML_MODE_LEGACY) - else - Html.fromHtml(ctx.blockchainName) - tvBlockchain.text = html - } else - tvBlockchain.text = ctx.blockchainName - - tvCardID.text = ctx.card!!.cidDescription - ivTangemCard.setImageBitmap(App.localStorage.getCardArtworkBitmap(ctx.card)) - - // set listeners - btnNewWallet.setOnClickListener { - requestPIN2Count = 0 - - val data = Bundle() - data.putString(Constant.EXTRA_MODE, PinRequestFragment.Mode.RequestPIN2.toString()) - data.putString(EXTRA_TANGEM_CARD_UID, ctx.card!!.uid) - data.putBundle(EXTRA_TANGEM_CARD, ctx.card!!.asBundle) - navigateForResult(Constant.REQUEST_CODE_REQUEST_PIN2, - R.id.action_emptyWalletFragment_to_pinRequestFragment, data) - } - - btnDetails.setOnClickListener { - if (cardProtocol != null) { - val data = Bundle() - ctx.saveToBundle(data) - navigateForResult(Constant.REQUEST_CODE_VERIFY_CARD, - R.id.action_emptyWalletFragment_to_verifyCard, data) - } else { - (activity as MainActivity).toastHelper - .showSingleToast(context, getString(R.string.general_notification_scan_again_to_verify)) - } - } - } - - override fun onNavigationResult(requestCode: String, resultCode: Int, data: Bundle?) { - if (requestCode == Constant.REQUEST_CODE_CREATE_NEW_WALLET_ACTIVITY) { - if (resultCode == Activity.RESULT_OK) { - if (data != null) { - data.putString(Constant.EXTRA_MODIFICATION, "updateAndViewCard") - data.putInt("updateDelay", 0) - navigateBackWithResult(Activity.RESULT_OK, data) - } - return - } else { - if (data != null && data.containsKey(EXTRA_TANGEM_CARD_UID) && data.containsKey(EXTRA_TANGEM_CARD)) { - val updatedCard = TangemCard(data.getString(EXTRA_TANGEM_CARD_UID)) - updatedCard.loadFromBundle(data.getBundle(EXTRA_TANGEM_CARD)) - ctx.card = updatedCard - } - if (resultCode == Constant.RESULT_INVALID_PIN && requestPIN2Count < 2) { - requestPIN2Count++ - val bundle = Bundle() - bundle.putString(Constant.EXTRA_MODE, PinRequestFragment.Mode.RequestPIN2.toString()) - bundle.putString(EXTRA_TANGEM_CARD_UID, ctx.card!!.uid) - bundle.putBundle(EXTRA_TANGEM_CARD, ctx.card!!.asBundle) - navigateForResult(Constant.REQUEST_CODE_REQUEST_PIN2, - R.id.action_emptyWalletFragment_to_pinRequestFragment, bundle) - return - } - } - navigateBackWithResult(resultCode, data) - return - } else if (requestCode == Constant.REQUEST_CODE_REQUEST_PIN2) { - if (resultCode == Activity.RESULT_OK) { - val bundle = Bundle() - bundle.putString(EXTRA_TANGEM_CARD_UID, ctx.card!!.uid) - bundle.putBundle(EXTRA_TANGEM_CARD, ctx.card!!.asBundle) - navigateForResult(Constant.REQUEST_CODE_CREATE_NEW_WALLET_ACTIVITY, - R.id.action_emptyWalletFragment_to_createNewWalletFragment, bundle) - } - } - } - - override fun onTagDiscovered(tag: Tag) { - try { - val isoDep = IsoDep.get(tag) - val uid = tag.id - val sUID = Util.byteArrayToHexString(uid) - if (ctx.card.uid != sUID || cardProtocol != null) { - (activity as MainActivity).nfcManager.ignoreTag(isoDep.tag) - return - } - - if (lastReadSuccess) - isoDep.timeout = 1000 - else - isoDep.timeout = 65000 - - verifyCardTask = VerifyCardTask(ctx.card, NfcReader((activity as MainActivity).nfcManager, isoDep), - App.localStorage, App.pinStorage, App.firmwaresStorage, this) - verifyCardTask?.start() - } catch (e: Exception) { - e.printStackTrace() - } - } - - override fun onReadStart(cardProtocol: CardProtocol) { - rlProgressBar?.post { rlProgressBar.visibility = View.VISIBLE } - - progressBar?.post { - progressBar?.visibility = View.VISIBLE - progressBar?.progress = 5 - } - } - - override fun onReadFinish(cardProtocol: CardProtocol?) { - verifyCardTask = null - if (cardProtocol != null) { - if (cardProtocol.error == null) { - rlProgressBar?.post { rlProgressBar.visibility = View.GONE } - - progressBar?.post { - progressBar?.progress = 100 - progressBar?.progressTintList = ColorStateList.valueOf(Color.GREEN) - this.cardProtocol = cardProtocol - } - } else { - FirebaseCrashlytics.getInstance().recordException(cardProtocol.error) - // remove last UIDs because of error and no card read - progressBar?.post { - lastReadSuccess = false - if (cardProtocol.error is CardProtocol.TangemException_ExtendedLengthNotSupported) { - if (!NoExtendedLengthSupportDialog.allReadyShowed) - NoExtendedLengthSupportDialog().show(activity!!.supportFragmentManager, NoExtendedLengthSupportDialog.TAG) - } else - Toast.makeText(context, R.string.general_notification_scan_again, Toast.LENGTH_LONG).show() - - progressBar?.progress = 100 - progressBar?.progressTintList = ColorStateList.valueOf(Color.RED) - } - } - } - - rlProgressBar?.postDelayed({ - try { - rlProgressBar?.visibility = View.GONE - } catch (e: Exception) { - e.printStackTrace() - } - }, 500) - - progressBar?.postDelayed({ - try { - progressBar?.progress = 0 - progressBar?.progressTintList = ColorStateList.valueOf(Color.DKGRAY) - progressBar?.visibility = View.INVISIBLE - } catch (e: Exception) { - e.printStackTrace() - } - }, 500) - } - - override fun onReadProgress(protocol: CardProtocol, progress: Int) { - progressBar?.post { progressBar?.progress = progress } - } - - override fun onReadCancel() { - verifyCardTask = null - - rlProgressBar?.postDelayed({ - try { - rlProgressBar?.visibility = View.GONE - } catch (e: Exception) { - e.printStackTrace() - } - }, 500) - - progressBar?.postDelayed({ - try { - progressBar?.progress = 0 - progressBar?.progressTintList = ColorStateList.valueOf(Color.DKGRAY) - progressBar?.visibility = View.INVISIBLE - } catch (e: Exception) { - e.printStackTrace() - } - }, 500) - } - - override fun onReadWait(msec: Int) { - WaitSecurityDelayDialog.onReadWait(activity, msec) - } - - override fun onReadBeforeRequest(timeout: Int) { - WaitSecurityDelayDialog.onReadBeforeRequest(activity, timeout) - } - - override fun onReadAfterRequest() { - WaitSecurityDelayDialog.onReadAfterRequest(activity) - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/ui/fragment/wallet/LoadedWalletFragment.kt b/app/src/main/java/com/tangem/ui/fragment/wallet/LoadedWalletFragment.kt deleted file mode 100644 index b358ddebf5..0000000000 --- a/app/src/main/java/com/tangem/ui/fragment/wallet/LoadedWalletFragment.kt +++ /dev/null @@ -1,964 +0,0 @@ -package com.tangem.ui.fragment.wallet - -import android.app.Activity -import android.app.AlertDialog -import android.content.* -import android.content.Context.CLIPBOARD_SERVICE -import android.content.pm.PackageManager -import android.content.res.ColorStateList -import android.media.MediaPlayer -import android.nfc.NfcAdapter -import android.nfc.Tag -import android.nfc.tech.IsoDep -import android.os.Build -import android.os.Bundle -import android.text.Html -import android.view.Gravity -import android.view.LayoutInflater -import android.view.View -import android.widget.Toast -import androidx.appcompat.app.AppCompatActivity -import androidx.core.content.ContextCompat -import androidx.lifecycle.Observer -import androidx.lifecycle.ViewModelProviders -import com.tangem.App -import com.tangem.Constant -import com.tangem.data.Blockchain -import com.tangem.data.dp.PrefsManager -import com.tangem.data.getPayIdNetwork -import com.tangem.data.isPayIdSupported -import com.tangem.server_android.Result -import com.tangem.server_android.ServerApiTangem -import com.tangem.server_android.model.CardVerifyAndGetInfo -import com.tangem.tangem_card.data.TangemCard -import com.tangem.tangem_card.reader.CardProtocol -import com.tangem.tangem_card.tasks.VerifyCardTask -import com.tangem.tangem_card.util.Util -import com.tangem.tangem_sdk.android.reader.NfcReader -import com.tangem.tangem_sdk.data.EXTRA_TANGEM_CARD -import com.tangem.tangem_sdk.data.EXTRA_TANGEM_CARD_UID -import com.tangem.tangem_sdk.data.loadFromBundle -import com.tangem.ui.activity.MainActivity -import com.tangem.ui.dialog.NoExtendedLengthSupportDialog -import com.tangem.ui.dialog.PINSwapWarningDialog -import com.tangem.ui.dialog.ShowQRCodeDialog -import com.tangem.ui.dialog.WaitSecurityDelayDialog -import com.tangem.ui.event.DeletingWalletFinish -import com.tangem.ui.event.TransactionFinishWithSuccess -import com.tangem.ui.fragment.BaseFragment -import com.tangem.ui.fragment.pin.PinRequestFragment -import com.tangem.ui.fragment.pin.PinSwapFragment -import com.tangem.ui.navigation.NavigationResultListener -import com.tangem.util.LOG -import com.tangem.util.UtilHelper -import com.tangem.util.extensions.isStart2CoinCard -import com.tangem.wallet.* -import kotlinx.android.synthetic.main.dialog_pay_id.view.* -import kotlinx.android.synthetic.main.fr_loaded_wallet.* -import kotlinx.android.synthetic.main.layout_btn_details.* -import kotlinx.android.synthetic.main.layout_tangem_card.* -import org.greenrobot.eventbus.EventBus -import org.greenrobot.eventbus.Subscribe -import retrofit2.HttpException -import java.io.InputStream -import java.util.* -import kotlin.concurrent.timerTask - -class LoadedWalletFragment : BaseFragment(), NavigationResultListener, NfcAdapter.ReaderCallback, - CardProtocol.Notifications, SharedPreferences.OnSharedPreferenceChangeListener { - companion object { - val TAG: String = LoadedWalletFragment::class.java.simpleName - const val PAY_ID_TANGEM = "\$payid.tangem.com" - } - - override val layoutId = R.layout.fr_loaded_wallet - - private lateinit var viewModel: LoadedWalletViewModel - private lateinit var ctx: TangemContext - private lateinit var mpSecondScanSound: MediaPlayer - private var serverApiTangem: ServerApiTangem = ServerApiTangem() - private var lastTag: Tag? = null - private var lastReadSuccess = true - private var verifyCardTask: VerifyCardTask? = null - private var requestPIN2Count = 0 - private var timerHideErrorAndMessage: Timer? = null - private var newPIN = "" - private var newPIN2 = "" - private var cardProtocol: CardProtocol? = null - private var refreshAction: Runnable? = null - private val inactiveColor: ColorStateList by lazy { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) - resources.getColorStateList(R.color.btn_dark, activity?.theme) - else - @Suppress("DEPRECATION") - resources.getColorStateList(R.color.btn_dark) - } - private val activeColor: ColorStateList by lazy { - val color = if (ctx.card?.isStart2CoinCard() == true) { - R.color.start2coin_orange - } else { - R.color.colorAccent - } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) - resources.getColorStateList(color, activity?.theme) - else - @Suppress("DEPRECATION") - resources.getColorStateList(color) - } - private var requestCounter: Int = 0 - set(value) { - field = value - LOG.i(TAG, "requestCounter, set $field") - if (field <= 0) { - LOG.e(TAG, "+++++++++++ FINISH REFRESH") - if (srl != null && srl.isRefreshing) - srl.isRefreshing = false - } else if (srl != null && !srl.isRefreshing) - srl.isRefreshing = true - } - - private var payId: String? = null - private lateinit var btnLoadItems: MutableList - - - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - ctx = TangemContext.loadFromBundle(context, arguments) - - lastTag = activity?.intent?.getParcelableExtra(Constant.EXTRA_LAST_DISCOVERED_TAG) - - if (arguments?.containsKey(NfcAdapter.EXTRA_TAG) == true) { - val tag = requireArguments().getParcelable(NfcAdapter.EXTRA_TAG) - if (tag != null) onTagDiscovered(tag) - } - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - viewModel = ViewModelProviders.of(this).get(LoadedWalletViewModel::class.java) - - mpSecondScanSound = MediaPlayer.create(activity, R.raw.scan_card_sound) - - val engine = CoinEngineFactory.create(ctx) - - tvBalance.setSingleLine(!engine!!.needMultipleLinesForBalance()) - - ivTangemCard.setImageBitmap(App.localStorage.getCardArtworkBitmap(ctx.card)) - - btnExtract.isEnabled = false - btnExtract.backgroundTintList = inactiveColor - - tvWallet.text = ctx.coinData.wallet - - btnLoadItems = mutableListOf( - getString(R.string.loaded_wallet_load_via_app), - getString(R.string.loaded_wallet_load_via_share_address), - getString(R.string.loaded_wallet_load_via_qr) - ) - - if (ctx.blockchain.isPayIdSupported() && !ctx.card.isStart2CoinCard()) { - ivPayId.visibility = View.VISIBLE - ivPayId.imageAlpha = 100 - ivPayId.setOnClickListener { createPayIdDialog() } - - getPayIdIfApplicable() - } - - - // set listeners - srl.setOnRefreshListener { refresh() } - - tvWallet.setOnClickListener { doShareWallet(false) } - - btnExplore.setOnClickListener { startActivity(Intent(Intent.ACTION_VIEW, engine.walletExplorerUri)) } - - btnCopy.setOnClickListener { doShareWallet(false) } - - - - if (ctx.card?.isStart2CoinCard() == true) { - btnLoad?.visibility = View.GONE - } - - btnLoad.setOnClickListener { - val cw = android.view.ContextThemeWrapper(activity, R.style.AlertDialogTheme) - val dialog = AlertDialog.Builder(cw).setItems(btnLoadItems.toTypedArray() - ) { _, which -> - when (btnLoadItems[which]) { - getString(R.string.loaded_wallet_load_via_app) -> { - try { - val intent = Intent(Intent.ACTION_VIEW, engine.shareWalletUriEx) - intent.addCategory(Intent.CATEGORY_DEFAULT) - startActivity(intent) - } catch (e: ActivityNotFoundException) { - (activity as MainActivity).toastHelper.showSingleToast(context, getString(R.string.loaded_wallet_no_compatible_wallet)) - } - } - - getString(R.string.loaded_wallet_load_via_share_address) -> { - doShareWallet(true) - } - - getString(R.string.loaded_wallet_load_via_qr) -> { - ShowQRCodeDialog.show(activity as AppCompatActivity?, engine.shareWalletUriEx.toString()) - } - payId -> { - if (payId == getString(R.string.loaded_wallet_create_pay_id)) { - createPayIdDialog() - } else { - payId?.let { copyText(it) } - } - } - - else -> { - } - } - } - val dlg = dialog.show() - val wlp = dlg.window.attributes - wlp.gravity = Gravity.BOTTOM - dlg.window.attributes = wlp - } - - btnExtract.setOnClickListener { - if (UtilHelper.isOnline(context as Activity)) - if (!engine.isExtractPossible) { - (activity as MainActivity).toastHelper.showSingleToast(context, ctx.message) - } else if (ctx.card!!.remainingSignatures == 0) { - (activity as MainActivity).toastHelper.showSingleToast(context, getString(R.string.loaded_wallet_warning_no_signature)) - } else { - val bundle = Bundle().apply { ctx.saveToBundle(this) } - navigateForResult(Constant.REQUEST_CODE_SEND_TRANSACTION, - R.id.action_loadedWalletFragment_to_prepareTransactionFragment, bundle) - } - else - Toast.makeText(activity, getString(R.string.general_error_no_connection), Toast.LENGTH_SHORT).show() - } - - btnDetails.setOnClickListener { - if (cardProtocol != null) { - val bundle = Bundle().apply { ctx.saveToBundle(this) } - navigateForResult(Constant.REQUEST_CODE_VERIFY_CARD, - R.id.action_loadedWalletFragment_to_verifyCard, bundle) - } else { - (activity as MainActivity).toastHelper - .showSingleToast(context, getString(R.string.general_notification_scan_again_to_verify)) - } - } - - btnNewScan.setOnClickListener { - // navigateToDestination(R.id.action_loadedWalletFragment_to_main) - navigateUp() - } - - // request card verify and get info listener - val cardVerifyAndGetInfoListener: ServerApiTangem.CardVerifyAndGetInfoListener = object : ServerApiTangem.CardVerifyAndGetInfoListener { - override fun onSuccess(cardVerifyAndGetArtworkResponse: CardVerifyAndGetInfo.Response?) { - LOG.i(TAG, "cardVerifyAndGetInfoListener onSuccess") - if (activity == null || !UtilHelper.isOnline(activity!!)) return - - val result = cardVerifyAndGetArtworkResponse?.results!![0] - if (result.error != null) { - ctx.card!!.isOnlineVerified = false - return - } - ctx.card!!.isOnlineVerified = result.passed - - requestCounter-- - updateViews() - - if (!result.passed) return - - if (App.localStorage.checkBatchInfoChanged(ctx.card!!, result)) { - LOG.w(TAG, "Batch ${result.batch} info changed to '$result'") - ivTangemCard.setImageBitmap(App.localStorage.getCardArtworkBitmap(ctx.card!!)) - App.localStorage.applySubstitution(ctx.card!!) - refresh() - } - if (result.artwork != null && App.localStorage.checkNeedUpdateArtwork(result.artwork)) { - LOG.w(TAG, "Artwork '${result.artwork!!.id}' updated, need download") - requestCounter++ - serverApiTangem.requestArtwork(result.artwork!!.id, result.artwork!!.getUpdateDate(), ctx.card!!) - updateViews() - } - } - - override fun onFail(message: String?) { - LOG.i(TAG, "cardVerifyAndGetInfoListener onFail") - if (activity == null || !UtilHelper.isOnline(activity!!)) return - requestCounter-- - updateViews() - } - } - serverApiTangem.setCardVerifyAndGetInfoListener(cardVerifyAndGetInfoListener) - - // request artwork listener - val artworkListener: ServerApiTangem.ArtworkListener = object : ServerApiTangem.ArtworkListener { - override fun onSuccess(artworkId: String?, inputStream: InputStream?, updateDate: Date?) { - LOG.i(TAG, "artworkListener onSuccess") - if (activity == null || !UtilHelper.isOnline(activity!!)) return - App.localStorage.updateArtwork(artworkId!!, inputStream!!, updateDate!!) - requestCounter-- - ivTangemCard.setImageBitmap(App.localStorage.getCardArtworkBitmap(ctx.card!!)) - updateViews() - } - - override fun onFail(message: String?) { - LOG.i(TAG, "artworkListener onFail") - if (activity == null || !UtilHelper.isOnline(activity!!)) return - requestCounter-- - updateViews() - } - } - serverApiTangem.setArtworkListener(artworkListener) - refresh() - startVerify(lastTag) - - - - // set rate info to CoinData - viewModel.getRateInfo().observe(viewLifecycleOwner, Observer { rate -> - ctx.coinData.rate = rate - ctx.coinData.rateAlter = rate - updateViews() - }) - viewModel.requestRateInfo(ctx) - } - - private fun getPayIdIfApplicable() { - if (ctx.blockchain.isPayIdSupported() && !ctx.card.isStart2CoinCard()) { - viewModel.getPayId( - Util.byteArrayToHexString(ctx.card.cid!!), - Util.byteArrayToHexString(ctx.card.cardPublicKey!!)) - .observe( - viewLifecycleOwner, Observer { result -> - when (result) { - is Result.Success -> { - onPayIdFound(result.data.payId) - } - is Result.Failure -> { - (result.error as? HttpException)?.let { - if (it.code() == 404) { - val createPayId = getString(R.string.loaded_wallet_create_pay_id) - payId = createPayId - if (!btnLoadItems.contains(createPayId)) { - btnLoadItems.add(createPayId) - } - } - } - } - } - }) - } - } - - private fun createPayIdDialog() { - val dialogView = LayoutInflater.from(context).inflate(R.layout.dialog_pay_id, null); - val alertDialogBuilderUserInput = AlertDialog.Builder(context); - alertDialogBuilderUserInput.setView(dialogView); - alertDialogBuilderUserInput - .setCancelable(true) - .setPositiveButton(getString(R.string.create_pay_id_create)) { dialogBox, _ -> - if (dialogView.etPayId.text.isNullOrBlank()) { - Toast.makeText(context, getString(R.string.create_pay_id_empty), Toast.LENGTH_LONG).show() - return@setPositiveButton - } - - dialogBox.cancel() - createPayId("${dialogView.etPayId.text}$PAY_ID_TANGEM") - } - - .setNegativeButton(getString(R.string.general_cancel)){ dialogBox, _ -> - dialogBox.cancel() - } - - alertDialogBuilderUserInput.create().show() - } - - private fun createPayId(payId: String) { - viewModel.setPayId( - Util.byteArrayToHexString(ctx.card.cid!!), - Util.byteArrayToHexString(ctx.card.cardPublicKey!!), - payId, - ctx.coinData.wallet, ctx.blockchain.getPayIdNetwork() - ) - .observe(viewLifecycleOwner, Observer { result -> - when (result) { - is Result.Success -> { - onPayIdFound(payId) - Toast.makeText(context, getString(R.string.create_pay_id_success), Toast.LENGTH_LONG).show() - } - is Result.Failure -> { - Toast.makeText(context, getString(R.string.create_pay_id_error), Toast.LENGTH_LONG).show() - } - } - }) - } - - private fun onPayIdFound(payId: String) { - updateLoadButtonItemsWithNewPayId(payId) - ivPayId.imageAlpha = 255 - this.payId = payId - ivPayId.setOnClickListener { copyText(payId) } - } - - private fun updateLoadButtonItemsWithNewPayId(payId: String) { - val oldPayId = btnLoadItems.firstOrNull { it.contains(PAY_ID_TANGEM) } - btnLoadItems.remove(oldPayId) - btnLoadItems.remove(getString(R.string.loaded_wallet_create_pay_id)) - btnLoadItems.add(payId) - } - - override fun onPause() { - super.onPause() - if (timerHideErrorAndMessage != null) { - timerHideErrorAndMessage!!.cancel() - timerHideErrorAndMessage = null - } - } - - override fun onStart() { - super.onStart() - if (!EventBus.getDefault().isRegistered(this)) - EventBus.getDefault().register(this) - } - - override fun onStop() { - srl?.removeCallbacks(refreshAction) - super.onStop() - } - - override fun onDestroy() { - EventBus.getDefault().unregister(this) - super.onDestroy() - } - - @Subscribe - fun onTransactionFinishWithSuccess(transactionFinishWithSuccess: TransactionFinishWithSuccess) { - refreshAction = Runnable { refresh() } - ctx.card.remainingSignatures -= 1 - srl?.postDelayed(refreshAction, 10000) - } - - @Subscribe - fun onDeleteWalletFinish(deletingWalletFinish: DeletingWalletFinish) { - navigateUp() - } - - override fun onNavigationResult(requestCode: String, resultCode: Int, data: Bundle?) { - when (requestCode) { - Constant.REQUEST_CODE_VERIFY_CARD -> - // action after erase wallet - if (resultCode == Activity.RESULT_OK) - navigateUp() - - Constant.REQUEST_CODE_ENTER_NEW_PIN -> if (resultCode == Activity.RESULT_OK) - if (data != null) - if (data.containsKey(Constant.EXTRA_CONFIRM_PIN)) { - val bundle = PinRequestFragment.callingIntentRequestPin( - PinRequestFragment.Mode.RequestPIN.toString(), ctx, - data.getString(Constant.EXTRA_NEW_PIN) ?: "") - - navigateForResult(Constant.REQUEST_CODE_REQUEST_PIN2_FOR_SWAP_PIN, - R.id.action_loadedWalletFragment_to_pinRequestFragment, - bundle) - } else { - val bundle = PinRequestFragment.callingIntentConfirmPin( - PinRequestFragment.Mode.ConfirmNewPIN.toString(), - data.getString(Constant.EXTRA_NEW_PIN) ?: "") - - navigateForResult(Constant.REQUEST_CODE_ENTER_NEW_PIN, - R.id.action_loadedWalletFragment_to_pinRequestFragment, bundle) - } - - - Constant.REQUEST_CODE_ENTER_NEW_PIN2 -> if (resultCode == Activity.RESULT_OK) - if (data != null) - if (data.containsKey(Constant.EXTRA_CONFIRM_PIN_2)) { - val bundle = PinRequestFragment.callingIntentRequestPin2( - PinRequestFragment.Mode.RequestPIN2.toString(), ctx, - data.getString(Constant.EXTRA_NEW_PIN_2)) - navigateForResult(Constant.REQUEST_CODE_REQUEST_PIN2_FOR_SWAP_PIN, - R.id.action_loadedWalletFragment_to_pinRequestFragment, bundle) - - } else { - val bundle = PinRequestFragment.callingIntentConfirmPin2( - PinRequestFragment.Mode.ConfirmNewPIN2.toString(), - data.getString(Constant.EXTRA_NEW_PIN_2)) - navigateForResult(Constant.REQUEST_CODE_ENTER_NEW_PIN2, - R.id.action_loadedWalletFragment_to_pinRequestFragment, bundle) - } - - Constant.REQUEST_CODE_REQUEST_PIN2_FOR_SWAP_PIN -> if (resultCode == Activity.RESULT_OK) { - if (newPIN == "") - newPIN = ctx.card!!.pin - - if (newPIN2 == "") - newPIN2 = App.pinStorage.piN2 - - val pinSwapWarningDialog = PINSwapWarningDialog() - pinSwapWarningDialog.setOnRefreshPage { - val bundle = PinSwapFragment.callingIntent(newPIN, newPIN2) - navigateForResult(Constant.REQUEST_CODE_SWAP_PIN, R.id.pinSwapFragment, bundle) - } - val bundle = Bundle() - if (!CardProtocol.isDefaultPIN(newPIN) || !CardProtocol.isDefaultPIN2(newPIN2)) - bundle.putString(Constant.EXTRA_MESSAGE, getString(R.string.loaded_wallet_warning_dont_forget_pin)) - else - bundle.putString(Constant.EXTRA_MESSAGE, getString(R.string.loaded_wallet_warning_default_pin)) - pinSwapWarningDialog.arguments = bundle - activity?.supportFragmentManager?.let { pinSwapWarningDialog.show(it, PINSwapWarningDialog.TAG) } - } - - Constant.REQUEST_CODE_SWAP_PIN -> if (resultCode == Activity.RESULT_OK) { - if (data == null) { - val resultData = Bundle() - ctx.saveToBundle(resultData) - resultData.putString(Constant.EXTRA_MODIFICATION, Constant.EXTRA_MODIFICATION_DELETE) - navigateBackWithResult(Activity.RESULT_OK, resultData) - } else { - data.putString(Constant.EXTRA_MODIFICATION, Constant.EXTRA_MODIFICATION_UPDATE) - navigateBackWithResult(Activity.RESULT_OK, data) - } - - } else { - if (data != null && data.containsKey(EXTRA_TANGEM_CARD_UID) && data.containsKey(EXTRA_TANGEM_CARD)) { - val updatedCard = TangemCard(data.getString(EXTRA_TANGEM_CARD_UID)) - updatedCard.loadFromBundle(data.getBundle(EXTRA_TANGEM_CARD)) - ctx.card = updatedCard - } - if (resultCode == Constant.RESULT_INVALID_PIN && requestPIN2Count < 2) { - requestPIN2Count++ - val bundle = PinRequestFragment.callingIntentRequestPin2( - PinRequestFragment.Mode.RequestPIN2.toString(), ctx) - navigateForResult(Constant.REQUEST_CODE_REQUEST_PIN2_FOR_SWAP_PIN, - R.id.action_loadedWalletFragment_to_pinRequestFragment, bundle) - return - } else { - if (data != null && data.containsKey(Constant.EXTRA_MESSAGE)) { - ctx.error = data.getString(Constant.EXTRA_MESSAGE) - } - } - } - - Constant.REQUEST_CODE_PURGE -> if (resultCode == Activity.RESULT_OK) { - if (data == null) { - val resultData = Bundle() - ctx.saveToBundle(resultData) - resultData.putString(Constant.EXTRA_MODIFICATION, Constant.EXTRA_MODIFICATION_DELETE) - navigateBackWithResult(Activity.RESULT_OK, resultData) - } else { - data.putString(Constant.EXTRA_MODIFICATION, Constant.EXTRA_MODIFICATION_UPDATE) - navigateBackWithResult(Activity.RESULT_OK, data) - } - - } else { - if (data != null && data.containsKey(EXTRA_TANGEM_CARD_UID) && data.containsKey(EXTRA_TANGEM_CARD)) { - val updatedCard = TangemCard(data.getString(EXTRA_TANGEM_CARD_UID)) - updatedCard.loadFromBundle(data.getBundle(EXTRA_TANGEM_CARD)) - ctx.card = updatedCard - } - if (resultCode == Constant.RESULT_INVALID_PIN && requestPIN2Count < 2) { - requestPIN2Count++ - - val bundle = Bundle() - bundle.putString(Constant.EXTRA_MODE, PinRequestFragment.Mode.RequestPIN2.toString()) - ctx.saveToBundle(bundle) - navigateForResult(Constant.REQUEST_CODE_REQUEST_PIN2_FOR_PURGE, - R.id.action_loadedWalletFragment_to_pinRequestFragment, bundle) - - return - } else { - if (data != null && data.containsKey(Constant.EXTRA_MESSAGE)) { - ctx.error = data.getString(Constant.EXTRA_MESSAGE) - } - } - updateViews() - } - - Constant.REQUEST_CODE_SEND_TRANSACTION, Constant.REQUEST_CODE_RECEIVE_TRANSACTION -> { - if (resultCode == Activity.RESULT_OK) { -// ctx.card.remainingSignatures = ctx.card.remainingSignatures - 1 - ctx.coinData?.clearInfo() - srl?.postDelayed({ this.refresh() }, 5000) - srl?.isRefreshing = true - updateViews() - } - - if (data != null) { - if (data.containsKey(EXTRA_TANGEM_CARD_UID) && data.containsKey(EXTRA_TANGEM_CARD)) { - val updatedCard = TangemCard(data.getString(EXTRA_TANGEM_CARD_UID)) - updatedCard.loadFromBundle(data.getBundle(EXTRA_TANGEM_CARD)) - ctx.card = updatedCard - } - } - } - else -> if (data != null && data.containsKey(Constant.EXTRA_MESSAGE)) { - when (resultCode) { - Activity.RESULT_CANCELED -> ctx.error = data.getString(Constant.EXTRA_MESSAGE) - Activity.RESULT_OK -> ctx.message = data.getString(Constant.EXTRA_MESSAGE) - } - updateViews() - } - } - } - - override fun onTagDiscovered(tag: Tag) { - startVerify(tag) - } - - override fun onReadStart(cardProtocol: CardProtocol) { - rlProgressBar?.post { rlProgressBar?.visibility = View.VISIBLE } - } - - override fun onReadProgress(protocol: CardProtocol, progress: Int) { - - } - - override fun onReadFinish(cardProtocol: CardProtocol?) { - verifyCardTask = null - if (cardProtocol != null) { - if (cardProtocol.error == null) { - rlProgressBar?.post { - rlProgressBar?.visibility = View.GONE - this.cardProtocol = cardProtocol - if (!cardProtocol.card.isWalletPublicKeyValid) - refresh() - else - updateViews() - - // TODO - remove, only for test&demo reason! - // TODO - begin - if (cardProtocol.card.isIDCard) { - if (cardProtocol.card.hasIDCardData()) { - Toast.makeText(activity, "ID card: ${cardProtocol.card.idCardData.fullName}", Toast.LENGTH_SHORT).show() - } else { - Toast.makeText(activity, "Empty ID card", Toast.LENGTH_SHORT).show() - } - } - // TODO - end - - mpSecondScanSound.start() - } - } else { - // remove last UIDs because of error and no card read - rlProgressBar?.post { - lastReadSuccess = false - if (cardProtocol.error is CardProtocol.TangemException_ExtendedLengthNotSupported) - if (!NoExtendedLengthSupportDialog.allReadyShowed) - activity?.supportFragmentManager?.let { NoExtendedLengthSupportDialog().show(it, NoExtendedLengthSupportDialog.TAG) } - else - Toast.makeText(activity, R.string.general_notification_scan_again, Toast.LENGTH_SHORT).show() - } - } - } - - rlProgressBar?.postDelayed({ - try { - rlProgressBar?.visibility = View.GONE - } catch (e: Exception) { - e.printStackTrace() - } - }, 500) - } - - override fun onReadCancel() { - verifyCardTask = null - rlProgressBar?.postDelayed({ - try { - rlProgressBar?.visibility = View.GONE - } catch (e: Exception) { - e.printStackTrace() - } - }, 500) - } - - override fun onReadWait(msec: Int) { - WaitSecurityDelayDialog.onReadWait(activity as AppCompatActivity?, msec) - } - - override fun onReadBeforeRequest(timeout: Int) { - WaitSecurityDelayDialog.onReadBeforeRequest(activity as AppCompatActivity?, timeout) - } - - override fun onReadAfterRequest() { - WaitSecurityDelayDialog.onReadAfterRequest(activity) - } - - override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { - - } - - fun updateViews() { - if (activity == null || this.view == null) return - - if (timerHideErrorAndMessage != null) { - timerHideErrorAndMessage!!.cancel() - timerHideErrorAndMessage = null - } - - if (ctx.hasError()) { - tvError?.visibility = View.VISIBLE - tvError?.text = ctx.error - } else { - tvError?.visibility = View.GONE - tvError?.text = "" - } - - if (ctx.message == null || ctx.message.isEmpty()) { - tvMessage?.text = "" - tvMessage?.visibility = View.GONE - } else { - tvMessage?.text = ctx.message - tvMessage?.visibility = View.VISIBLE - } - - if (tvError?.visibility == View.VISIBLE || tvMessage?.visibility == View.VISIBLE) { - timerHideErrorAndMessage = Timer() - timerHideErrorAndMessage?.schedule( - timerTask { - activity?.runOnUiThread { - tvMessage?.visibility = View.GONE - tvError?.visibility = View.GONE - // clear only already viewed messages - if (tvMessage?.text == ctx.message) ctx.message = null - if (tvError?.text == ctx.error) ctx.error = null - } - }, - 5000) - } - - if (srl.isRefreshing) { - tvBalanceLine1.setTextColor(resources.getColor(R.color.primary)) - tvBalanceLine1.text = getString(R.string.loaded_wallet_verifying_in_blockchain) - tvBalanceLine2.text = "" - tvBalance.text = "" - tvBalanceEquivalent.text = "" - } else { - val validator = BalanceValidator() - // TODO why attest=false? - validator.check(ctx, false) - context?.let { ContextCompat.getColor(it, validator.color) }?.let { tvBalanceLine1?.setTextColor(it) } - tvBalanceLine1?.text = getString(validator.firstLine) - tvBalanceLine2?.text = getString(validator.getSecondLine(false)) - } - - val engine = CoinEngineFactory.create(ctx) - when { - engine!!.hasBalanceInfo() -> { - @Suppress("DEPRECATION") val html = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) - Html.fromHtml(engine.balanceHTML, Html.FROM_HTML_MODE_LEGACY) - else - Html.fromHtml(engine.balanceHTML) - tvBalance.text = html - tvBalanceEquivalent.text = engine.balanceEquivalent - } - - ctx.card?.offlineBalance != null -> { - @Suppress("DEPRECATION") val html = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) - Html.fromHtml(engine.offlineBalanceHTML, Html.FROM_HTML_MODE_LEGACY) - else - Html.fromHtml(engine.offlineBalanceHTML) - tvBalance.text = html - } - - else -> tvBalance.text = "" - } - - tvWallet.text = ctx.coinData!!.wallet - - if (ctx.card!!.tokenSymbol.length > 1) { - @Suppress("DEPRECATION") val html = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) - Html.fromHtml(ctx.blockchainName, Html.FROM_HTML_MODE_LEGACY) - else - Html.fromHtml(ctx.blockchainName) - tvBlockchain.text = html - } else - tvBlockchain.text = ctx.blockchainName - - if (requestCounter == 0 && engine.hasBalanceInfo()) { - btnExtract.isEnabled = true - btnExtract.backgroundTintList = activeColor - } else { - btnExtract.isEnabled = false - btnExtract.backgroundTintList = inactiveColor - } - - if (engine.isNftToken) { - btnLoad.isEnabled = false - btnLoad.backgroundTintList = inactiveColor - btnExtract.isEnabled = false - btnExtract.backgroundTintList = inactiveColor - } - } - - private fun refresh() { - if (ctx.card == null) return - - // clear all card data and request again - ctx.coinData.clearInfo() - ctx.error = null - ctx.message = null - - LOG.w(TAG, "============= START REFRESH") - requestCounter = 0 - srl?.isRefreshing = true - - if (payId == null || payId == getString(R.string.loaded_wallet_create_pay_id)) { - getPayIdIfApplicable() - } - - updateViews() - - // Bitcoin, Litecoin, BitcoinCash, Stellar - if (ctx.blockchain == Blockchain.Bitcoin || ctx.blockchain == Blockchain.BitcoinTestNet || ctx.blockchain == Blockchain.BitcoinDual || - ctx.blockchain == Blockchain.Litecoin || ctx.blockchain == Blockchain.BitcoinCash || - ctx.blockchain == Blockchain.Stellar || ctx.blockchain == Blockchain.StellarTestNet || ctx.blockchain == Blockchain.StellarAsset) { - ctx.coinData.setIsBalanceEqual(true) - } - - requestVerifyAndGetInfo() - - requestBalanceAndUnspentTransactions() - - if (::viewModel.isInitialized) - viewModel.requestRateInfo(ctx) - - if (requestCounter == 0) { - // if no connection and no requests posted - srl?.isRefreshing = false - updateViews() - } - } - - private fun requestBalanceAndUnspentTransactions() { - if (UtilHelper.isOnline(context as Activity)) { - val coinEngine = CoinEngineFactory.create(ctx) - requestCounter++ - coinEngine!!.requestBalanceAndUnspentTransactions( - object : CoinEngine.BlockchainRequestsCallbacks { - - - override fun onComplete(success: Boolean) { - LOG.i(TAG, "requestBalanceAndUnspentTransactions onComplete: $success, request counter $requestCounter") - if (activity == null) return - requestCounter-- - if (!success) { - LOG.e(TAG, "requestBalanceAndUnspentTransactions ctx.error: " + ctx.error) - } - updateViews() - - if (coinEngine.isBalanceNotZero) showWarningIfPendingTransactionIsPossible() - } - - override fun onProgress() { - if (activity == null) return - LOG.i(TAG, "requestBalanceAndUnspentTransactions onProgress") - updateViews() - } - - override fun allowAdvance(): Boolean { - return try { - context?.let { UtilHelper.isOnline(it) }!! - } catch (e: KotlinNullPointerException) { - e.printStackTrace() - false - } - } - } - ) - } else { - ctx.error = getString(R.string.general_error_no_connection) - updateViews() - } - } - - private fun showWarningIfPendingTransactionIsPossible() { - if (ctx.card.signedHashes > 0 && isNewCid(ctx.card.cidDescription) - && ctx.card.signedHashes != ctx.coinData.sentTransactionsCount) { - AlertDialog.Builder(context) - .setTitle(R.string.dialog_warning) - .setMessage(R.string.loaded_wallet_warning_card_signed_transactions) - .setPositiveButton(R.string.general_ok) { _, _ -> } - .create() - .show() - } - PrefsManager.getInstance().appendCid(ctx.card.cidDescription) - } - - private fun isNewCid(cid: String): Boolean = !PrefsManager.getInstance().getAllCids().contains(cid) - - private fun requestVerifyAndGetInfo() { - if (UtilHelper.isOnline(context as Activity)) { - if ((ctx.card!!.isOnlineVerified == null || !ctx.card!!.isOnlineVerified)) { - LOG.i(TAG, "requestVerifyAndGetInfo") - requestCounter++ - serverApiTangem.cardVerifyAndGetInfo(ctx.card) - } - } else { - ctx.error = getString(R.string.general_error_no_connection) - updateViews() - } - } - - private fun startVerify(tag: Tag?) { - try { - val isoDep = IsoDep.get(tag) - val uid = tag?.id - val sUID = Util.byteArrayToHexString(uid) - if (ctx.card.uid != sUID || cardProtocol != null) { - (activity as? MainActivity)?.nfcManager?.ignoreTag(isoDep.tag) - return - } - - if (lastReadSuccess) - isoDep.timeout = 1000 - else - isoDep.timeout = 65000 - - verifyCardTask = VerifyCardTask(ctx.card, NfcReader((activity as MainActivity).nfcManager, isoDep), - App.localStorage, App.pinStorage, App.firmwaresStorage, this) - verifyCardTask?.start() - } catch (e: Exception) { - e.printStackTrace() - } - } - - private fun doShareWallet(useURI: Boolean) { - if (useURI) { - val txtShare = ctx.coinData.wallet - val intent = Intent(Intent.ACTION_SEND) - intent.type = Constant.INTENT_TYPE_TEXT_PLAIN - intent.putExtra(Intent.EXTRA_SUBJECT, Constant.WALLET_ADDRESS) - intent.putExtra(Intent.EXTRA_TEXT, txtShare) - - val packageManager = activity?.packageManager - val activities = packageManager?.queryIntentActivities(intent, PackageManager.MATCH_ALL) - val isIntentSafe = activities?.size!! > 0 - - if (isIntentSafe) { - // create intent to show chooser - val chooser = Intent.createChooser(intent, getString(R.string.loaded_wallet_chooser_share)) - - // verify the intent will resolve to at least one activity - if (intent.resolveActivity(requireActivity().packageManager) != null) { - startActivity(chooser) - } - } else { - val clipboard = activity?.getSystemService(CLIPBOARD_SERVICE) as ClipboardManager - clipboard.primaryClip = ClipData.newPlainText(txtShare, txtShare) - Toast.makeText(activity, R.string.loaded_wallet_toast_copied, Toast.LENGTH_LONG).show() - } - } else { - copyText(ctx.coinData.wallet) - } - } - - private fun copyText(text: String) { - val clipboard = activity?.getSystemService(CLIPBOARD_SERVICE) as ClipboardManager - clipboard.primaryClip = ClipData.newPlainText(text, text) - Toast.makeText(activity, R.string.loaded_wallet_toast_copied, Toast.LENGTH_LONG).show() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/ui/fragment/wallet/LoadedWalletViewModel.kt b/app/src/main/java/com/tangem/ui/fragment/wallet/LoadedWalletViewModel.kt deleted file mode 100644 index fb1191f6b1..0000000000 --- a/app/src/main/java/com/tangem/ui/fragment/wallet/LoadedWalletViewModel.kt +++ /dev/null @@ -1,76 +0,0 @@ -package com.tangem.ui.fragment.wallet - -import androidx.lifecycle.* -import com.tangem.data.network.ServerApiCommon -import com.tangem.data.network.ServerApiCommon.RateInfoListener -import com.tangem.data.network.model.RateInfoResponse -import com.tangem.server_android.PayIdResponse -import com.tangem.server_android.PayIdService -import com.tangem.server_android.Result -import com.tangem.server_android.SetPayIdResponse -import com.tangem.wallet.TangemContext -import kotlinx.coroutines.CoroutineExceptionHandler - -class LoadedWalletViewModel : ViewModel() { - private val serverApiCommon: ServerApiCommon = ServerApiCommon() - private val payIdService = PayIdService() - private lateinit var ctx: TangemContext - private val rate: MutableLiveData by lazy { - MutableLiveData().also { - loadRateInfo() - } - } - - private val scope = viewModelScope.coroutineContext + CoroutineExceptionHandler { _, ex -> - throw ex - } - - fun getRateInfo(): LiveData { - return rate - } - - fun requestRateInfo(ctx: TangemContext) { - this.ctx = ctx - // TODO - move loadRateInfo to CoinEngine - serverApiCommon.requestRateInfo(ctx.blockchain.currency) - } - - private fun loadRateInfo() { - serverApiCommon.setRateInfoListener(object : RateInfoListener { - override fun onSuccess(rateInfoResponse: RateInfoResponse?) { - rate.value = rateInfoResponse!!.data!!.quote!!.usd!!.price - } - - override fun onFail(message: String?) { - ctx.error = message - } - }) - } - - fun getPayId(cardId: String, publicKey: String): LiveData> { - return liveData(viewModelScope.coroutineContext) { - emit( - payIdService.getPayId( - cardId, - publicKey - ) - ) - } - } - - fun setPayId( - cardId: String, - publicKey: String, - payId: String, - address: String, - network: String - ): LiveData> { - return liveData(scope) { - emit( - payIdService.setPayId( - cardId, publicKey, payId, address, network - ) - ) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/ui/navigation/NavigationResult.kt b/app/src/main/java/com/tangem/ui/navigation/NavigationResult.kt deleted file mode 100644 index ffa42a3976..0000000000 --- a/app/src/main/java/com/tangem/ui/navigation/NavigationResult.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.tangem.ui.navigation - -import android.os.Bundle - -data class NavigationResult(val requestCode: String, val resultCode: Int, val data: Bundle? = null) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/ui/navigation/NavigationResultListener.kt b/app/src/main/java/com/tangem/ui/navigation/NavigationResultListener.kt deleted file mode 100644 index f0e1681864..0000000000 --- a/app/src/main/java/com/tangem/ui/navigation/NavigationResultListener.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.tangem.ui.navigation - -import android.os.Bundle - -interface NavigationResultListener { - fun onNavigationResult(requestCode: String, resultCode: Int, data: Bundle? = null) -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/util/Analytics.kt b/app/src/main/java/com/tangem/util/Analytics.kt deleted file mode 100644 index 3885d57da0..0000000000 --- a/app/src/main/java/com/tangem/util/Analytics.kt +++ /dev/null @@ -1,54 +0,0 @@ -package com.tangem.util - -import android.content.Context -import android.os.Bundle -import androidx.core.os.bundleOf -import com.google.firebase.analytics.FirebaseAnalytics -import com.google.firebase.crashlytics.FirebaseCrashlytics -import com.google.firebase.perf.FirebasePerformance -import com.tangem.data.dp.PrefsManager -import com.tangem.wallet.BuildConfig -import com.tangem.wallet.R -import com.tangem.wallet.TangemContext - -enum class AnalyticsEvent(val event: String) { - CARD_IS_SCANNED("card_is_scanned"), - TRANSACTION_IS_SENT("transaction_is_sent"), - READY_TO_SCAN("ready_to_scan"), - READY_TO_SIGN("ready_to_sign"), - SIGNED("signed"), - ANALYTICS_TURNED_OFF("analytics_turned_off"), - CARD_TAP_USER_TIMER("CardTapUserTimer"), - ; -} - -enum class AnalyticsParam(val param: String) { - BLOCKCHAIN("blockchain"), - BATCH_ID("batch_id"), - FIRMWARE("firmware"), - ; -} - -class Analytics { - companion object { - fun setCardData(ctx: TangemContext): Bundle { - return bundleOf( - AnalyticsParam.BLOCKCHAIN.param to ctx.blockchainName, - AnalyticsParam.BATCH_ID.param to ctx.card.batch, - AnalyticsParam.FIRMWARE.param to ctx.card.firmwareVersion - ) - } - - fun isEnabled(): Boolean { - return PrefsManager.getInstance().getSettingsBoolean(R.string.pref_analytics, true) - } - - fun setFirebaseEnabled(context: Context, enable: Boolean = isEnabled()) { - FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(enable) - FirebaseAnalytics.getInstance(context).setAnalyticsCollectionEnabled( - enable && !BuildConfig.DEBUG - ) - FirebasePerformance.getInstance().isPerformanceCollectionEnabled = enable - } - } -} diff --git a/app/src/main/java/com/tangem/util/ByteUtil.java b/app/src/main/java/com/tangem/util/ByteUtil.java deleted file mode 100644 index 6a55a35daa..0000000000 --- a/app/src/main/java/com/tangem/util/ByteUtil.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.tangem.util; - -public class ByteUtil { - - public static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; - public static byte[] and(byte[] b1, byte[] b2) { - if (b1.length != b2.length) throw new RuntimeException("Array sizes differ"); - byte[] ret = new byte[b1.length]; - for (int i = 0; i < ret.length; i++) { - ret[i] = (byte) (b1[i] & b2[i]); - } - return ret; - } - - public static byte[] or(byte[] b1, byte[] b2) { - if (b1.length != b2.length) throw new RuntimeException("Array sizes differ"); - byte[] ret = new byte[b1.length]; - for (int i = 0; i < ret.length; i++) { - ret[i] = (byte) (b1[i] | b2[i]); - } - return ret; - } - - public static boolean isNullOrZeroArray(byte[] array){ - return (array == null) || (array.length == 0); - } - - public static boolean isSingleZero(byte[] array){ - return (array.length == 1 && array[0] == 0); - } - - public static int length(byte[]... bytes) { - int result = 0; - for (byte[] array : bytes) { - result += (array == null) ? 0 : array.length; - } - return result; - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/util/CommonUtil.java b/app/src/main/java/com/tangem/util/CommonUtil.java deleted file mode 100644 index 613fefd364..0000000000 --- a/app/src/main/java/com/tangem/util/CommonUtil.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.tangem.util; - -import android.content.Context; -import android.content.Intent; -import android.content.pm.ResolveInfo; -import android.net.Uri; -import android.util.Log; - -import com.tangem.wallet.R; - -import java.io.File; -import java.util.List; - -public class CommonUtil { - - public static void sendEmail(Context context, File zipFile, String logTag, String subject, String text, File[] fileLocations) { - if (zipFile != null) return; - try { - Intent intent = new Intent(Intent.ACTION_SEND) - //.setData(new Uri.Builder().scheme("mailto").build()) - .setType("text/plain") - .putExtra(Intent.EXTRA_EMAIL, new String[]{"android@tangem.com"}) - .putExtra(Intent.EXTRA_SUBJECT, subject) - .putExtra(Intent.EXTRA_TEXT, text); - - if (fileLocations != null && fileLocations.length > 0) { - String[] fileNames = new String[fileLocations.length]; - for (int i = 0; i < fileLocations.length; i++) - fileNames[i] = fileLocations[i].getAbsolutePath(); - zipFile = File.createTempFile("tangemLogs", ".zip", fileLocations[0].getParentFile()); - Compress compress = new Compress(fileNames, zipFile.getAbsolutePath()); - compress.zip(); - Log.e(logTag, String.format("Send %d bytes zip with logs", zipFile.length())); - Uri attachment = Uri.parse("content://" + context.getString(R.string.log_file_provider_authorities) + "/" + zipFile.getName()); - - intent.putExtra(Intent.EXTRA_STREAM, attachment); - zipFile.deleteOnExit(); - } - - List activities = context.getPackageManager().queryIntentActivities(intent, 0); - boolean isIntentSafe = activities.size() > 0; - - if (isIntentSafe) { - context.startActivity(intent); - } - - } catch (Exception e) { - e.printStackTrace(); - } - } - -} diff --git a/app/src/main/java/com/tangem/util/Compress.java b/app/src/main/java/com/tangem/util/Compress.java deleted file mode 100644 index 20deeefd86..0000000000 --- a/app/src/main/java/com/tangem/util/Compress.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.tangem.util; - -import android.util.Log; - -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.util.zip.ZipEntry; -import java.util.zip.ZipOutputStream; - -public class Compress { - private static final int BUFFER = 2048; - - private String[] _files; - private String _zipFile; - - public Compress(String[] files, String zipFile) { - _files = files; - _zipFile = zipFile; - } - - public void zip() { - try { - BufferedInputStream origin = null; - FileOutputStream dest = new FileOutputStream(_zipFile); - - ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(dest)); - - byte data[] = new byte[BUFFER]; - - for (String _file : _files) { - Log.v("Compress", "Adding: " + _file); - FileInputStream fi = new FileInputStream(_file); - origin = new BufferedInputStream(fi, BUFFER); - ZipEntry entry = new ZipEntry(_file.substring(_file.lastIndexOf("/") + 1)); - out.putNextEntry(entry); - int count; - while ((count = origin.read(data, 0, BUFFER)) != -1) { - out.write(data, 0, count); - } - origin.close(); - } - - out.close(); - } catch (Exception e) { - e.printStackTrace(); - } - - } -} diff --git a/app/src/main/java/com/tangem/util/CryptoUtil.java b/app/src/main/java/com/tangem/util/CryptoUtil.java deleted file mode 100644 index bc6eb16d42..0000000000 --- a/app/src/main/java/com/tangem/util/CryptoUtil.java +++ /dev/null @@ -1,271 +0,0 @@ -package com.tangem.util; - -import android.util.Log; - -import com.tangem.tangem_card.util.Util; -import com.tangem.wallet.ECDSASignatureETH; - -import org.bouncycastle.asn1.ASN1EncodableVector; -import org.bouncycastle.asn1.ASN1Integer; -import org.bouncycastle.asn1.DERSequence; -import org.bouncycastle.asn1.sec.SECNamedCurves; -import org.bouncycastle.asn1.x9.X9ECParameters; -import org.bouncycastle.asn1.x9.X9IntegerConverter; -import org.bouncycastle.crypto.params.ECDomainParameters; -import org.bouncycastle.crypto.params.ECPrivateKeyParameters; -import org.bouncycastle.crypto.params.ECPublicKeyParameters; -import org.bouncycastle.crypto.signers.ECDSASigner; -import org.bouncycastle.jce.ECNamedCurveTable; -import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec; -import org.bouncycastle.jce.spec.ECPublicKeySpec; -import org.bouncycastle.math.ec.ECAlgorithms; -import org.bouncycastle.math.ec.ECCurve; -import org.bouncycastle.math.ec.ECPoint; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.math.BigInteger; -import java.security.InvalidKeyException; -import java.security.KeyFactory; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import java.security.PublicKey; -import java.security.Signature; -import java.security.SignatureException; -import java.security.spec.InvalidKeySpecException; -import java.util.Arrays; - -import static org.bitcoinj.core.ECKey.CURVE; -import static org.bitcoinj.core.ECKey.HALF_CURVE_ORDER; - -/** - * Created by Ilia on 15.02.2018. - */ - -public class CryptoUtil { - - public static boolean checkHashSign2(byte[] pub, byte[] hash, BigInteger r, BigInteger s) - { - ECDSASigner signer = new ECDSASigner(); - - ECPublicKeyParameters params = new ECPublicKeyParameters(CURVE.getCurve().decodePoint(pub), CURVE); - signer.init(false, params); - return signer.verifySignature(hash, r, s); - } - - public static boolean isCanonical(BigInteger s) { - return s.compareTo(HALF_CURVE_ORDER) <= 0; - } - - public static BigInteger toCanonicalised(BigInteger s) { - - // The order of the curve is the number of valid points that exist on that curve. If S is in the upper - // half of the number of valid points, then bring it back to the lower half. Otherwise, imagine that - // N = 10 - // s = 8, so (-8 % 10 == 2) thus both (r, 8) and (r, 2) are valid solutions. - // 10 - 8 == 2, giving us always the latter solution, which is canonical. - if(!isCanonical(s)) { - BigInteger canon = CURVE.getN().subtract(s); - Log.e("TX_SIGN", "non Canonical S"); - return canon; - } - - return s; - - } - - private static ECPoint decompressKey(BigInteger xBN, boolean yBit) { - X9IntegerConverter x9 = new X9IntegerConverter(); - byte[] compEnc = x9.integerToBytes(xBN, 1 + x9.getByteLength(CURVE.getCurve())); - compEnc[0] = (byte) (yBit ? 0x03 : 0x02); - return CURVE.getCurve().decodePoint(compEnc); - } - - public static byte[] recoverPubBytesFromSignature(int recId, ECDSASignatureETH sig, byte[] messageHash) { - // 1.0 For j from 0 to h (h == recId here and the loop is outside this function) - // 1.1 Let x = r + jn - - X9ECParameters params = SECNamedCurves.getByName("secp256k1"); - ECDomainParameters CURVE2 = new ECDomainParameters(params.getCurve(), params.getG(), params.getN(), params.getH()); - - BigInteger n = CURVE2.getN(); // Curve order. - BigInteger i = BigInteger.valueOf((long) recId / 2); - BigInteger x = sig.r.add(i.multiply(n)); - // 1.2. Convert the integer x to an octet string X of length mlen using the conversion routine - // specified in Section 2.3.7, where mlen = ⌈(log2 p)/8⌉ or mlen = ⌈m/8⌉. - // 1.3. Convert the octet string (16 set binary digits)||X to an elliptic curve point R using the - // conversion routine specified in Section 2.3.4. If this conversion routine outputs “invalid”, then - // do another iteration of Step 1. - // - // More concisely, what these points mean is to use X as a compressed public key. - ECCurve.Fp curve = (ECCurve.Fp) CURVE2.getCurve(); - BigInteger prime = curve.getQ(); // Bouncy Castle is not consistent about the letter it uses for the prime. - if (x.compareTo(prime) >= 0) { - // Cannot have point co-ordinates larger than this as everything takes place modulo Q. - return null; - } - // Compressed keys require you to know an extra bit of data about the y-coord as there are two possibilities. - // So it's encoded in the recId. - ECPoint R = decompressKey(x, (recId & 1) == 1); - // 1.4. If nR != point at infinity, then do another iteration of Step 1 (callers responsibility). - if (!R.multiply(n).isInfinity()) - return null; - // 1.5. Compute e from M using Steps 2 and 3 of ECDSA signature verification. - BigInteger e = new BigInteger(1, messageHash); - // 1.6. For k from 1 to 2 do the following. (loop is outside this function via iterating recId) - // 1.6.1. Compute a candidate public key as: - // Q = mi(r) * (sR - eG) - // - // Where mi(x) is the modular multiplicative inverse. We transform this into the following: - // Q = (mi(r) * s ** R) + (mi(r) * -e ** G) - // Where -e is the modular additive inverse of e, that is z such that z + e = 0 (mod n). In the above equation - // ** is point multiplication and + is point addition (the EC group operator). - // - // We can find the additive inverse by subtracting e from zero then taking the mod. For example the additive - // inverse of 3 modulo 11 is 8 because 3 + 8 mod 11 = 0, and -3 mod 11 = 8. - BigInteger eInv = BigInteger.ZERO.subtract(e).mod(n); - BigInteger rInv = sig.r.modInverse(n); - BigInteger srInv = rInv.multiply(sig.s).mod(n); - BigInteger eInvrInv = rInv.multiply(eInv).mod(n); - ECPoint.Fp q = (ECPoint.Fp) ECAlgorithms.sumOfTwoMultiplies(CURVE2.getG(), eInvrInv, R, srInv); - return q.getEncoded(/* compressed */ false); - } - - public static byte[] calcSign(byte[] priv, byte[] hash) - { - ECDSASigner signer = new ECDSASigner(); - BigInteger d = new BigInteger(priv); - ECPrivateKeyParameters params = new ECPrivateKeyParameters(d, CURVE); - signer.init(true, params); - BigInteger[] rs = signer.generateSignature(hash); - byte[] r = rs[0].toByteArray(); - byte[] s = rs[1].toByteArray(); - byte[] sign = new byte[64]; - for(int i = 0; i < 32; ++i) - { - sign[i] = r[i]; - sign[i+32] = s[i]; - } - return sign; - - } - - public static boolean isEncodingCanonical(byte[] signature) { - // See Bitcoin Core's IsCanonicalSignature, https://bitcointalk.org/index.php?topic=8392.msg127623#msg127623 - // A canonical signature exists of: <30> <02> <02> - // Where R and S are not negative (their first byte has its highest bit not set), and not - // excessively padded (do not init with a 0 byte, unless an otherwise negative number follows, - // in which case a single 0 byte is necessary and even required). - if (signature.length < 9 || signature.length > 73) - return false; - - int hashType = (signature[signature.length-1] & 0xff) & ~0x80; // mask the byte to prevent sign-extension hurting us - if (hashType < 1 || hashType > 3) - return false; - - // "wrong type" "wrong length marker" - if ((signature[0] & 0xff) != 0x30 || (signature[1] & 0xff) != signature.length-3) - return false; - - int lenR = signature[3] & 0xff; - if (5 + lenR >= signature.length || lenR == 0) - return false; - int lenS = signature[5+lenR] & 0xff; - if (lenR + lenS + 7 != signature.length || lenS == 0) - return false; - - // R value type mismatch R value negative - if (signature[4-2] != 0x02 || (signature[4] & 0x80) == 0x80) - return false; - if (lenR > 1 && signature[4] == 0x00 && (signature[4+1] & 0x80) != 0x80) - return false; // R value excessively padded - - // S value type mismatch S value negative - if (signature[6 + lenR - 2] != 0x02 || (signature[6 + lenR] & 0x80) == 0x80) - return false; - if (lenS > 1 && signature[6 + lenR] == 0x00 && (signature[6 + lenR + 1] & 0x80) != 0x80) - return false; // S value excessively padded - - return true; - } - - - public static boolean VerifySign(byte[] tlvPublicKey, byte[] data, byte[] tlvSignature) throws IOException, SignatureException, InvalidKeyException, InvalidKeySpecException, NoSuchAlgorithmException, NoSuchProviderException { - Signature signature = Signature.getInstance("SHA256withECDSA"); - ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256k1"); - KeyFactory factory = KeyFactory.getInstance("EC", "SC"); - - ECPoint p1 = spec.getCurve().decodePoint(tlvPublicKey); - ECPublicKeySpec keySpec = new ECPublicKeySpec(p1, spec); - - PublicKey publicKey = factory.generatePublic(keySpec); - signature.initVerify(publicKey); - signature.update(data); - - ASN1EncodableVector v = new ASN1EncodableVector(); - int size = tlvSignature.length / 2; - v.add(/*r*/new ASN1Integer(new BigInteger(1, Arrays.copyOfRange(tlvSignature, 0, size)))); - v.add(/*s*/new ASN1Integer(new BigInteger(1, Arrays.copyOfRange(tlvSignature, size, size * 2)))); - byte[] sigDer = new DERSequence(v).getEncoded(); - - return signature.verify(sigDer); - } - - private static byte[] leaderZero(byte[] s) - { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - if (s[0] > 0x7f) { - baos.write((byte) 0x00); - } - for(int i = 0; i < 32; ++i) - baos.write(s[i]); - - return baos.toByteArray(); - } - public static boolean checkHashSign(byte[] pub, byte[] hash, byte[] sign) - { - byte[] rtmp = new byte[32]; - byte[] stmp = new byte[32]; - - for(int i = 0; i< 32; ++i) - { - rtmp[i] = sign[i]; - stmp[i] = sign[i+32]; - } - - leaderZero(rtmp); - byte[] r2 = leaderZero(rtmp); - byte[] s2 = leaderZero(stmp); - - BigInteger r = new BigInteger(r2); - BigInteger s = new BigInteger(s2); - ECDSASigner signer = new ECDSASigner(); - - ECPublicKeyParameters params = new ECPublicKeyParameters(CURVE.getCurve().decodePoint(pub), CURVE); - signer.init(false, params); - return signer.verifySignature(hash, r, s); - } - public static byte[] doubleSha256(byte[] bytes) { - try { - MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); - return sha256.digest(sha256.digest(bytes)); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(e); - } - } - - public static byte[] sha256ripemd160(byte[] publicKey) { - try { - MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); - byte[] sha256hash = sha256.digest(publicKey); - byte[] hashedPublicKey = Util.calculateRIPEMD160(sha256hash); - return hashedPublicKey; - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(e); - } catch (NoSuchProviderException e) { - e.printStackTrace(); - } - return null; - } -} diff --git a/app/src/main/java/com/tangem/util/DecimalDigitsInputFilter.java b/app/src/main/java/com/tangem/util/DecimalDigitsInputFilter.java deleted file mode 100644 index d3c456c747..0000000000 --- a/app/src/main/java/com/tangem/util/DecimalDigitsInputFilter.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.tangem.util; - -import android.text.InputFilter; -import android.text.Spanned; - -public class DecimalDigitsInputFilter implements InputFilter { - private final int decimalDigits; - - /** - * Constructor. - * - * @param decimalDigits maximum decimal digits - */ - public DecimalDigitsInputFilter(int decimalDigits) { - this.decimalDigits = decimalDigits; - } - - @Override - public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) { - int dotPos = -1; - int len = dest.length(); - for (int i = 0; i < len; i++) { - char c = dest.charAt(i); - if (c == '.' || c == ',') { - dotPos = i; - break; - } - } - - if (dotPos >= 0) { - // protects against many dots - if (source.equals(".") || source.equals(",")) { - return ""; - } - - // if the text is entered before the dot - if (dend <= dotPos) { - return null; - } - - if (len - dotPos > decimalDigits) { - return ""; - } - } - - return null; - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/util/DerEncodingUtil.java b/app/src/main/java/com/tangem/util/DerEncodingUtil.java deleted file mode 100644 index f3a88131df..0000000000 --- a/app/src/main/java/com/tangem/util/DerEncodingUtil.java +++ /dev/null @@ -1,132 +0,0 @@ -package com.tangem.util; - -import com.tangem.wallet.btc.BitcoinOutputStream; - -import org.spongycastle.asn1.ASN1Integer; -import org.spongycastle.asn1.DERSequenceGenerator; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.math.BigInteger; - -/** - * Created by Ilia on 15.02.2018. - */ - -public class DerEncodingUtil { - - public static byte[] PackInteger(byte[] s) - { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - baos.write((byte)0x02); - - byte length = (byte)s.length; - if (s[0] > 0x7f) { - baos.write((byte)(length+1)); - baos.write((byte) 0x00); - } - else { - baos.write((byte)length); - } - - for(int i = 0; i < length; ++i) - baos.write(s[i]); - - return baos.toByteArray(); - } - - public static byte[] packSignDer(BigInteger r, BigInteger s, byte[] pubKey) throws IOException - { - byte[] signDer = DerEncoding(r, s); - BitcoinOutputStream packKey = new BitcoinOutputStream(); - - packKey.write((byte)0x41); - packKey.write(pubKey); - - byte[] keyArray = packKey.toByteArray(); - - BitcoinOutputStream result = new BitcoinOutputStream(); - result.write((byte)(signDer.length+1)); - result.write(signDer); - result.write((byte)0x1); - - result.write(keyArray); - - return result.toByteArray(); - - } - - public static byte[] packSignDerBitcoinCash(BigInteger r, BigInteger s, byte[] pubKey) throws IOException - { - byte[] signDer = DerEncoding(r, s); - BitcoinOutputStream packKey = new BitcoinOutputStream(); - - packKey.write((byte)0x21); //compress key - packKey.write(pubKey); - - byte[] keyArray = packKey.toByteArray(); - - BitcoinOutputStream result = new BitcoinOutputStream(); - result.write((byte)(signDer.length+1)); - result.write(signDer); - result.write((byte)0x41); - - result.write(keyArray); - - return result.toByteArray(); - - } - - - public static byte[] DerEncoding(BigInteger r, BigInteger s) throws IOException { - ByteArrayOutputStream bos = new ByteArrayOutputStream(72); - DERSequenceGenerator seq = new DERSequenceGenerator(bos); - seq.addObject(new ASN1Integer(r)); - seq.addObject(new ASN1Integer(s)); - seq.close(); - return bos.toByteArray(); - } - -// public static byte[] DerEncoding(byte[] sign) -// { -// byte[] r = sign; -// byte[] s = new byte[32]; -// for(int i =0; i < 32; ++i) -// { -// s[i] = sign[i+32]; -// } -// -// byte[] newR = PackInteger(r); -// byte[] newS = PackInteger(s); -// -// ByteArrayOutputStream baos = new ByteArrayOutputStream(); -// baos.write((byte)(newR.length+newS.length+2)); -// baos.write((byte)newR.length); -// baos.write(newR, 0, newR.length); -// baos.write((byte)newS.length); -// baos.write(newS, 0, newS.length); -// -// return baos.toByteArray(); -// } -// -// public static byte[] DerEncodingBI(BigInteger[] sign) -// { -// byte[] r = sign[0].toByteArray(); -// byte[] s = sign[1].toByteArray(); -// -// byte[] newR = PackInteger(r); -// byte[] newS = PackInteger(s); -// -// ByteArrayOutputStream baos = new ByteArrayOutputStream(); -// -// baos.write((byte)(newR.length+newS.length+2)); -// -// baos.write((byte)newR.length); -// baos.write(newR, 0, newR.length); -// -// baos.write((byte)newS.length); -// baos.write(newS, 0, newS.length); -// -// return baos.toByteArray(); -// } -} diff --git a/app/src/main/java/com/tangem/util/DeviceName.java b/app/src/main/java/com/tangem/util/DeviceName.java deleted file mode 100644 index f577dd16bb..0000000000 --- a/app/src/main/java/com/tangem/util/DeviceName.java +++ /dev/null @@ -1,2091 +0,0 @@ -package com.tangem.util; - -/** - * Created by Ilia on 20.04.2018. - */ -/* - * Copyright (C) 2017 Jared Rummler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - import android.Manifest; - import android.content.Context; - import android.content.SharedPreferences; - import android.content.pm.PackageManager; - import android.net.ConnectivityManager; - import android.net.NetworkInfo; - import android.os.Build; - import android.os.Handler; - import android.os.Looper; - import androidx.annotation.WorkerThread; - import android.text.TextUtils; - import java.io.BufferedReader; - import java.io.IOException; - import java.io.InputStreamReader; - import java.net.HttpURLConnection; - import java.net.URL; - import java.util.Locale; - import org.json.JSONArray; - import org.json.JSONException; - import org.json.JSONObject; - -// @formatter:off -/** - *

Get the consumer friendly name of an Android device.

- * - *

On many popular devices the market name of the device is not available. For example, on the - * Samsung Galaxy S6 the value of {@link Build#MODEL} could be "SM-G920F", "SM-G920I", "SM-G920W8", - * etc.

- * - *

See the usages below to get the consumer friends name of a device:

- * - *

Get the name of the current device:

- * - *
- * String deviceName = DeviceName.getDeviceName();
- * 
- * - *

The above code will get the correct device name for the top 600 Android devices. If the - * device is unrecognized, then Build.MODEL is returned.

- * - *

Get the name of a device using the device's codename:

- * - *
- * // Retruns "Moto X Style"
- * DeviceName.getDeviceName("clark", "Unknown device");
- * 
- * - *

Get information about the device:

- * - *
- * DeviceName.with(context).request(new DeviceName.Callback() {
- *
- *   @Override public void onFinished(DeviceName.DeviceInfo info, Exception error) {
- *     String manufacturer = info.manufacturer;  // "Samsung"
- *     String name = info.marketName;            // "Galaxy S6 Edge"
- *     String model = info.model;                // "SM-G925I"
- *     String codename = info.codename;          // "zerolte"
- *     String deviceName = info.getName();       // "Galaxy S6 Edge"
- *     // FYI: We are on the UI thread.
- *   }
- * });
- * 
- * - *

The above code loads JSON from a generated list of device names based on Google's maintained - * list. It will be up-to-date with Google's supported device list so that you will get the correct - * name for new or unknown devices. This supports over 10,000 devices.

- * - *

This will only make a network call once. The value is saved to SharedPreferences for future - * calls.

- */ - -public class DeviceName { - - // @formatter:on - - // JSON which is derived from Google's PDF document which contains all devices on Google Play. - // To get the URL to the JSON file which contains information about the device name: - // String url = String.format(DEVICE_JSON_URL, Build.DEVICE); - private static final String DEVICE_JSON_URL = - "https://raw.githubusercontent.com/jaredrummler/AndroidDeviceNames/master/json/devices/%s.json"; - - // Preference filename for storing device info so we don't need to download it again. - private static final String SHARED_PREF_NAME = "device_names"; - - /** - * Create a new request to get information about a device. - * - * @param context - * the application context - * @return a new Request instance. - */ - public static Request with(Context context) { - return new Request(context.getApplicationContext()); - } - - /** - * Get the consumer friendly name of the device. - * - * @return the market name of the current device. - * @see #getDeviceName(String, String) - */ - public static String getDeviceName() { - return getDeviceName(Build.DEVICE, Build.MODEL, capitalize(Build.MODEL)); - } - - /** - * Get the consumer friendly name of a device. - * - * @param codename - * the value of the system property "ro.product.device" ({@link Build#DEVICE}) - * or - * the value of the system property "ro.product.model" ({@link Build#MODEL}) - * @param fallback - * the fallback name if the device is unknown. Usually the value of the system property - * "ro.product.model" ({@link Build#MODEL}) - * @return the market name of a device or {@code fallback} if the device is unknown. - */ - public static String getDeviceName(String codename, String fallback) { - return getDeviceName(codename, codename, fallback); - } - - /** - * Get the consumer friendly name of a device. - * - * @param codename - * the value of the system property "ro.product.device" ({@link Build#DEVICE}). - * @param model - * the value of the system property "ro.product.model" ({@link Build#MODEL}). - * @param fallback - * the fallback name if the device is unknown. Usually the value of the system property - * "ro.product.model" ({@link Build#MODEL}) - * @return the market name of a device or {@code fallback} if the device is unknown. - */ - public static String getDeviceName(String codename, String model, String fallback) { - // ---------------------------------------------------------------------------- - // Acer - if ((codename != null && codename.equals("acer_S57")) - || (model != null && model.equals("S57"))) { - return "Liquid Jade Z"; - } - if ((codename != null && codename.equals("acer_t08")) - || (model != null && model.equals("T08"))) { - return "Liquid Zest Plus"; - } - // ---------------------------------------------------------------------------- - // Asus - if ((codename != null && (codename.equals("grouper") - || codename.equals("tilapia")))) { - return "Nexus 7 (2012)"; - } - if ((codename != null && (codename.equals("deb") - || codename.equals("flo")))) { - return "Nexus 7 (2013)"; - } - // ---------------------------------------------------------------------------- - // Google - if ((codename != null && codename.equals("sailfish"))) { - return "Pixel"; - } - if ((codename != null && codename.equals("walleye"))) { - return "Pixel 2"; - } - if ((codename != null && codename.equals("taimen"))) { - return "Pixel 2 XL"; - } - if ((codename != null && codename.equals("dragon"))) { - return "Pixel C"; - } - if ((codename != null && codename.equals("marlin"))) { - return "Pixel XL"; - } - // ---------------------------------------------------------------------------- - // HTC - if ((codename != null && codename.equals("flounder"))) { - return "Nexus 9"; - } - // ---------------------------------------------------------------------------- - // Huawei - if ((codename != null && (codename.equals("HWBND-H"))) - || (model != null && (model.equals("BND-L21") - || model.equals("BND-L24")))) { - return "Honor 7X"; - } - if (model != null && model.contains("WAS-LX")) { - return "P10 lite"; - } - if ((codename != null && codename.equals("HWBKL")) - || (model != null && model.equals("BKL-L09"))) { - return "Honor View 10"; - } - if ((codename != null && (codename.equals("HWALP"))) - || (model != null && (model.equals("ALP-AL00") - || model.equals("ALP-L09") - || model.equals("ALP-L29") - || model.equals("ALP-TL00")))) { - return "Mate 10"; - } - if ((codename != null && (codename.equals("HWMHA"))) - || (model != null && (model.equals("MHA-AL00") - || model.equals("MHA-L09") - || model.equals("MHA-L29") - || model.equals("MHA-TL00")))) { - return "Mate 9"; - } - if ((codename != null && codename.equals("angler"))) { - return "Nexus 6P"; - } - // ---------------------------------------------------------------------------- - // LGE - if ((codename != null && (codename.equals("g2"))) - || (model != null && (model.equals("LG-D800") - || model.equals("LG-D801") - || model.equals("LG-D802") - || model.equals("LG-D802T") - || model.equals("LG-D802TR") - || model.equals("LG-D803") - || model.equals("LG-D805") - || model.equals("LG-D806") - || model.equals("LG-F320K") - || model.equals("LG-F320L") - || model.equals("LG-F320S") - || model.equals("LG-LS980") - || model.equals("VS980 4G")))) { - return "LG G2"; - } - if ((codename != null && (codename.equals("g3"))) - || (model != null && (model.equals("AS985") - || model.equals("LG-AS990") - || model.equals("LG-D850") - || model.equals("LG-D851") - || model.equals("LG-D852") - || model.equals("LG-D852G") - || model.equals("LG-D855") - || model.equals("LG-D856") - || model.equals("LG-D857") - || model.equals("LG-D858") - || model.equals("LG-D858HK") - || model.equals("LG-D859") - || model.equals("LG-F400K") - || model.equals("LG-F400L") - || model.equals("LG-F400S") - || model.equals("LGL24") - || model.equals("LGLS990") - || model.equals("LGUS990") - || model.equals("LGV31") - || model.equals("VS985 4G")))) { - return "LG G3"; - } - if ((codename != null && (codename.equals("p1"))) - || (model != null && (model.equals("AS986") - || model.equals("LG-AS811") - || model.equals("LG-AS991") - || model.equals("LG-F500K") - || model.equals("LG-F500L") - || model.equals("LG-F500S") - || model.equals("LG-H810") - || model.equals("LG-H811") - || model.equals("LG-H812") - || model.equals("LG-H815") - || model.equals("LG-H818") - || model.equals("LG-H819") - || model.equals("LGLS991") - || model.equals("LGUS991") - || model.equals("LGV32") - || model.equals("VS986")))) { - return "LG G4"; - } - if ((codename != null && (codename.equals("h1"))) - || (model != null && (model.equals("LG-F700K") - || model.equals("LG-F700L") - || model.equals("LG-F700S") - || model.equals("LG-H820") - || model.equals("LG-H820PR") - || model.equals("LG-H830") - || model.equals("LG-H831") - || model.equals("LG-H850") - || model.equals("LG-H858") - || model.equals("LG-H860") - || model.equals("LG-H868") - || model.equals("LGAS992") - || model.equals("LGLS992") - || model.equals("LGUS992") - || model.equals("RS988") - || model.equals("VS987")))) { - return "LG G5"; - } - if ((codename != null && (codename.equals("lucye"))) - || (model != null && (model.equals("LG-AS993") - || model.equals("LG-H870") - || model.equals("LG-H870AR") - || model.equals("LG-H870DS") - || model.equals("LG-H870I") - || model.equals("LG-H870S") - || model.equals("LG-H871") - || model.equals("LG-H872") - || model.equals("LG-H872PR") - || model.equals("LG-H873") - || model.equals("LG-LS993") - || model.equals("LGM-G600K") - || model.equals("LGM-G600L") - || model.equals("LGM-G600S") - || model.equals("LGUS997") - || model.equals("VS988")))) { - return "LG G6"; - } - if ((codename != null && codename.equals("mako"))) { - return "Nexus 4"; - } - if ((codename != null && codename.equals("hammerhead"))) { - return "Nexus 5"; - } - if ((codename != null && codename.equals("bullhead"))) { - return "Nexus 5X"; - } - // ---------------------------------------------------------------------------- - // Motorola - if ((codename != null && codename.equals("shamu"))) { - return "Nexus 6"; - } - // ---------------------------------------------------------------------------- - // OnePlus - if ((codename != null && codename.equals("OnePlus3")) - || (model != null && model.equals("ONEPLUS A3000"))) { - return "OnePlus3"; - } - if ((codename != null && codename.equals("OnePlus3T")) - || (model != null && model.equals("ONEPLUS A3000"))) { - return "OnePlus3T"; - } - if ((codename != null && codename.equals("OnePlus5")) - || (model != null && model.equals("ONEPLUS A5000"))) { - return "OnePlus5"; - } - if ((codename != null && codename.equals("OnePlus5T")) - || (model != null && model.equals("ONEPLUS A5010"))) { - return "OnePlus5T"; - } - // ---------------------------------------------------------------------------- - // Samsung - if ((codename != null && (codename.equals("a53g") - || codename.equals("a5lte") - || codename.equals("a5ltechn") - || codename.equals("a5ltectc") - || codename.equals("a5ltezh") - || codename.equals("a5ltezt") - || codename.equals("a5ulte") - || codename.equals("a5ultebmc") - || codename.equals("a5ultektt") - || codename.equals("a5ultelgt") - || codename.equals("a5ulteskt"))) - || (model != null && (model.equals("SM-A5000") - || model.equals("SM-A5009") - || model.equals("SM-A500F") - || model.equals("SM-A500F1") - || model.equals("SM-A500FU") - || model.equals("SM-A500G") - || model.equals("SM-A500H") - || model.equals("SM-A500K") - || model.equals("SM-A500L") - || model.equals("SM-A500M") - || model.equals("SM-A500S") - || model.equals("SM-A500W") - || model.equals("SM-A500X") - || model.equals("SM-A500XZ") - || model.equals("SM-A500Y") - || model.equals("SM-A500YZ")))) { - return "Galaxy A5"; - } - if ((codename != null && (codename.equals("vivaltods5m"))) - || (model != null && (model.equals("SM-G313HU") - || model.equals("SM-G313HY") - || model.equals("SM-G313M") - || model.equals("SM-G313MY")))) { - return "Galaxy Ace 4"; - } - if ((codename != null && (codename.equals("GT-S6352") - || codename.equals("GT-S6802") - || codename.equals("GT-S6802B") - || codename.equals("SCH-I579") - || codename.equals("SCH-I589") - || codename.equals("SCH-i579") - || codename.equals("SCH-i589"))) - || (model != null && (model.equals("GT-S6352") - || model.equals("GT-S6802") - || model.equals("GT-S6802B") - || model.equals("SCH-I589") - || model.equals("SCH-i579") - || model.equals("SCH-i589")))) { - return "Galaxy Ace Duos"; - } - if ((codename != null && (codename.equals("GT-S7500") - || codename.equals("GT-S7500L") - || codename.equals("GT-S7500T") - || codename.equals("GT-S7500W") - || codename.equals("GT-S7508"))) - || (model != null && (model.equals("GT-S7500") - || model.equals("GT-S7500L") - || model.equals("GT-S7500T") - || model.equals("GT-S7500W") - || model.equals("GT-S7508")))) { - return "Galaxy Ace Plus"; - } - if ((codename != null && (codename.equals("heat3gtfnvzw") - || codename.equals("heatnfc3g") - || codename.equals("heatqlte"))) - || (model != null && (model.equals("SM-G310HN") - || model.equals("SM-G357FZ") - || model.equals("SM-S765C") - || model.equals("SM-S766C")))) { - return "Galaxy Ace Style"; - } - if ((codename != null && (codename.equals("vivalto3g") - || codename.equals("vivalto3mve3g") - || codename.equals("vivalto5mve3g") - || codename.equals("vivaltolte") - || codename.equals("vivaltonfc3g"))) - || (model != null && (model.equals("SM-G313F") - || model.equals("SM-G313HN") - || model.equals("SM-G313ML") - || model.equals("SM-G313MU") - || model.equals("SM-G316H") - || model.equals("SM-G316HU") - || model.equals("SM-G316M") - || model.equals("SM-G316MY")))) { - return "Galaxy Ace4"; - } - if ((codename != null && (codename.equals("core33g") - || codename.equals("coreprimelte") - || codename.equals("coreprimelteaio") - || codename.equals("coreprimeltelra") - || codename.equals("coreprimeltespr") - || codename.equals("coreprimeltetfnvzw") - || codename.equals("coreprimeltevzw") - || codename.equals("coreprimeve3g") - || codename.equals("coreprimevelte") - || codename.equals("cprimeltemtr") - || codename.equals("cprimeltetmo") - || codename.equals("rossalte") - || codename.equals("rossaltectc") - || codename.equals("rossaltexsa"))) - || (model != null && (model.equals("SAMSUNG-SM-G360AZ") - || model.equals("SM-G3606") - || model.equals("SM-G3608") - || model.equals("SM-G3609") - || model.equals("SM-G360F") - || model.equals("SM-G360FY") - || model.equals("SM-G360GY") - || model.equals("SM-G360H") - || model.equals("SM-G360HU") - || model.equals("SM-G360M") - || model.equals("SM-G360P") - || model.equals("SM-G360R6") - || model.equals("SM-G360T") - || model.equals("SM-G360T1") - || model.equals("SM-G360V") - || model.equals("SM-G361F") - || model.equals("SM-G361H") - || model.equals("SM-G361HU") - || model.equals("SM-G361M") - || model.equals("SM-S820L")))) { - return "Galaxy Core Prime"; - } - if ((codename != null && (codename.equals("kanas") - || codename.equals("kanas3g") - || codename.equals("kanas3gcmcc") - || codename.equals("kanas3gctc") - || codename.equals("kanas3gnfc"))) - || (model != null && (model.equals("SM-G3556D") - || model.equals("SM-G3558") - || model.equals("SM-G3559") - || model.equals("SM-G355H") - || model.equals("SM-G355HN") - || model.equals("SM-G355HQ") - || model.equals("SM-G355M")))) { - return "Galaxy Core2"; - } - if ((codename != null && (codename.equals("e53g") - || codename.equals("e5lte") - || codename.equals("e5ltetfnvzw") - || codename.equals("e5ltetw"))) - || (model != null && (model.equals("SM-E500F") - || model.equals("SM-E500H") - || model.equals("SM-E500M") - || model.equals("SM-E500YZ") - || model.equals("SM-S978L")))) { - return "Galaxy E5"; - } - if ((codename != null && (codename.equals("e73g") - || codename.equals("e7lte") - || codename.equals("e7ltechn") - || codename.equals("e7ltectc") - || codename.equals("e7ltehktw"))) - || (model != null && (model.equals("SM-E7000") - || model.equals("SM-E7009") - || model.equals("SM-E700F") - || model.equals("SM-E700H") - || model.equals("SM-E700M")))) { - return "Galaxy E7"; - } - if ((codename != null && (codename.equals("SCH-I629") - || codename.equals("nevis") - || codename.equals("nevis3g") - || codename.equals("nevis3gcmcc") - || codename.equals("nevisds") - || codename.equals("nevisnvess") - || codename.equals("nevisp") - || codename.equals("nevisvess") - || codename.equals("nevisw"))) - || (model != null && (model.equals("GT-S6790") - || model.equals("GT-S6790E") - || model.equals("GT-S6790L") - || model.equals("GT-S6790N") - || model.equals("GT-S6810") - || model.equals("GT-S6810B") - || model.equals("GT-S6810E") - || model.equals("GT-S6810L") - || model.equals("GT-S6810M") - || model.equals("GT-S6810P") - || model.equals("GT-S6812") - || model.equals("GT-S6812B") - || model.equals("GT-S6812C") - || model.equals("GT-S6812i") - || model.equals("GT-S6818") - || model.equals("GT-S6818V") - || model.equals("SCH-I629")))) { - return "Galaxy Fame"; - } - if ((codename != null && codename.equals("grandprimelteatt")) - || (model != null && model.equals("SAMSUNG-SM-G530A"))) { - return "Galaxy Go Prime"; - } - if ((codename != null && (codename.equals("baffinlite") - || codename.equals("baffinlitedtv") - || codename.equals("baffinq3g"))) - || (model != null && (model.equals("GT-I9060") - || model.equals("GT-I9060L") - || model.equals("GT-I9063T") - || model.equals("GT-I9082C") - || model.equals("GT-I9168") - || model.equals("GT-I9168I")))) { - return "Galaxy Grand Neo"; - } - if ((codename != null && (codename.equals("fortuna3g") - || codename.equals("fortuna3gdtv") - || codename.equals("fortunalte") - || codename.equals("fortunaltectc") - || codename.equals("fortunaltezh") - || codename.equals("fortunaltezt") - || codename.equals("fortunave3g") - || codename.equals("gprimelteacg") - || codename.equals("gprimeltecan") - || codename.equals("gprimeltemtr") - || codename.equals("gprimeltespr") - || codename.equals("gprimeltetfnvzw") - || codename.equals("gprimeltetmo") - || codename.equals("gprimelteusc") - || codename.equals("grandprimelte") - || codename.equals("grandprimelteaio") - || codename.equals("grandprimeve3g") - || codename.equals("grandprimeve3gdtv") - || codename.equals("grandprimevelte") - || codename.equals("grandprimevelteltn") - || codename.equals("grandprimeveltezt"))) - || (model != null && (model.equals("SAMSUNG-SM-G530AZ") - || model.equals("SM-G5306W") - || model.equals("SM-G5308W") - || model.equals("SM-G5309W") - || model.equals("SM-G530BT") - || model.equals("SM-G530F") - || model.equals("SM-G530FZ") - || model.equals("SM-G530H") - || model.equals("SM-G530M") - || model.equals("SM-G530MU") - || model.equals("SM-G530P") - || model.equals("SM-G530R4") - || model.equals("SM-G530R7") - || model.equals("SM-G530T") - || model.equals("SM-G530T1") - || model.equals("SM-G530W") - || model.equals("SM-G530Y") - || model.equals("SM-G531BT") - || model.equals("SM-G531F") - || model.equals("SM-G531H") - || model.equals("SM-G531M") - || model.equals("SM-G531Y") - || model.equals("SM-S920L") - || model.equals("gprimelteacg")))) { - return "Galaxy Grand Prime"; - } - if ((codename != null && (codename.equals("ms013g") - || codename.equals("ms013gdtv") - || codename.equals("ms013gss") - || codename.equals("ms01lte") - || codename.equals("ms01ltektt") - || codename.equals("ms01ltelgt") - || codename.equals("ms01lteskt"))) - || (model != null && (model.equals("SM-G710") - || model.equals("SM-G7102") - || model.equals("SM-G7102T") - || model.equals("SM-G7105") - || model.equals("SM-G7105H") - || model.equals("SM-G7105L") - || model.equals("SM-G7106") - || model.equals("SM-G7108") - || model.equals("SM-G7109") - || model.equals("SM-G710K") - || model.equals("SM-G710L") - || model.equals("SM-G710S")))) { - return "Galaxy Grand2"; - } - if ((codename != null && (codename.equals("j13g") - || codename.equals("j13gtfnvzw") - || codename.equals("j1lte") - || codename.equals("j1nlte") - || codename.equals("j1qltevzw") - || codename.equals("j1xlte") - || codename.equals("j1xlteaio") - || codename.equals("j1xlteatt") - || codename.equals("j1xltecan") - || codename.equals("j1xqltespr") - || codename.equals("j1xqltetfnvzw"))) - || (model != null && (model.equals("SAMSUNG-SM-J120A") - || model.equals("SAMSUNG-SM-J120AZ") - || model.equals("SM-J100F") - || model.equals("SM-J100FN") - || model.equals("SM-J100G") - || model.equals("SM-J100H") - || model.equals("SM-J100M") - || model.equals("SM-J100ML") - || model.equals("SM-J100MU") - || model.equals("SM-J100VPP") - || model.equals("SM-J100Y") - || model.equals("SM-J120F") - || model.equals("SM-J120FN") - || model.equals("SM-J120M") - || model.equals("SM-J120P") - || model.equals("SM-J120W") - || model.equals("SM-S120VL") - || model.equals("SM-S777C")))) { - return "Galaxy J1"; - } - if ((codename != null && (codename.equals("j1acelte") - || codename.equals("j1acelteltn") - || codename.equals("j1acevelte") - || codename.equals("j1pop3g"))) - || (model != null && (model.equals("SM-J110F") - || model.equals("SM-J110G") - || model.equals("SM-J110H") - || model.equals("SM-J110L") - || model.equals("SM-J110M") - || model.equals("SM-J111F") - || model.equals("SM-J111M")))) { - return "Galaxy J1 Ace"; - } - if ((codename != null && (codename.equals("j53g") - || codename.equals("j5lte") - || codename.equals("j5ltechn") - || codename.equals("j5ltekx") - || codename.equals("j5nlte") - || codename.equals("j5ylte"))) - || (model != null && (model.equals("SM-J5007") - || model.equals("SM-J5008") - || model.equals("SM-J500F") - || model.equals("SM-J500FN") - || model.equals("SM-J500G") - || model.equals("SM-J500H") - || model.equals("SM-J500M") - || model.equals("SM-J500N0") - || model.equals("SM-J500Y")))) { - return "Galaxy J5"; - } - if ((codename != null && (codename.equals("j75ltektt") - || codename.equals("j7e3g") - || codename.equals("j7elte") - || codename.equals("j7ltechn"))) - || (model != null && (model.equals("SM-J7008") - || model.equals("SM-J700F") - || model.equals("SM-J700H") - || model.equals("SM-J700K") - || model.equals("SM-J700M")))) { - return "Galaxy J7"; - } - if ((codename != null && (codename.equals("maguro") - || codename.equals("toro") - || codename.equals("toroplus"))) - || (model != null && (model.equals("Galaxy X")))) { - return "Galaxy Nexus"; - } - if ((codename != null && (codename.equals("lt033g") - || codename.equals("lt03ltektt") - || codename.equals("lt03ltelgt") - || codename.equals("lt03lteskt") - || codename.equals("p4notelte") - || codename.equals("p4noteltektt") - || codename.equals("p4noteltelgt") - || codename.equals("p4notelteskt") - || codename.equals("p4noteltespr") - || codename.equals("p4notelteusc") - || codename.equals("p4noteltevzw") - || codename.equals("p4noterf") - || codename.equals("p4noterfktt") - || codename.equals("p4notewifi") - || codename.equals("p4notewifi43241any") - || codename.equals("p4notewifiany") - || codename.equals("p4notewifiktt") - || codename.equals("p4notewifiww"))) - || (model != null && (model.equals("GT-N8000") - || model.equals("GT-N8005") - || model.equals("GT-N8010") - || model.equals("GT-N8013") - || model.equals("GT-N8020") - || model.equals("SCH-I925") - || model.equals("SCH-I925U") - || model.equals("SHV-E230K") - || model.equals("SHV-E230L") - || model.equals("SHV-E230S") - || model.equals("SHW-M480K") - || model.equals("SHW-M480W") - || model.equals("SHW-M485W") - || model.equals("SHW-M486W") - || model.equals("SM-P601") - || model.equals("SM-P602") - || model.equals("SM-P605K") - || model.equals("SM-P605L") - || model.equals("SM-P605S") - || model.equals("SPH-P600")))) { - return "Galaxy Note 10.1"; - } - if ((codename != null && (codename.equals("SC-01G") - || codename.equals("SCL24") - || codename.equals("tbeltektt") - || codename.equals("tbeltelgt") - || codename.equals("tbelteskt") - || codename.equals("tblte") - || codename.equals("tblteatt") - || codename.equals("tbltecan") - || codename.equals("tbltechn") - || codename.equals("tbltespr") - || codename.equals("tbltetmo") - || codename.equals("tblteusc") - || codename.equals("tbltevzw"))) - || (model != null && (model.equals("SAMSUNG-SM-N915A") - || model.equals("SC-01G") - || model.equals("SCL24") - || model.equals("SM-N9150") - || model.equals("SM-N915F") - || model.equals("SM-N915FY") - || model.equals("SM-N915G") - || model.equals("SM-N915K") - || model.equals("SM-N915L") - || model.equals("SM-N915P") - || model.equals("SM-N915R4") - || model.equals("SM-N915S") - || model.equals("SM-N915T") - || model.equals("SM-N915T3") - || model.equals("SM-N915V") - || model.equals("SM-N915W8") - || model.equals("SM-N915X")))) { - return "Galaxy Note Edge"; - } - if ((codename != null && (codename.equals("v1a3g") - || codename.equals("v1awifi") - || codename.equals("v1awifikx") - || codename.equals("viennalte") - || codename.equals("viennalteatt") - || codename.equals("viennaltekx") - || codename.equals("viennaltevzw"))) - || (model != null && (model.equals("SAMSUNG-SM-P907A") - || model.equals("SM-P900") - || model.equals("SM-P901") - || model.equals("SM-P905") - || model.equals("SM-P905F0") - || model.equals("SM-P905M") - || model.equals("SM-P905V")))) { - return "Galaxy Note Pro 12.2"; - } - if ((codename != null && (codename.equals("tre3caltektt") - || codename.equals("tre3caltelgt") - || codename.equals("tre3calteskt") - || codename.equals("tre3g") - || codename.equals("trelte") - || codename.equals("treltektt") - || codename.equals("treltelgt") - || codename.equals("trelteskt") - || codename.equals("trhplte") - || codename.equals("trlte") - || codename.equals("trlteatt") - || codename.equals("trltecan") - || codename.equals("trltechn") - || codename.equals("trltechnzh") - || codename.equals("trltespr") - || codename.equals("trltetmo") - || codename.equals("trlteusc") - || codename.equals("trltevzw"))) - || (model != null && (model.equals("SAMSUNG-SM-N910A") - || model.equals("SM-N9100") - || model.equals("SM-N9106W") - || model.equals("SM-N9108V") - || model.equals("SM-N9109W") - || model.equals("SM-N910C") - || model.equals("SM-N910F") - || model.equals("SM-N910G") - || model.equals("SM-N910H") - || model.equals("SM-N910K") - || model.equals("SM-N910L") - || model.equals("SM-N910P") - || model.equals("SM-N910R4") - || model.equals("SM-N910S") - || model.equals("SM-N910T") - || model.equals("SM-N910T2") - || model.equals("SM-N910T3") - || model.equals("SM-N910U") - || model.equals("SM-N910V") - || model.equals("SM-N910W8") - || model.equals("SM-N910X") - || model.equals("SM-N916K") - || model.equals("SM-N916L") - || model.equals("SM-N916S")))) { - return "Galaxy Note4"; - } - if ((codename != null && (codename.equals("noblelte") - || codename.equals("noblelteacg") - || codename.equals("noblelteatt") - || codename.equals("nobleltebmc") - || codename.equals("nobleltechn") - || codename.equals("nobleltecmcc") - || codename.equals("nobleltehk") - || codename.equals("nobleltektt") - || codename.equals("nobleltelgt") - || codename.equals("nobleltelra") - || codename.equals("noblelteskt") - || codename.equals("nobleltespr") - || codename.equals("nobleltetmo") - || codename.equals("noblelteusc") - || codename.equals("nobleltevzw"))) - || (model != null && (model.equals("SAMSUNG-SM-N920A") - || model.equals("SM-N9200") - || model.equals("SM-N9208") - || model.equals("SM-N920C") - || model.equals("SM-N920F") - || model.equals("SM-N920G") - || model.equals("SM-N920I") - || model.equals("SM-N920K") - || model.equals("SM-N920L") - || model.equals("SM-N920P") - || model.equals("SM-N920R4") - || model.equals("SM-N920R6") - || model.equals("SM-N920R7") - || model.equals("SM-N920S") - || model.equals("SM-N920T") - || model.equals("SM-N920V") - || model.equals("SM-N920W8") - || model.equals("SM-N920X")))) { - return "Galaxy Note5"; - } - if ((codename != null && (codename.equals("SC-01J") - || codename.equals("SCV34") - || codename.equals("gracelte") - || codename.equals("graceltektt") - || codename.equals("graceltelgt") - || codename.equals("gracelteskt") - || codename.equals("graceqlteacg") - || codename.equals("graceqlteatt") - || codename.equals("graceqltebmc") - || codename.equals("graceqltechn") - || codename.equals("graceqltedcm") - || codename.equals("graceqltelra") - || codename.equals("graceqltespr") - || codename.equals("graceqltetfnvzw") - || codename.equals("graceqltetmo") - || codename.equals("graceqlteue") - || codename.equals("graceqlteusc") - || codename.equals("graceqltevzw"))) - || (model != null && (model.equals("SAMSUNG-SM-N930A") - || model.equals("SC-01J") - || model.equals("SCV34") - || model.equals("SGH-N037") - || model.equals("SM-N9300") - || model.equals("SM-N930F") - || model.equals("SM-N930K") - || model.equals("SM-N930L") - || model.equals("SM-N930P") - || model.equals("SM-N930R4") - || model.equals("SM-N930R6") - || model.equals("SM-N930R7") - || model.equals("SM-N930S") - || model.equals("SM-N930T") - || model.equals("SM-N930U") - || model.equals("SM-N930V") - || model.equals("SM-N930VL") - || model.equals("SM-N930W8") - || model.equals("SM-N930X")))) { - return "Galaxy Note7"; - } - if ((codename != null && (codename.equals("SC-01K") - || codename.equals("SCV37") - || codename.equals("greatlte") - || codename.equals("greatlteks") - || codename.equals("greatqlte") - || codename.equals("greatqltechn") - || codename.equals("greatqltecmcc") - || codename.equals("greatqltecs") - || codename.equals("greatqlteue"))) - || (model != null && (model.equals("SC-01K") - || model.equals("SCV37") - || model.equals("SM-N9500") - || model.equals("SM-N9508") - || model.equals("SM-N950F") - || model.equals("SM-N950N") - || model.equals("SM-N950U") - || model.equals("SM-N950U1") - || model.equals("SM-N950W") - || model.equals("SM-N950XN")))) { - return "Galaxy Note8"; - } - if ((codename != null && (codename.equals("o5lte") - || codename.equals("o5ltechn") - || codename.equals("o5prolte") - || codename.equals("on5ltemtr") - || codename.equals("on5ltetfntmo") - || codename.equals("on5ltetmo"))) - || (model != null && (model.equals("SM-G5500") - || model.equals("SM-G550FY") - || model.equals("SM-G550T") - || model.equals("SM-G550T1") - || model.equals("SM-G550T2") - || model.equals("SM-S550TL")))) { - return "Galaxy On5"; - } - if ((codename != null && (codename.equals("o7lte") - || codename.equals("o7ltechn") - || codename.equals("on7elte"))) - || (model != null && (model.equals("SM-G6000") - || model.equals("SM-G600F") - || model.equals("SM-G600FY")))) { - return "Galaxy On7"; - } - if ((codename != null && (codename.equals("GT-I9000") - || codename.equals("GT-I9000B") - || codename.equals("GT-I9000M") - || codename.equals("GT-I9000T") - || codename.equals("GT-I9003") - || codename.equals("GT-I9003L") - || codename.equals("GT-I9008L") - || codename.equals("GT-I9010") - || codename.equals("GT-I9018") - || codename.equals("GT-I9050") - || codename.equals("SC-02B") - || codename.equals("SCH-I500") - || codename.equals("SCH-S950C") - || codename.equals("SCH-i909") - || codename.equals("SGH-I897") - || codename.equals("SGH-T959V") - || codename.equals("SGH-T959W") - || codename.equals("SHW-M110S") - || codename.equals("SHW-M190S") - || codename.equals("SPH-D700") - || codename.equals("loganlte"))) - || (model != null && (model.equals("GT-I9000") - || model.equals("GT-I9000B") - || model.equals("GT-I9000M") - || model.equals("GT-I9000T") - || model.equals("GT-I9003") - || model.equals("GT-I9003L") - || model.equals("GT-I9008L") - || model.equals("GT-I9010") - || model.equals("GT-I9018") - || model.equals("GT-I9050") - || model.equals("GT-S7275") - || model.equals("SAMSUNG-SGH-I897") - || model.equals("SC-02B") - || model.equals("SCH-I500") - || model.equals("SCH-S950C") - || model.equals("SCH-i909") - || model.equals("SGH-T959V") - || model.equals("SGH-T959W") - || model.equals("SHW-M110S") - || model.equals("SHW-M190S") - || model.equals("SPH-D700")))) { - return "Galaxy S"; - } - if ((codename != null && (codename.equals("kylechn") - || codename.equals("kyleopen") - || codename.equals("kyletdcmcc"))) - || (model != null && (model.equals("GT-S7562") - || model.equals("GT-S7568")))) { - return "Galaxy S Duos"; - } - if ((codename != null && (codename.equals("kyleprods"))) - || (model != null && (model.equals("GT-S7582") - || model.equals("GT-S7582L")))) { - return "Galaxy S Duos2"; - } - if ((codename != null && codename.equals("vivalto3gvn")) - || (model != null && model.equals("SM-G313HZ"))) { - return "Galaxy S Duos3"; - } - if ((codename != null && (codename.equals("SC-03E") - || codename.equals("c1att") - || codename.equals("c1ktt") - || codename.equals("c1lgt") - || codename.equals("c1skt") - || codename.equals("d2att") - || codename.equals("d2can") - || codename.equals("d2cri") - || codename.equals("d2dcm") - || codename.equals("d2lteMetroPCS") - || codename.equals("d2lterefreshspr") - || codename.equals("d2ltetmo") - || codename.equals("d2mtr") - || codename.equals("d2spi") - || codename.equals("d2spr") - || codename.equals("d2tfnspr") - || codename.equals("d2tfnvzw") - || codename.equals("d2tmo") - || codename.equals("d2usc") - || codename.equals("d2vmu") - || codename.equals("d2vzw") - || codename.equals("d2xar") - || codename.equals("m0") - || codename.equals("m0apt") - || codename.equals("m0chn") - || codename.equals("m0cmcc") - || codename.equals("m0ctc") - || codename.equals("m0ctcduos") - || codename.equals("m0skt") - || codename.equals("m3") - || codename.equals("m3dcm"))) - || (model != null && (model.equals("GT-I9300") - || model.equals("GT-I9300T") - || model.equals("GT-I9305") - || model.equals("GT-I9305N") - || model.equals("GT-I9305T") - || model.equals("GT-I9308") - || model.equals("Gravity") - || model.equals("GravityQuad") - || model.equals("SAMSUNG-SGH-I747") - || model.equals("SC-03E") - || model.equals("SC-06D") - || model.equals("SCH-I535") - || model.equals("SCH-I535PP") - || model.equals("SCH-I939") - || model.equals("SCH-I939D") - || model.equals("SCH-L710") - || model.equals("SCH-R530C") - || model.equals("SCH-R530M") - || model.equals("SCH-R530U") - || model.equals("SCH-R530X") - || model.equals("SCH-S960L") - || model.equals("SCH-S968C") - || model.equals("SGH-I747M") - || model.equals("SGH-I748") - || model.equals("SGH-T999") - || model.equals("SGH-T999L") - || model.equals("SGH-T999N") - || model.equals("SGH-T999V") - || model.equals("SHV-E210K") - || model.equals("SHV-E210L") - || model.equals("SHV-E210S") - || model.equals("SHW-M440S") - || model.equals("SPH-L710") - || model.equals("SPH-L710T")))) { - return "Galaxy S3"; - } - if ((codename != null && (codename.equals("golden") - || codename.equals("goldenlteatt") - || codename.equals("goldenltebmc") - || codename.equals("goldenltevzw") - || codename.equals("goldenve3g"))) - || (model != null && (model.equals("GT-I8190") - || model.equals("GT-I8190L") - || model.equals("GT-I8190N") - || model.equals("GT-I8190T") - || model.equals("GT-I8200L") - || model.equals("SAMSUNG-SM-G730A") - || model.equals("SM-G730V") - || model.equals("SM-G730W8")))) { - return "Galaxy S3 Mini"; - } - if ((codename != null && (codename.equals("goldenve3g") - || codename.equals("goldenvess3g"))) - || (model != null && (model.equals("GT-I8200") - || model.equals("GT-I8200N") - || model.equals("GT-I8200Q")))) { - return "Galaxy S3 Mini Value Edition"; - } - if ((codename != null && (codename.equals("s3ve3g") - || codename.equals("s3ve3gdd") - || codename.equals("s3ve3gds") - || codename.equals("s3ve3gdsdd"))) - || (model != null && (model.equals("GT-I9300I") - || model.equals("GT-I9301I") - || model.equals("GT-I9301Q")))) { - return "Galaxy S3 Neo"; - } - if ((codename != null && (codename.equals("SC-04E") - || codename.equals("ja3g") - || codename.equals("ja3gduosctc") - || codename.equals("jaltektt") - || codename.equals("jaltelgt") - || codename.equals("jalteskt") - || codename.equals("jflte") - || codename.equals("jflteMetroPCS") - || codename.equals("jflteaio") - || codename.equals("jflteatt") - || codename.equals("jfltecan") - || codename.equals("jfltecri") - || codename.equals("jfltecsp") - || codename.equals("jfltelra") - || codename.equals("jflterefreshspr") - || codename.equals("jfltespr") - || codename.equals("jfltetfnatt") - || codename.equals("jfltetfntmo") - || codename.equals("jfltetmo") - || codename.equals("jflteusc") - || codename.equals("jfltevzw") - || codename.equals("jfltevzwpp") - || codename.equals("jftdd") - || codename.equals("jfvelte") - || codename.equals("jfwifi") - || codename.equals("jsglte") - || codename.equals("ks01lte") - || codename.equals("ks01ltektt") - || codename.equals("ks01ltelgt"))) - || (model != null && (model.equals("GT-I9500") - || model.equals("GT-I9505") - || model.equals("GT-I9505X") - || model.equals("GT-I9506") - || model.equals("GT-I9507") - || model.equals("GT-I9507V") - || model.equals("GT-I9508") - || model.equals("GT-I9508C") - || model.equals("GT-I9508V") - || model.equals("GT-I9515") - || model.equals("GT-I9515L") - || model.equals("SAMSUNG-SGH-I337") - || model.equals("SAMSUNG-SGH-I337Z") - || model.equals("SC-04E") - || model.equals("SCH-I545") - || model.equals("SCH-I545L") - || model.equals("SCH-I545PP") - || model.equals("SCH-I959") - || model.equals("SCH-R970") - || model.equals("SCH-R970C") - || model.equals("SCH-R970X") - || model.equals("SGH-I337M") - || model.equals("SGH-M919") - || model.equals("SGH-M919N") - || model.equals("SGH-M919V") - || model.equals("SGH-S970G") - || model.equals("SHV-E300K") - || model.equals("SHV-E300L") - || model.equals("SHV-E300S") - || model.equals("SHV-E330K") - || model.equals("SHV-E330L") - || model.equals("SM-S975L") - || model.equals("SPH-L720") - || model.equals("SPH-L720T")))) { - return "Galaxy S4"; - } - if ((codename != null && (codename.equals("serrano3g") - || codename.equals("serranods") - || codename.equals("serranolte") - || codename.equals("serranoltebmc") - || codename.equals("serranoltektt") - || codename.equals("serranoltekx") - || codename.equals("serranoltelra") - || codename.equals("serranoltespr") - || codename.equals("serranolteusc") - || codename.equals("serranoltevzw") - || codename.equals("serranove3g") - || codename.equals("serranovelte") - || codename.equals("serranovolteatt"))) - || (model != null && (model.equals("GT-I9190") - || model.equals("GT-I9192") - || model.equals("GT-I9192I") - || model.equals("GT-I9195") - || model.equals("GT-I9195I") - || model.equals("GT-I9195L") - || model.equals("GT-I9195T") - || model.equals("GT-I9195X") - || model.equals("GT-I9197") - || model.equals("SAMSUNG-SGH-I257") - || model.equals("SCH-I435") - || model.equals("SCH-I435L") - || model.equals("SCH-R890") - || model.equals("SGH-I257M") - || model.equals("SHV-E370D") - || model.equals("SHV-E370K") - || model.equals("SPH-L520")))) { - return "Galaxy S4 Mini"; - } - if ((codename != null && (codename.equals("SC-04F") - || codename.equals("SCL23") - || codename.equals("k3g") - || codename.equals("klte") - || codename.equals("klteMetroPCS") - || codename.equals("klteacg") - || codename.equals("klteaio") - || codename.equals("klteatt") - || codename.equals("kltecan") - || codename.equals("klteduoszn") - || codename.equals("kltektt") - || codename.equals("kltelgt") - || codename.equals("kltelra") - || codename.equals("klteskt") - || codename.equals("kltespr") - || codename.equals("kltetfnvzw") - || codename.equals("kltetmo") - || codename.equals("klteusc") - || codename.equals("kltevzw") - || codename.equals("kwifi") - || codename.equals("lentisltektt") - || codename.equals("lentisltelgt") - || codename.equals("lentislteskt"))) - || (model != null && (model.equals("SAMSUNG-SM-G900A") - || model.equals("SAMSUNG-SM-G900AZ") - || model.equals("SC-04F") - || model.equals("SCL23") - || model.equals("SM-G9006W") - || model.equals("SM-G9008W") - || model.equals("SM-G9009W") - || model.equals("SM-G900F") - || model.equals("SM-G900FQ") - || model.equals("SM-G900H") - || model.equals("SM-G900I") - || model.equals("SM-G900K") - || model.equals("SM-G900L") - || model.equals("SM-G900M") - || model.equals("SM-G900MD") - || model.equals("SM-G900P") - || model.equals("SM-G900R4") - || model.equals("SM-G900R6") - || model.equals("SM-G900R7") - || model.equals("SM-G900S") - || model.equals("SM-G900T") - || model.equals("SM-G900T1") - || model.equals("SM-G900T3") - || model.equals("SM-G900T4") - || model.equals("SM-G900V") - || model.equals("SM-G900W8") - || model.equals("SM-G900X") - || model.equals("SM-G906K") - || model.equals("SM-G906L") - || model.equals("SM-G906S") - || model.equals("SM-S903VL")))) { - return "Galaxy S5"; - } - if ((codename != null && (codename.equals("s5neolte") - || codename.equals("s5neoltecan"))) - || (model != null && (model.equals("SM-G903F") - || model.equals("SM-G903M") - || model.equals("SM-G903W")))) { - return "Galaxy S5 Neo"; - } - if ((codename != null && (codename.equals("SC-05G") - || codename.equals("zeroflte") - || codename.equals("zeroflteacg") - || codename.equals("zeroflteaio") - || codename.equals("zeroflteatt") - || codename.equals("zerofltebmc") - || codename.equals("zerofltechn") - || codename.equals("zerofltectc") - || codename.equals("zerofltektt") - || codename.equals("zerofltelgt") - || codename.equals("zerofltelra") - || codename.equals("zerofltemtr") - || codename.equals("zeroflteskt") - || codename.equals("zerofltespr") - || codename.equals("zerofltetfnvzw") - || codename.equals("zerofltetmo") - || codename.equals("zeroflteusc") - || codename.equals("zerofltevzw"))) - || (model != null && (model.equals("SAMSUNG-SM-G920A") - || model.equals("SAMSUNG-SM-G920AZ") - || model.equals("SC-05G") - || model.equals("SM-G9200") - || model.equals("SM-G9208") - || model.equals("SM-G9209") - || model.equals("SM-G920F") - || model.equals("SM-G920I") - || model.equals("SM-G920K") - || model.equals("SM-G920L") - || model.equals("SM-G920P") - || model.equals("SM-G920R4") - || model.equals("SM-G920R6") - || model.equals("SM-G920R7") - || model.equals("SM-G920S") - || model.equals("SM-G920T") - || model.equals("SM-G920T1") - || model.equals("SM-G920V") - || model.equals("SM-G920W8") - || model.equals("SM-G920X") - || model.equals("SM-S906L") - || model.equals("SM-S907VL")))) { - return "Galaxy S6"; - } - if ((codename != null && (codename.equals("404SC") - || codename.equals("SC-04G") - || codename.equals("SCV31") - || codename.equals("zerolte") - || codename.equals("zerolteacg") - || codename.equals("zerolteatt") - || codename.equals("zeroltebmc") - || codename.equals("zeroltechn") - || codename.equals("zeroltektt") - || codename.equals("zeroltelgt") - || codename.equals("zeroltelra") - || codename.equals("zerolteskt") - || codename.equals("zeroltespr") - || codename.equals("zeroltetmo") - || codename.equals("zerolteusc") - || codename.equals("zeroltevzw"))) - || (model != null && (model.equals("404SC") - || model.equals("SAMSUNG-SM-G925A") - || model.equals("SC-04G") - || model.equals("SCV31") - || model.equals("SM-G9250") - || model.equals("SM-G925F") - || model.equals("SM-G925I") - || model.equals("SM-G925K") - || model.equals("SM-G925L") - || model.equals("SM-G925P") - || model.equals("SM-G925R4") - || model.equals("SM-G925R6") - || model.equals("SM-G925R7") - || model.equals("SM-G925S") - || model.equals("SM-G925T") - || model.equals("SM-G925V") - || model.equals("SM-G925W8") - || model.equals("SM-G925X")))) { - return "Galaxy S6 Edge"; - } - if ((codename != null && (codename.equals("zenlte") - || codename.equals("zenlteatt") - || codename.equals("zenltebmc") - || codename.equals("zenltechn") - || codename.equals("zenltektt") - || codename.equals("zenltekx") - || codename.equals("zenltelgt") - || codename.equals("zenlteskt") - || codename.equals("zenltespr") - || codename.equals("zenltetmo") - || codename.equals("zenlteusc") - || codename.equals("zenltevzw"))) - || (model != null && (model.equals("SAMSUNG-SM-G928A") - || model.equals("SM-G9280") - || model.equals("SM-G9287") - || model.equals("SM-G9287C") - || model.equals("SM-G928C") - || model.equals("SM-G928F") - || model.equals("SM-G928G") - || model.equals("SM-G928I") - || model.equals("SM-G928K") - || model.equals("SM-G928L") - || model.equals("SM-G928N0") - || model.equals("SM-G928P") - || model.equals("SM-G928R4") - || model.equals("SM-G928S") - || model.equals("SM-G928T") - || model.equals("SM-G928V") - || model.equals("SM-G928W8") - || model.equals("SM-G928X")))) { - return "Galaxy S6 Edge+"; - } - if ((codename != null && (codename.equals("herolte") - || codename.equals("heroltebmc") - || codename.equals("heroltektt") - || codename.equals("heroltelgt") - || codename.equals("herolteskt") - || codename.equals("heroqlteacg") - || codename.equals("heroqlteaio") - || codename.equals("heroqlteatt") - || codename.equals("heroqltecctvzw") - || codename.equals("heroqltechn") - || codename.equals("heroqltelra") - || codename.equals("heroqltemtr") - || codename.equals("heroqltespr") - || codename.equals("heroqltetfnvzw") - || codename.equals("heroqltetmo") - || codename.equals("heroqlteue") - || codename.equals("heroqlteusc") - || codename.equals("heroqltevzw"))) - || (model != null && (model.equals("SAMSUNG-SM-G930A") - || model.equals("SAMSUNG-SM-G930AZ") - || model.equals("SM-G9300") - || model.equals("SM-G9308") - || model.equals("SM-G930F") - || model.equals("SM-G930K") - || model.equals("SM-G930L") - || model.equals("SM-G930P") - || model.equals("SM-G930R4") - || model.equals("SM-G930R6") - || model.equals("SM-G930R7") - || model.equals("SM-G930S") - || model.equals("SM-G930T") - || model.equals("SM-G930T1") - || model.equals("SM-G930U") - || model.equals("SM-G930V") - || model.equals("SM-G930VC") - || model.equals("SM-G930VL") - || model.equals("SM-G930W8") - || model.equals("SM-G930X")))) { - return "Galaxy S7"; - } - if ((codename != null && (codename.equals("SC-02H") - || codename.equals("SCV33") - || codename.equals("hero2lte") - || codename.equals("hero2ltebmc") - || codename.equals("hero2ltektt") - || codename.equals("hero2ltelgt") - || codename.equals("hero2lteskt") - || codename.equals("hero2qlteatt") - || codename.equals("hero2qltecctvzw") - || codename.equals("hero2qltechn") - || codename.equals("hero2qltespr") - || codename.equals("hero2qltetmo") - || codename.equals("hero2qlteue") - || codename.equals("hero2qlteusc") - || codename.equals("hero2qltevzw"))) - || (model != null && (model.equals("SAMSUNG-SM-G935A") - || model.equals("SC-02H") - || model.equals("SCV33") - || model.equals("SM-G9350") - || model.equals("SM-G935F") - || model.equals("SM-G935K") - || model.equals("SM-G935L") - || model.equals("SM-G935P") - || model.equals("SM-G935R4") - || model.equals("SM-G935S") - || model.equals("SM-G935T") - || model.equals("SM-G935U") - || model.equals("SM-G935V") - || model.equals("SM-G935VC") - || model.equals("SM-G935W8") - || model.equals("SM-G935X")))) { - return "Galaxy S7 Edge"; - } - if ((codename != null && (codename.equals("SC-02J") - || codename.equals("SCV36") - || codename.equals("dreamlte") - || codename.equals("dreamlteks") - || codename.equals("dreamqltecan") - || codename.equals("dreamqltechn") - || codename.equals("dreamqltecmcc") - || codename.equals("dreamqltesq") - || codename.equals("dreamqlteue"))) - || (model != null && (model.equals("SC-02J") - || model.equals("SCV36") - || model.equals("SM-G9500") - || model.equals("SM-G9508") - || model.equals("SM-G950F") - || model.equals("SM-G950N") - || model.equals("SM-G950U") - || model.equals("SM-G950U1") - || model.equals("SM-G950W")))) { - return "Galaxy S8"; - } - if ((codename != null && (codename.equals("SC-03J") - || codename.equals("SCV35") - || codename.equals("dream2lte") - || codename.equals("dream2lteks") - || codename.equals("dream2qltecan") - || codename.equals("dream2qltechn") - || codename.equals("dream2qltesq") - || codename.equals("dream2qlteue"))) - || (model != null && (model.equals("SC-03J") - || model.equals("SCV35") - || model.equals("SM-G9550") - || model.equals("SM-G955F") - || model.equals("SM-G955N") - || model.equals("SM-G955U") - || model.equals("SM-G955U1") - || model.equals("SM-G955W")))) { - return "Galaxy S8+"; - } - if ((codename != null && (codename.equals("starlte") - || codename.equals("starlteks") - || codename.equals("starqltechn") - || codename.equals("starqltecmcc") - || codename.equals("starqltecs") - || codename.equals("starqltesq") - || codename.equals("starqlteue"))) - || (model != null && (model.equals("SM-G9600") - || model.equals("SM-G9608") - || model.equals("SM-G960F") - || model.equals("SM-G960N") - || model.equals("SM-G960U") - || model.equals("SM-G960U1") - || model.equals("SM-G960W")))) { - return "Galaxy S9"; - } - if ((codename != null && (codename.equals("star2lte") - || codename.equals("star2lteks") - || codename.equals("star2qltechn") - || codename.equals("star2qltecs") - || codename.equals("star2qltesq") - || codename.equals("star2qlteue"))) - || (model != null && (model.equals("SM-G9650") - || model.equals("SM-G965F") - || model.equals("SM-G965N") - || model.equals("SM-G965U") - || model.equals("SM-G965U1") - || model.equals("SM-G965W")))) { - return "Galaxy S9+"; - } - if ((codename != null && (codename.equals("GT-P7500") - || codename.equals("GT-P7500D") - || codename.equals("GT-P7503") - || codename.equals("GT-P7510") - || codename.equals("SC-01D") - || codename.equals("SCH-I905") - || codename.equals("SGH-T859") - || codename.equals("SHW-M300W") - || codename.equals("SHW-M380K") - || codename.equals("SHW-M380S") - || codename.equals("SHW-M380W"))) - || (model != null && (model.equals("GT-P7500") - || model.equals("GT-P7500D") - || model.equals("GT-P7503") - || model.equals("GT-P7510") - || model.equals("SC-01D") - || model.equals("SCH-I905") - || model.equals("SGH-T859") - || model.equals("SHW-M300W") - || model.equals("SHW-M380K") - || model.equals("SHW-M380S") - || model.equals("SHW-M380W")))) { - return "Galaxy Tab 10.1"; - } - if ((codename != null && (codename.equals("GT-P6200") - || codename.equals("GT-P6200L") - || codename.equals("GT-P6201") - || codename.equals("GT-P6210") - || codename.equals("GT-P6211") - || codename.equals("SC-02D") - || codename.equals("SGH-T869") - || codename.equals("SHW-M430W"))) - || (model != null && (model.equals("GT-P6200") - || model.equals("GT-P6200L") - || model.equals("GT-P6201") - || model.equals("GT-P6210") - || model.equals("GT-P6211") - || model.equals("SC-02D") - || model.equals("SGH-T869") - || model.equals("SHW-M430W")))) { - return "Galaxy Tab 7.0 Plus"; - } - if ((codename != null && (codename.equals("gteslteatt") - || codename.equals("gtesltebmc") - || codename.equals("gtesltelgt") - || codename.equals("gteslteskt") - || codename.equals("gtesltetmo") - || codename.equals("gtesltetw") - || codename.equals("gtesltevzw") - || codename.equals("gtesqltespr") - || codename.equals("gtesqlteusc"))) - || (model != null && (model.equals("SAMSUNG-SM-T377A") - || model.equals("SM-T375L") - || model.equals("SM-T375S") - || model.equals("SM-T3777") - || model.equals("SM-T377P") - || model.equals("SM-T377R4") - || model.equals("SM-T377T") - || model.equals("SM-T377V") - || model.equals("SM-T377W")))) { - return "Galaxy Tab E 8.0"; - } - if ((codename != null && (codename.equals("gtel3g") - || codename.equals("gtelltevzw") - || codename.equals("gtelwifi") - || codename.equals("gtelwifichn") - || codename.equals("gtelwifiue"))) - || (model != null && (model.equals("SM-T560") - || model.equals("SM-T560NU") - || model.equals("SM-T561") - || model.equals("SM-T561M") - || model.equals("SM-T561Y") - || model.equals("SM-T562") - || model.equals("SM-T567V")))) { - return "Galaxy Tab E 9.6"; - } - if ((codename != null && (codename.equals("403SC") - || codename.equals("degas2wifi") - || codename.equals("degas2wifibmwchn") - || codename.equals("degas3g") - || codename.equals("degaslte") - || codename.equals("degasltespr") - || codename.equals("degasltevzw") - || codename.equals("degasvelte") - || codename.equals("degasveltechn") - || codename.equals("degaswifi") - || codename.equals("degaswifibmwzc") - || codename.equals("degaswifidtv") - || codename.equals("degaswifiopenbnn") - || codename.equals("degaswifiue"))) - || (model != null && (model.equals("403SC") - || model.equals("SM-T230") - || model.equals("SM-T230NT") - || model.equals("SM-T230NU") - || model.equals("SM-T230NW") - || model.equals("SM-T230NY") - || model.equals("SM-T230X") - || model.equals("SM-T231") - || model.equals("SM-T232") - || model.equals("SM-T235") - || model.equals("SM-T235Y") - || model.equals("SM-T237P") - || model.equals("SM-T237V") - || model.equals("SM-T239") - || model.equals("SM-T2397") - || model.equals("SM-T239C") - || model.equals("SM-T239M")))) { - return "Galaxy Tab4 7.0"; - } - if ((codename != null && (codename.equals("gvlte") - || codename.equals("gvlteatt") - || codename.equals("gvltevzw") - || codename.equals("gvltexsp") - || codename.equals("gvwifijpn") - || codename.equals("gvwifiue"))) - || (model != null && (model.equals("SAMSUNG-SM-T677A") - || model.equals("SM-T670") - || model.equals("SM-T677") - || model.equals("SM-T677V")))) { - return "Galaxy View"; - } - if ((codename != null && codename.equals("manta"))) { - return "Nexus 10"; - } - // ---------------------------------------------------------------------------- - // Sony - if ((codename != null && (codename.equals("D2104") - || codename.equals("D2105"))) - || (model != null && (model.equals("D2104") - || model.equals("D2105")))) { - return "Xperia E1 dual"; - } - if ((codename != null && (codename.equals("D2202") - || codename.equals("D2203") - || codename.equals("D2206") - || codename.equals("D2243"))) - || (model != null && (model.equals("D2202") - || model.equals("D2203") - || model.equals("D2206") - || model.equals("D2243")))) { - return "Xperia E3"; - } - if ((codename != null && (codename.equals("E5603") - || codename.equals("E5606") - || codename.equals("E5653"))) - || (model != null && (model.equals("E5603") - || model.equals("E5606") - || model.equals("E5653")))) { - return "Xperia M5"; - } - if ((codename != null && (codename.equals("E5633") - || codename.equals("E5643") - || codename.equals("E5663"))) - || (model != null && (model.equals("E5633") - || model.equals("E5643") - || model.equals("E5663")))) { - return "Xperia M5 Dual"; - } - if ((codename != null && codename.equals("LT26i")) - || (model != null && model.equals("LT26i"))) { - return "Xperia S"; - } - if ((codename != null && (codename.equals("D5303") - || codename.equals("D5306") - || codename.equals("D5316") - || codename.equals("D5316N") - || codename.equals("D5322"))) - || (model != null && (model.equals("D5303") - || model.equals("D5306") - || model.equals("D5316") - || model.equals("D5316N") - || model.equals("D5322")))) { - return "Xperia T2 Ultra"; - } - if ((codename != null && (codename.equals("txs03"))) - || (model != null && (model.equals("SGPT12") - || model.equals("SGPT13")))) { - return "Xperia Tablet S"; - } - if ((codename != null && (codename.equals("SGP311") - || codename.equals("SGP312") - || codename.equals("SGP321") - || codename.equals("SGP351"))) - || (model != null && (model.equals("SGP311") - || model.equals("SGP312") - || model.equals("SGP321") - || model.equals("SGP351")))) { - return "Xperia Tablet Z"; - } - if ((codename != null && (codename.equals("D6502") - || codename.equals("D6503") - || codename.equals("D6543") - || codename.equals("SO-03F"))) - || (model != null && (model.equals("D6502") - || model.equals("D6503") - || model.equals("D6543") - || model.equals("SO-03F")))) { - return "Xperia Z2"; - } - if ((codename != null && (codename.equals("401SO") - || codename.equals("D6603") - || codename.equals("D6616") - || codename.equals("D6643") - || codename.equals("D6646") - || codename.equals("D6653") - || codename.equals("SO-01G") - || codename.equals("SOL26") - || codename.equals("leo"))) - || (model != null && (model.equals("401SO") - || model.equals("D6603") - || model.equals("D6616") - || model.equals("D6643") - || model.equals("D6646") - || model.equals("D6653") - || model.equals("SO-01G") - || model.equals("SOL26")))) { - return "Xperia Z3"; - } - if ((codename != null && (codename.equals("402SO") - || codename.equals("SO-03G") - || codename.equals("SOV31"))) - || (model != null && (model.equals("402SO") - || model.equals("SO-03G") - || model.equals("SOV31")))) { - return "Xperia Z4"; - } - if ((codename != null && (codename.equals("E5803") - || codename.equals("E5823") - || codename.equals("SO-02H"))) - || (model != null && (model.equals("E5803") - || model.equals("E5823") - || model.equals("SO-02H")))) { - return "Xperia Z5 Compact"; - } - // ---------------------------------------------------------------------------- - // Sony Ericsson - if ((codename != null && (codename.equals("LT26i") - || codename.equals("SO-02D"))) - || (model != null && (model.equals("LT26i") - || model.equals("SO-02D")))) { - return "Xperia S"; - } - if ((codename != null && (codename.equals("SGP311") - || codename.equals("SGP321") - || codename.equals("SGP341") - || codename.equals("SO-03E"))) - || (model != null && (model.equals("SGP311") - || model.equals("SGP321") - || model.equals("SGP341") - || model.equals("SO-03E")))) { - return "Xperia Tablet Z"; - } - return fallback; - } - - /** - * Get the {@link DeviceInfo} for the current device. Do not run on the UI thread, as this may - * download JSON to retrieve the {@link DeviceInfo}. JSON is only downloaded once and then - * stored to {@link SharedPreferences}. - * - * @param context - * the application context. - * @return {@link DeviceInfo} for the current device. - */ - @WorkerThread - public static DeviceInfo getDeviceInfo(Context context) { - return getDeviceInfo(context.getApplicationContext(), Build.DEVICE, Build.MODEL); - } - - /** - * Get the {@link DeviceInfo} for the current device. Do not run on the UI thread, as this may - * download JSON to retrieve the {@link DeviceInfo}. JSON is only downloaded once and then - * stored to {@link SharedPreferences}. - * - * @param context - * the application context. - * @param codename - * the codename of the device - * @return {@link DeviceInfo} for the current device. - */ - @WorkerThread - public static DeviceInfo getDeviceInfo(Context context, String codename) { - return getDeviceInfo(context, codename, null); - } - - /** - * Get the {@link DeviceInfo} for the current device. Do not run on the UI thread, as this may - * download JSON to retrieve the {@link DeviceInfo}. JSON is only downloaded once and then - * stored to {@link SharedPreferences}. - * - * @param context - * the application context. - * @param codename - * the codename of the device - * @param model - * the model of the device - * @return {@link DeviceInfo} for the current device. - */ - @WorkerThread - public static DeviceInfo getDeviceInfo(Context context, String codename, String model) { - SharedPreferences prefs = context.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE); - String key = String.format("%s:%s", codename, model); - String savedJson = prefs.getString(key, null); - if (savedJson != null) { - try { - return new DeviceInfo(new JSONObject(savedJson)); - } catch (JSONException e) { - e.printStackTrace(); - } - } - - // check if we have an internet connection - int ret = context.checkCallingOrSelfPermission(Manifest.permission.ACCESS_NETWORK_STATE); - boolean isConnectedToNetwork = false; - if (ret == PackageManager.PERMISSION_GRANTED) { - ConnectivityManager connMgr = (ConnectivityManager) - context.getSystemService(Context.CONNECTIVITY_SERVICE); - NetworkInfo networkInfo = connMgr.getActiveNetworkInfo(); - if (networkInfo != null && networkInfo.isConnected()) { - isConnectedToNetwork = true; - } - } else { - // assume we are connected. - isConnectedToNetwork = true; - } - - if (isConnectedToNetwork) { - try { - // Get the device name from the generated JSON files created from Google's device list. - String url = String.format(DEVICE_JSON_URL, codename.toLowerCase(Locale.ENGLISH)); - String jsonString = downloadJson(url); - JSONArray jsonArray = new JSONArray(jsonString); - for (int i = 0, len = jsonArray.length(); i < len; i++) { - JSONObject json = jsonArray.getJSONObject(i); - DeviceInfo info = new DeviceInfo(json); - if ((codename.equalsIgnoreCase(info.codename) && model == null) - || codename.equalsIgnoreCase(info.codename) && model.equalsIgnoreCase(info.model)) { - // Save to SharedPreferences so we don't need to make another request. - SharedPreferences.Editor editor = prefs.edit(); - editor.putString(key, json.toString()); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) { - editor.apply(); - } else { - editor.commit(); - } - return info; - } - } - } catch (Exception e) { - e.printStackTrace(); - } - } - - if (codename.equals(Build.DEVICE) && model.equals(Build.MODEL)) { - return new DeviceInfo(Build.MANUFACTURER, getDeviceName(), codename, model); // current device - } - - return new DeviceInfo(null, null, codename, model); // unknown device - } - - /** - *

Capitalizes getAllProcesses the whitespace separated words in a String. Only the first - * letter of each word is changed.

- * - * Whitespace is defined by {@link Character#isWhitespace(char)}. - * - * @param str - * the String to capitalize - * @return capitalized The capitalized String - */ - private static String capitalize(String str) { - if (TextUtils.isEmpty(str)) { - return str; - } - char[] arr = str.toCharArray(); - boolean capitalizeNext = true; - String phrase = ""; - for (char c : arr) { - if (capitalizeNext && Character.isLetter(c)) { - phrase += Character.toUpperCase(c); - capitalizeNext = false; - continue; - } else if (Character.isWhitespace(c)) { - capitalizeNext = true; - } - phrase += c; - } - return phrase; - } - - /** Download URL to String */ - @WorkerThread - private static String downloadJson(String myurl) throws IOException { - StringBuilder sb = new StringBuilder(); - BufferedReader reader = null; - try { - URL url = new URL(myurl); - HttpURLConnection conn = (HttpURLConnection) url.openConnection(); - conn.setReadTimeout(10000); - conn.setConnectTimeout(15000); - conn.setRequestMethod("GET"); - conn.setDoInput(true); - conn.connect(); - if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) { - reader = new BufferedReader(new InputStreamReader(conn.getInputStream())); - String line; - while ((line = reader.readLine()) != null) { - sb.append(line).append('\n'); - } - } - return sb.toString(); - } finally { - if (reader != null) { - reader.close(); - } - } - } - - public static final class Request { - - final Context context; - final Handler handler; - String codename; - String model; - - private Request(Context ctx) { - context = ctx; - handler = new Handler(ctx.getMainLooper()); - } - - /** - * Set the device codename to query. You should also set the model. - * - * @param codename - * the value of the system property "ro.product.device" - * @return This Request object to allow for chaining of calls to set methods. - * @see Build#DEVICE - */ - public Request setCodename(String codename) { - this.codename = codename; - return this; - } - - /** - * Set the device model to query. You should also set the codename. - * - * @param model - * the value of the system property "ro.product.model" - * @return This Request object to allow for chaining of calls to set methods. - * @see Build#MODEL - */ - public Request setModel(String model) { - this.model = model; - return this; - } - - /** - * Download information about the device. This saves the results in shared-preferences so - * future requests will not need a network connection. - * - * @param callback - * the callback to retrieve the {@link DeviceInfo} - */ - public void request(Callback callback) { - if (codename == null && model == null) { - codename = Build.DEVICE; - model = Build.MODEL; - } - GetDeviceRunnable runnable = new GetDeviceRunnable(callback); - if (Looper.myLooper() == Looper.getMainLooper()) { - new Thread(runnable).start(); - } else { - runnable.run(); // already running in background thread. - } - } - - private final class GetDeviceRunnable implements Runnable { - - final Callback callback; - DeviceInfo deviceInfo; - Exception error; - - public GetDeviceRunnable(Callback callback) { - this.callback = callback; - } - - @Override public void run() { - try { - deviceInfo = getDeviceInfo(context, codename, model); - } catch (Exception e) { - error = e; - } - handler.post(new Runnable() { - - @Override public void run() { - callback.onFinished(deviceInfo, error); - } - }); - } - } - - } - - /** - * Callback which is invoked when the {@link DeviceInfo} is finished loading. - */ - public interface Callback { - - /** - * Callback to get the device info. This is run on the UI thread. - * - * @param info - * the requested {@link DeviceInfo} - * @param error - * {@code null} if nothing went wrong. - */ - void onFinished(DeviceInfo info, Exception error); - } - - /** - * Device information based on - * Google's maintained list. - */ - public static final class DeviceInfo { - - /** Retail branding */ - public final String manufacturer; - - /** Marketing name */ - public final String marketName; - - /** the value of the system property "ro.product.device" */ - public final String codename; - - /** the value of the system property "ro.product.model" */ - public final String model; - - public DeviceInfo(String manufacturer, String marketName, String codename, String model) { - this.manufacturer = manufacturer; - this.marketName = marketName; - this.codename = codename; - this.model = model; - } - - private DeviceInfo(JSONObject jsonObject) throws JSONException { - manufacturer = jsonObject.getString("manufacturer"); - marketName = jsonObject.getString("market_name"); - codename = jsonObject.getString("codename"); - model = jsonObject.getString("model"); - } - - /** - * @return the consumer friendly name of the device. - */ - public String getName() { - if (!TextUtils.isEmpty(marketName)) { - return marketName; - } - return capitalize(model); - } - } - -} - diff --git a/app/src/main/java/com/tangem/util/FormatUtil.java b/app/src/main/java/com/tangem/util/FormatUtil.java deleted file mode 100644 index 5196d2bbaf..0000000000 --- a/app/src/main/java/com/tangem/util/FormatUtil.java +++ /dev/null @@ -1,87 +0,0 @@ -package com.tangem.util; - -import java.math.BigDecimal; -import java.math.BigInteger; -import java.text.DecimalFormat; -import java.text.DecimalFormatSymbols; -import java.util.Locale; - -/** - * Created by Ilia on 15.02.2018. - */ - -public class FormatUtil { - public static long parseValue(String valueStr) throws NumberFormatException { - return new BigDecimal(valueStr).multiply(BigDecimal.valueOf(1_0000_0000)).setScale(0, BigDecimal.ROUND_HALF_DOWN).longValueExact(); - } - - public static String DoubleToString(double amount) { - DecimalFormat myFormatter = GetDecimalFormat(); - String output = myFormatter.format(amount); - return output; - } - - public static DecimalFormat GetDecimalFormat() { - DecimalFormatSymbols symbols = new DecimalFormatSymbols(); - symbols.setDecimalSeparator('.'); - - String pattern = "#0.########"; -// DecimalFormat myFormatter = new DecimalFormat(pattern, symbols); - DecimalFormat myFormatter = new DecimalFormat(pattern); - - myFormatter.setParseBigDecimal(true); - - return myFormatter; - } - - public static BigDecimal stringToBigDecimal(final String formattedString, - final Locale locale) - { - final DecimalFormatSymbols symbols; - final char groupSeparatorChar; - final String groupSeparator; - final char decimalSeparatorChar; - final String decimalSeparator; - String fixedString; - final BigDecimal number; - - symbols = new DecimalFormatSymbols(locale); - groupSeparatorChar = symbols.getGroupingSeparator(); - decimalSeparatorChar = symbols.getDecimalSeparator(); - - if(groupSeparatorChar == '.') - { - groupSeparator = "\\" + groupSeparatorChar; - } - else - { - groupSeparator = Character.toString(groupSeparatorChar); - } - - if(decimalSeparatorChar == '.') - { - decimalSeparator = "\\" + decimalSeparatorChar; - } - else - { - decimalSeparator = Character.toString(decimalSeparatorChar); - } - - fixedString = formattedString.replaceAll(groupSeparator , ""); - fixedString = fixedString.replaceAll(decimalSeparator , "."); - number = new BigDecimal(fixedString); - - return (number); - } - - public static long ConvertStringToLong(String caption) throws Exception { -// BigDecimal d = new BigDecimal(caption); - BigDecimal d = stringToBigDecimal(caption,Locale.US); - d = d.multiply(new BigDecimal(100000000)); - d = d.setScale(5); - BigInteger b = d.toBigInteger(); - long l = b.longValue(); - return l; - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/util/LOG.java b/app/src/main/java/com/tangem/util/LOG.java deleted file mode 100644 index 387c3f90b1..0000000000 --- a/app/src/main/java/com/tangem/util/LOG.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.tangem.util; - -import android.util.Log; - -import com.tangem.wallet.BuildConfig; - -public final class LOG { - public static String TAG = "com.tangem.wallet"; - - public static void d(String msg) { - d(null, msg); - } - - public static void e(String msg) { - e(null, msg); - } - - public static void d(String tag, String msg) { - if (BuildConfig.DEBUG) - Log.d(TAG + (tag != null && !tag.equals("") ? "." + tag : ""), msg); - } - - public static void d(String tag, int msg) { - if (BuildConfig.DEBUG) - Log.d(TAG + (tag != null && !tag.equals("") ? "." + tag : ""), String.valueOf(msg)); - } - - public static void i(String tag, String msg) { - if (BuildConfig.DEBUG) - Log.i(TAG + (tag != null && !tag.equals("") ? "." + tag : ""), msg); - } - - public static void i(String tag, int msg) { - if (BuildConfig.DEBUG) - Log.i(TAG + (tag != null && !tag.equals("") ? "." + tag : ""), String.valueOf(msg)); - } - - public static void w(String tag, String msg) { - if (BuildConfig.DEBUG) - Log.w(TAG + (tag != null && !tag.equals("") ? "." + tag : ""), msg); - } - - public static void w(String tag, int msg) { - if (BuildConfig.DEBUG) - Log.w(TAG + (tag != null && !tag.equals("") ? "." + tag : ""), String.valueOf(msg)); - } - - public static void e(String tag, String msg) { - if (BuildConfig.DEBUG) - Log.e(TAG + (tag != null && !tag.equals("") ? "." + tag : ""), msg); - } - - public static void e(String tag, int msg) { - if (BuildConfig.DEBUG) - Log.d(TAG + (tag != null && !tag.equals("") ? "." + tag : ""), String.valueOf(msg)); - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/util/UtilHelper.kt b/app/src/main/java/com/tangem/util/UtilHelper.kt deleted file mode 100644 index 914520d6e2..0000000000 --- a/app/src/main/java/com/tangem/util/UtilHelper.kt +++ /dev/null @@ -1,67 +0,0 @@ -package com.tangem.util - -import android.content.Context -import android.graphics.Bitmap -import android.graphics.Color -import android.net.ConnectivityManager -import android.net.NetworkInfo -import android.os.Build -import android.widget.Toast -import com.google.zxing.BarcodeFormat -import com.google.zxing.EncodeHintType -import com.google.zxing.WriterException -import com.google.zxing.qrcode.QRCodeWriter -import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel -import java.util.* - -object UtilHelper { - - @Throws(WriterException::class) - fun generateQrCode(myCodeText: String): Bitmap { - val hintMap = Hashtable() - hintMap[EncodeHintType.ERROR_CORRECTION] = ErrorCorrectionLevel.M // H = 30% damage - hintMap[EncodeHintType.MARGIN] = 2 - - val qrCodeWriter = QRCodeWriter() - - val size = 256 - - val bitMatrix = qrCodeWriter.encode(myCodeText, BarcodeFormat.QR_CODE, size, size, hintMap) - val width = bitMatrix.width - val bmp = Bitmap.createBitmap(width, width, Bitmap.Config.RGB_565) - for (x in 0 until width) { - for (y in 0 until width) { - bmp.setPixel(y, x, if (bitMatrix.get(x, y)) Color.BLACK else Color.WHITE) - } - } - return bmp - } - - fun isOnline(context: Context): Boolean { - return try { - val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager - val netInfo: NetworkInfo? - netInfo = cm.activeNetworkInfo - netInfo != null && netInfo.isConnected - } catch (e: NullPointerException) { - e.printStackTrace() - false - } - } - - fun getDeviceInfo(): String { - return "----------------------------------------\n" + - "MODEL: " + Build.MODEL + "\n" + - "ID: " + Build.ID + "\n" + - "Manufacturer: " + Build.MANUFACTURER + "\n" + - "Brand: " + Build.BRAND + "\n" + - "Hardware: " + Build.HARDWARE + "\n" + - "Version: " + Build.VERSION.RELEASE + ", " + Build.VERSION.INCREMENTAL + "\n" + - "OS: " + Build.VERSION.BASE_OS + "\n" + - "SDK: " + Build.VERSION.SDK_INT + "\n" + - "BOARD: " + Build.BOARD + "\n" + - "FINGERPRINT: " + Build.FINGERPRINT + "\n" + - "----------------------------------------\n" - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/util/extensions/Context.kt b/app/src/main/java/com/tangem/util/extensions/Context.kt deleted file mode 100644 index f138f41bb7..0000000000 --- a/app/src/main/java/com/tangem/util/extensions/Context.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.tangem.util.extensions - -import android.content.Context -import androidx.annotation.ColorRes -import androidx.core.content.ContextCompat - -fun Context.colorFrom(@ColorRes colorId: Int) = ContextCompat.getColor(this, colorId) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/util/extensions/TangemCard.kt b/app/src/main/java/com/tangem/util/extensions/TangemCard.kt deleted file mode 100644 index 4e3cc50e4b..0000000000 --- a/app/src/main/java/com/tangem/util/extensions/TangemCard.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.tangem.util.extensions - -import com.tangem.tangem_card.data.TangemCard -import com.tangem.tangem_card.util.Util - -fun TangemCard.isStart2CoinCard(): Boolean = (Util.bytesToHex(this.cid).startsWith("1")) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/BCHUtils.java b/app/src/main/java/com/tangem/wallet/BCHUtils.java deleted file mode 100644 index 8264d849d0..0000000000 --- a/app/src/main/java/com/tangem/wallet/BCHUtils.java +++ /dev/null @@ -1,345 +0,0 @@ -package com.tangem.wallet; - -/** - * Created by Ilia on 29.09.2017. - */ - -import android.util.Log; - -import com.tangem.tangem_card.util.Util; -import com.tangem.util.CryptoUtil; -import com.tangem.util.FormatUtil; -import com.tangem.wallet.btc.BitcoinException; -import com.tangem.wallet.btc.BitcoinOutputStream; -import com.tangem.wallet.btc.BtcData; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.math.BigInteger; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -@SuppressWarnings({"WeakerAccess", "TryWithIdenticalCatches", "unused"}) -public final class BCHUtils { - static final BigInteger LARGEST_PRIVATE_KEY = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 16);//SECP256K1_N - public static final long MIN_FEE_PER_KB = 10000; - public static final long MAX_ALLOWED_FEE = FormatUtil.parseValue("0.1"); - public static final long MIN_PRIORITY_FOR_NO_FEE = 57600000; - public static final long MIN_MIN_OUTPUT_VALUE_FOR_NO_FEE = 10000000L; - public static final int MAX_TX_LEN_FOR_NO_FEE = 10000; - public static final float EXPECTED_BLOCKS_PER_DAY = 144.0f;//(expected confirmations per day) - - public static String toHex(byte[] bytes) { - if (bytes == null) { - return ""; - } - final char[] hexArray = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; - char[] hexChars = new char[bytes.length * 2]; - int v; - for (int j = 0; j < bytes.length; j++) { - v = bytes[j] & 0xFF; - hexChars[j * 2] = hexArray[v >>> 4]; - hexChars[j * 2 + 1] = hexArray[v & 0x0F]; - } - return new String(hexChars); - } - - public static byte[] buildTXForSign(String myAddress, String outputAddress, String changeAddress, ArrayList unspentOutputs, int currentInputPos, long amount, long change) throws BitcoinException, IOException { - - int inputPos = currentInputPos; - byte[] tx = buildPreimage(outputAddress, changeAddress, unspentOutputs, inputPos, amount, change); - return tx; - } - - public static byte[] buildTXForSend(String outputAddress, String changeAddress, ArrayList unspentOutputs, long amount, long change) throws BitcoinException, IOException { - - int inputPos = -1; - byte[] tx = buildBodyTX(outputAddress, changeAddress, unspentOutputs, inputPos, amount, change); - return tx; - } - - //BIP 143 as reference + script length added - public static byte[] buildPreimage(String outputAddress, String changeAddress, ArrayList unspentOutputs, int inputPos, long amount, long change) throws BitcoinException, IOException { - - //nVersion of the transaction (4-byte little endian) - BitcoinOutputStream forSign = new BitcoinOutputStream(); - //forSign.writeInt32(0x01); - forSign.write(new byte[]{0x02, 0x00, 0x00, 0x00}); // version - - //inputCount - byte inputCount = (byte) unspentOutputs.size(); - - //hashPrevouts (32-byte hash) - ByteArrayOutputStream prevouts = new ByteArrayOutputStream(); - for (int i = 0; i < inputCount; ++i) { - UnspentOutputInfo outPut = unspentOutputs.get(i); - byte[] txHash = BCHUtils.reverse(Util.hexToBytes(outPut.txHashForBuild));//Little-endian txID - byte[] txIndex = BCHUtils.reverse(Util.intToByteArray4(outPut.outputIndex));//Little-endian outputIndex - prevouts.write(txHash); - prevouts.write(txIndex); - } - byte[] hashPrevouts = CryptoUtil.doubleSha256(prevouts.toByteArray()); - forSign.write(hashPrevouts); - - //hashSequence (32-byte hash), ffffffff only - ByteArrayOutputStream sequences = new ByteArrayOutputStream(); - for (int i = 0; i < inputCount; ++i) { - sequences.write(new byte[]{(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff}); - } - byte[] hashSequence = CryptoUtil.doubleSha256(sequences.toByteArray()); - forSign.write(hashSequence); - - //outpoint (32-byte hash + 4-byte little endian) - UnspentOutputInfo outPut = unspentOutputs.get(inputPos); - byte[] txHash = BCHUtils.reverse(Util.hexToBytes(outPut.txHashForBuild));//Little-endian txID - byte[] txIndex = BCHUtils.reverse(Util.intToByteArray4(outPut.outputIndex));//Little-endian outputIndex - forSign.write(txHash); - forSign.write(txIndex); - - //scriptCode of the input (serialized as scripts inside CTxOuts) - byte[] scriptCode = Transaction.Script.buildOutput(changeAddress).bytes; //build change out - byte[] scriptLength = Util.intToByteArray(scriptCode.length); - forSign.write(scriptLength); - forSign.write(scriptCode); - - //value of the output spent by this input (8-byte little endian) - byte[] outValue = BCHUtils.reverse(Util.longToByteArray8(outPut.value)); - forSign.write(outValue); - - //nSequence of the input (4-byte little endian), ffffffff only - forSign.write(new byte[]{(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff}); - - //hashOutputs (32-byte hash) - ByteArrayOutputStream outputs = new ByteArrayOutputStream(); - byte[] sendAmount = BCHUtils.reverse(Util.longToByteArray8(amount)); - byte[] sendScript = Transaction.Script.buildOutput(outputAddress).bytes; // build out - byte[] sendLength = Util.intToByteArray(sendScript.length); - outputs.write(sendAmount); - outputs.write(sendLength); - outputs.write(sendScript); - //output for change (if any) - if (change != 0) { - byte[] changeAmount = BCHUtils.reverse(Util.longToByteArray8(change)); - byte[] changeScript = Transaction.Script.buildOutput(changeAddress).bytes; //build change out - byte[] changeLength = Util.intToByteArray(changeScript.length); - outputs.write(changeAmount); - outputs.write(changeLength); - outputs.write(changeScript); - } - byte[] hashOutputs = CryptoUtil.doubleSha256(outputs.toByteArray()); - forSign.write(hashOutputs); - - //nLocktime of the transaction (4-byte little endian) - forSign.write(new byte[]{0x00, 0x00, 0x00, 0x00}); - - //sighash type of the signature (4-byte little endian) - forSign.write(new byte[]{0x41, 0x00, 0x00, 0x00}); - - byte[] rawData = forSign.toByteArray(); - - Log.e("Sign_TX_Body", BCHUtils.toHex(rawData)); - - return rawData; - - } - public static byte[] buildBodyTX(String outputAddress, String changeAddress, ArrayList unspentOutputs, int inputPos, long amount, long change) throws BitcoinException, IOException { - - //0200000000 - BitcoinOutputStream forSign = new BitcoinOutputStream(); - //forSign.writeInt32(0x01); - forSign.write(new byte[]{0x02, 0x00, 0x00, 0x00}); // version - - //01 - byte inputCount = (byte) unspentOutputs.size(); - forSign.write(inputCount); // input count - //hex str hash prev btc - - for (int i = 0; i < inputCount; ++i) { - UnspentOutputInfo outPut = unspentOutputs.get(i); - int outputIndex = outPut.outputIndex; - byte[] txHash = BCHUtils.reverse(Util.hexToBytes(outPut.txHashForBuild));//Sha256Hash.hash(rawTxByte); - forSign.write(txHash); - forSign.writeInt32(outputIndex); //output index in prev tx - if (inputPos == -1 || i == inputPos) { - // hex str 1976a914....88ac - forSign.write((byte) outPut.scriptForBuild.length); - forSign.write(outPut.scriptForBuild); - } else { - forSign.write(0x00); - } - //ffffffff - forSign.write(new byte[]{(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff}); // sequence - } - - - //02 - byte outputCount = (byte) ((change == 0) ? 1 : 2); // outputCount - forSign.write(outputCount); - - //8 bytes - - forSign.writeInt64(amount); //amount - byte[] sendScript = Transaction.Script.buildOutput(outputAddress).bytes; // build out - //hex str 1976a914....88ac - forSign.write((byte) sendScript.length); - forSign.write(sendScript); - - if (change != 0) { - //8 bytes - forSign.writeInt64(change); // change - //hex str 1976a914....88ac - byte[] chancheScript = Transaction.Script.buildOutput(changeAddress).bytes; //build out - forSign.write((byte) chancheScript.length); - forSign.write(chancheScript); - - } - - //00000000 - forSign.write(new byte[]{0x00, 0x00, 0x00, 0x00}); - - //forSign.write(new byte[]{0x01, 0x00, 0x00, 0x00}); - - byte[] rawData = forSign.toByteArray(); - - Log.e("Sign_TX_Body", BCHUtils.toHex(rawData)); - - return rawData; - } - - public static byte[] buildBodyTX(String outputAddress, String changeAddress, int outputIndex, String prevID, long amount, long change, byte[] script) throws BitcoinException, IOException { - - //0200000000 - BitcoinOutputStream forSign = new BitcoinOutputStream(); - forSign.writeInt32(0x01);//write(new byte[]{0x02, 0x00, 0x00, 0x00}); // version - - //01 - byte inputCount = 1; - forSign.write(inputCount); // input count - //hex str hash prev btc - byte[] txHash = BCHUtils.reverse(Util.hexToBytes(prevID));//Sha256Hash.hash(rawTxByte); - forSign.write(txHash); //previos tx hash - - //00000000 - //byte indexOutput = outputIndex; - forSign.writeInt32(outputIndex/*indexOutput*/); //output index in prev tx - //forSign.write(0x00); - - // hex str 1976a914....88ac - forSign.write((byte) script.length); - forSign.write(script); - - //ffffffff - forSign.write(new byte[]{(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff}); // sequence - - //02 - byte outputCount = (byte) ((change == 0) ? 1 : 2); // outputCount - forSign.write(outputCount); - - //8 bytes - - forSign.writeInt64(amount); //amount - byte[] sendScript = Transaction.Script.buildOutput(outputAddress).bytes; // build out - //hex str 1976a914....88ac - forSign.write((byte) sendScript.length); - forSign.write(sendScript); - - if (change != 0) { - //8 bytes - forSign.writeInt64(change); // change - //hex str 1976a914....88ac - byte[] chancheScript = Transaction.Script.buildOutput(changeAddress).bytes; //build out - forSign.write((byte) chancheScript.length); - forSign.write(chancheScript); - - } - - //00000000 - forSign.write(new byte[]{0x00, 0x00, 0x00, 0x00}); - - byte[] rawData = forSign.toByteArray(); - - //Log.e("Sign_TX_Body", BCHUtils.toHex(rawData)); - return rawData; - } - - public static ArrayList getOutputs(List rawTxList, byte[] outputScriptWeAreAbleToSpend) throws BitcoinException { - ArrayList unspentOutputs = new ArrayList<>(); - - for (BtcData.UnspentTransaction current : rawTxList) { - byte[] rawTxByte = BCHUtils.fromHex(current.script); - if (rawTxByte == null || current.script.isEmpty()) { - continue; - } - - Transaction baseTx = new Transaction(rawTxByte); - - if (baseTx.inputs.length == 0 || baseTx.outputs.length == 0) - throw new IllegalArgumentException("Unable to decode given transaction"); - - byte[] txHash = BCHUtils.reverse(CryptoUtil.doubleSha256(rawTxByte)); - String txHashForBuild = current.txID; - byte[] sign = null; - - for (int outputIndex = 0; outputIndex < baseTx.outputs.length; outputIndex++) { - Transaction.Output output = baseTx.outputs[outputIndex]; - - // find outputs - if (Arrays.equals(outputScriptWeAreAbleToSpend, output.script.bytes)) { - unspentOutputs.add(new UnspentOutputInfo(txHash, output.script, output.value, outputIndex, -1, txHashForBuild, sign)); - } - } - - } - - return unspentOutputs; - } - - public static byte[] fromHex(String s) { - if (s != null) { - try { - StringBuilder sb = new StringBuilder(s.length()); - for (int i = 0; i < s.length(); i++) { - char ch = s.charAt(i); - if (!Character.isWhitespace(ch)) { - sb.append(ch); - } - } - s = sb.toString(); - int len = s.length(); - byte[] data = new byte[len / 2]; - for (int i = 0; i < len; i += 2) { - int hi = (Character.digit(s.charAt(i), 16) << 4); - int low = Character.digit(s.charAt(i + 1), 16); - if (hi >= 256 || low < 0 || low >= 16) { - return null; - } - data[i / 2] = (byte) (hi | low); - } - return data; - } catch (Exception ignored) { - } - } - return null; - } - - public static byte[] reverse(byte[] bytes) { - byte[] result = new byte[bytes.length]; - for (int i = 0; i < bytes.length; i++) { - result[i] = bytes[bytes.length - i - 1]; - } - return result; - } - - public static byte[] reverseInPlace(byte[] bytes) { - int len = bytes.length / 2; - for (int i = 0; i < len; i++) { - byte t = bytes[i]; - bytes[i] = bytes[bytes.length - i - 1]; - bytes[bytes.length - i - 1] = t; - } - return bytes; - } - -} - diff --git a/app/src/main/java/com/tangem/wallet/BTCUtils.java b/app/src/main/java/com/tangem/wallet/BTCUtils.java deleted file mode 100644 index 1b3c8fe894..0000000000 --- a/app/src/main/java/com/tangem/wallet/BTCUtils.java +++ /dev/null @@ -1,280 +0,0 @@ -package com.tangem.wallet; - -/** - * Created by Ilia on 29.09.2017. - */ - -import android.util.Log; - -import com.tangem.tangem_card.util.Util; -import com.tangem.util.CryptoUtil; -import com.tangem.util.FormatUtil; -import com.tangem.wallet.btc.BitcoinException; -import com.tangem.wallet.btc.BitcoinOutputStream; -import com.tangem.wallet.btc.BtcData; - -import org.bitcoinj.core.Address; -import org.bitcoinj.core.SegwitAddress; -import org.bitcoinj.script.ScriptBuilder; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.math.BigInteger; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -@SuppressWarnings({"WeakerAccess", "TryWithIdenticalCatches", "unused"}) -public final class BTCUtils { - static final BigInteger LARGEST_PRIVATE_KEY = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 16);//SECP256K1_N - public static final long MIN_FEE_PER_KB = 10000; - public static final long MAX_ALLOWED_FEE = FormatUtil.parseValue("0.1"); - public static final long MIN_PRIORITY_FOR_NO_FEE = 57600000; - public static final long MIN_MIN_OUTPUT_VALUE_FOR_NO_FEE = 10000000L; - public static final int MAX_TX_LEN_FOR_NO_FEE = 10000; - public static final float EXPECTED_BLOCKS_PER_DAY = 144.0f;//(expected confirmations per day) - - public static String toHex(byte[] bytes) { - if (bytes == null) { - return ""; - } - final char[] hexArray = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; - char[] hexChars = new char[bytes.length * 2]; - int v; - for (int j = 0; j < bytes.length; j++) { - v = bytes[j] & 0xFF; - hexChars[j * 2] = hexArray[v >>> 4]; - hexChars[j * 2 + 1] = hexArray[v & 0x0F]; - } - return new String(hexChars); - } - - public static byte[] buildTXForSign(String myAddress, String outputAddress, String changeAddress, ArrayList unspentOutputs, int currentInputPos, long amount, long change) throws BitcoinException, IOException { - byte[] myScript = Transaction.Script.buildOutput(myAddress).bytes; - unspentOutputs.get(currentInputPos).scriptForBuild = myScript; - int inputPos = currentInputPos; - byte[] body = buildBodyTX(outputAddress, changeAddress, unspentOutputs, inputPos, amount, change); - - ByteArrayOutputStream os = new ByteArrayOutputStream(); - os.write(body); - os.write(new byte[]{0x01, 0x00, 0x00, 0x00}); - byte[] tx = os.toByteArray(); - return tx; - } - - public static byte[] buildTXForSend(String outputAddress, String changeAddress, ArrayList unspentOutputs, long amount, long change) throws BitcoinException, IOException { - int inputPos = -1; - byte[] body = buildBodyTX(outputAddress, changeAddress, unspentOutputs, inputPos, amount, change); - ByteArrayOutputStream os = new ByteArrayOutputStream(); - os.write(body); - byte[] tx = os.toByteArray(); - return tx; - } - - public static byte[] buildBodyTX(String outputAddress, String changeAddress, ArrayList unspentOutputs, int inputPos, long amount, long change) throws BitcoinException, IOException { - - //0200000000 - BitcoinOutputStream forSign = new BitcoinOutputStream(); - forSign.writeInt32(0x01);//write(new byte[]{0x02, 0x00, 0x00, 0x00}); // version - - //01 - byte inputCount = (byte) unspentOutputs.size(); - forSign.write(inputCount); // input count - //hex str hash prev btc - - for (int i = 0; i < inputCount; ++i) { - UnspentOutputInfo outPut = unspentOutputs.get(i); - int outputIndex = outPut.outputIndex; - byte[] txHash = BTCUtils.reverse(Util.hexToBytes(outPut.txHashForBuild));//Sha256Hash.hash(rawTxByte); - forSign.write(txHash); - forSign.writeInt32(outputIndex); //output index in prev tx - if (inputPos == -1 || i == inputPos) { - // hex str 1976a914....88ac - forSign.write((byte) outPut.scriptForBuild.length); - forSign.write(outPut.scriptForBuild); - } else { - forSign.write(0x00); - } - //ffffffff - forSign.write(new byte[]{(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff}); // sequence - } - - - //02 - byte outputCount = (byte) ((change == 0) ? 1 : 2); // outputCount - forSign.write(outputCount); - - //8 bytes - - forSign.writeInt64(amount); //amount - - byte[] sendScript; - if (outputAddress.startsWith("bc1") || outputAddress.startsWith("tb1")) { - Address address = SegwitAddress.fromBech32(null, outputAddress); - sendScript = ScriptBuilder.createOutputScript(address).getProgram(); - } else { - sendScript = Transaction.Script.buildOutput(outputAddress).bytes; // build out - } - - //hex str 1976a914....88ac - forSign.write((byte) sendScript.length); - forSign.write(sendScript); - - if (change != 0) { - //8 bytes - forSign.writeInt64(change); // change - //hex str 1976a914....88ac - byte[] chancheScript = Transaction.Script.buildOutput(changeAddress).bytes; //build out - forSign.write((byte) chancheScript.length); - forSign.write(chancheScript); - - } - - //00000000 - forSign.write(new byte[]{0x00, 0x00, 0x00, 0x00}); - - //forSign.write(new byte[]{0x01, 0x00, 0x00, 0x00}); - - byte[] rawData = forSign.toByteArray(); - - Log.e("Sign_TX_Body", BTCUtils.toHex(rawData)); - - return rawData; - } - - public static byte[] buildBodyTX(String outputAddress, String changeAddress, int outputIndex, String prevID, long amount, long change, byte[] script) throws BitcoinException, IOException { - - //0200000000 - BitcoinOutputStream forSign = new BitcoinOutputStream(); - forSign.writeInt32(0x01);//write(new byte[]{0x02, 0x00, 0x00, 0x00}); // version - - //01 - byte inputCount = 1; - forSign.write(inputCount); // input count - //hex str hash prev btc - byte[] txHash = BTCUtils.reverse(Util.hexToBytes(prevID));//Sha256Hash.hash(rawTxByte); - forSign.write(txHash); //previos tx hash - - //00000000 - //byte indexOutput = outputIndex; - forSign.writeInt32(outputIndex/*indexOutput*/); //output index in prev tx - //forSign.write(0x00); - - // hex str 1976a914....88ac - forSign.write((byte) script.length); - forSign.write(script); - - //ffffffff - forSign.write(new byte[]{(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff}); // sequence - - //02 - byte outputCount = (byte) ((change == 0) ? 1 : 2); // outputCount - forSign.write(outputCount); - - //8 bytes - - forSign.writeInt64(amount); //amount - byte[] sendScript = Transaction.Script.buildOutput(outputAddress).bytes; // build out - //hex str 1976a914....88ac - forSign.write((byte) sendScript.length); - forSign.write(sendScript); - - if (change != 0) { - //8 bytes - forSign.writeInt64(change); // change - //hex str 1976a914....88ac - byte[] chancheScript = Transaction.Script.buildOutput(changeAddress).bytes; //build out - forSign.write((byte) chancheScript.length); - forSign.write(chancheScript); - - } - - //00000000 - forSign.write(new byte[]{0x00, 0x00, 0x00, 0x00}); - - byte[] rawData = forSign.toByteArray(); - - //Log.e("Sign_TX_Body", BTCUtils.toHex(rawData)); - return rawData; - } - - public static ArrayList getOutputs(List rawTxList, byte[] outputScriptWeAreAbleToSpend) throws BitcoinException { - ArrayList unspentOutputs = new ArrayList<>(); - - for (BtcData.UnspentTransaction current : rawTxList) { - byte[] rawTxByte = BTCUtils.fromHex(current.script); - if (rawTxByte == null || current.script.isEmpty()) { - continue; - } - - Transaction baseTx = new Transaction(rawTxByte); - - if (baseTx.inputs.length == 0 || baseTx.outputs.length == 0) - throw new IllegalArgumentException("Unable to decode given transaction"); - - byte[] txHash = BTCUtils.reverse(CryptoUtil.doubleSha256(rawTxByte)); - String txHashForBuild = current.txID; - byte[] sign = null; - - for (int outputIndex = 0; outputIndex < baseTx.outputs.length; outputIndex++) { - Transaction.Output output = baseTx.outputs[outputIndex]; - - // find outputs - if (Arrays.equals(outputScriptWeAreAbleToSpend, output.script.bytes)) { - unspentOutputs.add(new UnspentOutputInfo(txHash, output.script, output.value, outputIndex, -1, txHashForBuild, sign)); - } - } - - } - - return unspentOutputs; - } - - public static byte[] fromHex(String s) { - if (s != null) { - try { - StringBuilder sb = new StringBuilder(s.length()); - for (int i = 0; i < s.length(); i++) { - char ch = s.charAt(i); - if (!Character.isWhitespace(ch)) { - sb.append(ch); - } - } - s = sb.toString(); - int len = s.length(); - byte[] data = new byte[len / 2]; - for (int i = 0; i < len; i += 2) { - int hi = (Character.digit(s.charAt(i), 16) << 4); - int low = Character.digit(s.charAt(i + 1), 16); - if (hi >= 256 || low < 0 || low >= 16) { - return null; - } - data[i / 2] = (byte) (hi | low); - } - return data; - } catch (Exception ignored) { - } - } - return null; - } - - public static byte[] reverse(byte[] bytes) { - byte[] result = new byte[bytes.length]; - for (int i = 0; i < bytes.length; i++) { - result[i] = bytes[bytes.length - i - 1]; - } - return result; - } - - public static byte[] reverseInPlace(byte[] bytes) { - int len = bytes.length / 2; - for (int i = 0; i < len; i++) { - byte t = bytes[i]; - bytes[i] = bytes[bytes.length - i - 1]; - bytes[bytes.length - i - 1] = t; - } - return bytes; - } - -} - diff --git a/app/src/main/java/com/tangem/wallet/BalanceValidator.java b/app/src/main/java/com/tangem/wallet/BalanceValidator.java deleted file mode 100644 index 9457557164..0000000000 --- a/app/src/main/java/com/tangem/wallet/BalanceValidator.java +++ /dev/null @@ -1,119 +0,0 @@ -package com.tangem.wallet; - -import androidx.annotation.StringRes; - -import com.tangem.App; -import com.tangem.tangem_card.data.TangemCard; - -public class BalanceValidator { - private @StringRes int firstLine; - private @StringRes int secondLine; - private int score; - private boolean hasPending; - - public @StringRes int getFirstLine() { - return firstLine; - } - - public void setFirstLine(@StringRes int value) { - firstLine = value; - } - - public @StringRes int getSecondLine(Boolean recommend) { -// if (!recommend) - return secondLine; - -// if (score > 89) { -// return "Safe to accept. " + secondLine; -// } else if (score > 74) { -// return "Not fully safe to accept. " + secondLine; -// } else if (score > 30) { -// return "Not safe to accept. " + secondLine; -// } else { -// return "Do not accept! " + secondLine; -// } - } - - public void setSecondLine(@StringRes int value) { - secondLine = value; - } - - public void setScore(int score) { - this.score = score; - } - - public int getColor() { - if( hasPending ) - { - return R.color.primary_dark; - } - if (score > 89) { - return R.color.confirmed; - } else if (score > 74) { - return android.R.color.holo_orange_light; - } else if (score > 0) { - return android.R.color.holo_orange_dark; - } else { - return android.R.color.holo_red_light; - } - } - - public void check(TangemContext ctx, Boolean attest) { - firstLine = R.string.balance_validator_first_line_verification_failed; - secondLine = R.string.empty_string; - TangemCard card = ctx.getCard(); - CoinEngine engine = CoinEngineFactory.INSTANCE.create(ctx); - - if (!engine.validateBalance(this)) return; - - hasPending=App.pendingTransactionsStorage.hasTransactions(card); - - if( hasPending ) - { - firstLine = R.string.balance_validator_first_line_pending_transaction; - secondLine = R.string.balance_validator_second_line_swipe_to_refresh; - return; - } - - // Verify card? - if (attest) { - - if (!card.isWalletPublicKeyValid()) { - score = 0; - firstLine = R.string.balance_validator_first_line_verification_failed; - secondLine = R.string.balance_validator_second_line_verification_failed; - return; - } - - if (card.isOnlineVerified() != null && !card.isOnlineVerified()) { - score = 0; - firstLine = R.string.balance_validator_first_line_not_genuine; - secondLine = R.string.balance_validator_second_line_failed_attestation; - return; - } - - if (card.isCodeConfirmed() != null && !card.isCodeConfirmed()) { - score = 0; - firstLine = R.string.balance_validator_first_line_not_genuine; - secondLine = R.string.balance_validator_second_line_failed_binary_code_verification; - return; - } - - if (card.PIN2 == TangemCard.PIN2_Mode.CustomPIN2) { - score = 0; - firstLine = R.string.balance_validator_first_line_locked_pin2; - secondLine = R.string.balance_validator_second_line_disable_pin_2; - return; - } - - // rule 2.b - if (card.isOnlineVerified()) { - secondLine += R.string.balance_validator_first_line_verify_identity; - } else { - score = 80; - secondLine += R.string.balance_validator_second_line_identity_not_verified; - } - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/Base58.java b/app/src/main/java/com/tangem/wallet/Base58.java deleted file mode 100644 index 0948e420fc..0000000000 --- a/app/src/main/java/com/tangem/wallet/Base58.java +++ /dev/null @@ -1,115 +0,0 @@ -package com.tangem.wallet; - -import java.math.BigInteger; - -/** - * Created by Ilia on 15.02.2018. - */ - -public class Base58 { - private static final char[] BASE58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray(); - - private static final int BASE58_CHUNK_DIGITS = 10;//how many base 58 digits fits in long - private static final BigInteger BASE58_CHUNK_MOD = BigInteger.valueOf(0x5fa8624c7fba400L); //58^BASE58_CHUNK_DIGITS - private static final byte[] BASE58_VALUES = new byte[]{-1, -1, -1, -1, -1, -1, -1, -1, -1, -2, -2, -2, -2, -2, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, -1, -1, -1, -1, -1, -1, - -1, 9, 10, 11, 12, 13, 14, 15, 16, -1, 17, 18, 19, 20, 21, -1, - 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, -1, -1, -1, -1, -1, - -1, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, -1, 44, 45, 46, - 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}; - - public static byte[] decodeBase58(String input) { - if (input == null) { - return null; - } - input = input.trim(); - if (input.length() == 0) { - return new byte[0]; - } - BigInteger resultNum = BigInteger.ZERO; - int nLeadingZeros = 0; - while (nLeadingZeros < input.length() && input.charAt(nLeadingZeros) == BASE58[0]) { - nLeadingZeros++; - } - long acc = 0; - int nDigits = 0; - int p = nLeadingZeros; - while (p < input.length()) { - int v = BASE58_VALUES[input.charAt(p) & 0xff]; - if (v >= 0) { - acc *= 58; - acc += v; - nDigits++; - if (nDigits == BASE58_CHUNK_DIGITS) { - resultNum = resultNum.multiply(BASE58_CHUNK_MOD).add(BigInteger.valueOf(acc)); - acc = 0; - nDigits = 0; - } - p++; - } else { - break; - } - } - if (nDigits > 0) { - long mul = 58; - while (--nDigits > 0) { - mul *= 58; - } - resultNum = resultNum.multiply(BigInteger.valueOf(mul)).add(BigInteger.valueOf(acc)); - } - final int BASE58_SPACE = -2; - while (p < input.length() && BASE58_VALUES[input.charAt(p) & 0xff] == BASE58_SPACE) { - p++; - } - if (p < input.length()) { - return null; - } - byte[] plainNumber = resultNum.toByteArray(); - int plainNumbersOffs = plainNumber[0] == 0 ? 1 : 0; - byte[] result = new byte[nLeadingZeros + plainNumber.length - plainNumbersOffs]; - System.arraycopy(plainNumber, plainNumbersOffs, result, nLeadingZeros, plainNumber.length - plainNumbersOffs); - return result; - } - - public static String encodeBase58(byte[] input) { - if (input == null) { - return null; - } - StringBuilder str = new StringBuilder((input.length * 350) / 256 + 1); - BigInteger bn = new BigInteger(1, input); - long rem; - while (true) { - BigInteger[] divideAndRemainder = bn.divideAndRemainder(BASE58_CHUNK_MOD); - bn = divideAndRemainder[0]; - rem = divideAndRemainder[1].longValue(); - if (bn.compareTo(BigInteger.ZERO) == 0) { - break; - } - for (int i = 0; i < BASE58_CHUNK_DIGITS; i++) { - str.append(BASE58[(int) (rem % 58)]); - rem /= 58; - } - } - while (rem != 0) { - str.append(BASE58[(int) (rem % 58)]); - rem /= 58; - } - str.reverse(); - int nLeadingZeros = 0; - while (nLeadingZeros < input.length && input[nLeadingZeros] == 0) { - str.insert(0, BASE58[0]); - nLeadingZeros++; - } - return str.toString(); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/CoinData.java b/app/src/main/java/com/tangem/wallet/CoinData.java deleted file mode 100644 index 67dba01d34..0000000000 --- a/app/src/main/java/com/tangem/wallet/CoinData.java +++ /dev/null @@ -1,232 +0,0 @@ -package com.tangem.wallet; - -import android.os.Bundle; -import android.util.Log; - -import com.tangem.data.Blockchain; - -public abstract class CoinData { - - public CoinData() { - } - - private String wallet; - - public void setWallet(String wallet) { - this.wallet = wallet; - } - - public String getWallet() { - return wallet; - } - - public String getShortWalletString() { - if (wallet.length() < 22) { - return wallet; - } else { - return wallet.substring(0, 10) + "......" + wallet.substring(wallet.length() - 10, wallet.length()); - } - } - - public boolean isBalanceReceived() { - return balanceReceived; - } - - private boolean balanceReceived = false; - - public void setBalanceReceived(boolean value) { - balanceReceived = value; - } - - public void loadFromBundle(Bundle B) { - wallet = B.getString("Wallet"); - - if (B.containsKey("balanceReceived")) setBalanceReceived(B.getBoolean("balanceReceived")); - - validationNodeDescription = B.getString("validationNodeDescription"); - -// if (B.containsKey("FailedBalance")) -// failedBalanceRequestCounter = new AtomicInteger(B.getInt("FailedBalance")); - - if (B.containsKey("isBalanceEqual")) setIsBalanceEqual(B.getBoolean("isBalanceEqual")); - - if (B.containsKey("rate")) - rate = B.getFloat("rate"); - if (B.containsKey("rateAlter")) - rateAlter = B.getFloat("rateAlter"); - - if (B.containsKey("sentTransactionsCount")) { - sentTransactionsCount = B.getInt("sentTransactionsCount"); - } - - if (B.containsKey("ResolvedPayIdAddress")) resolvedPayIdAddress = B.getString("ResolvedPayIdAddress"); - else resolvedPayIdAddress = null; - } - - public void saveToBundle(Bundle B) { - try { - B.putString("Wallet", wallet); - - if (balanceEqual != null) B.putBoolean("isBalanceEqual", balanceEqual); - -// if (failedBalanceRequestCounter != null) -// B.putInt("FailedBalance", failedBalanceRequestCounter.get()); - - B.putFloat("rate", rate); - B.putFloat("rateAlter", rateAlter); - - B.putBoolean("balanceReceived", balanceReceived); - B.putString("validationNodeDescription", validationNodeDescription); - - B.putInt("sentTransactionsCount", sentTransactionsCount); - - if (resolvedPayIdAddress != null) B.putString("ResolvedPayIdAddress", resolvedPayIdAddress); - } catch (Exception e) { - Log.e("Can't save to bundle ", e.getMessage()); - } - - } - - public Bundle asBundle() { - Bundle bundle = new Bundle(); - saveToBundle(bundle); - return bundle; - } - - public static CoinData fromBundle(Blockchain blockchain, Bundle bundle) { - CoinEngine engine = CoinEngineFactory.INSTANCE.create(blockchain); - if (engine == null) return null; - CoinData result = engine.createCoinData(); - result.loadFromBundle(bundle); - return result; - } - - //TODO - move all to special engines - private float rate = 0; - private float rateAlter = 0; - - public float getRate() { - return rate; - } - - public float getRateAlter() { - return rateAlter; - } - - public void setRate(float rate) { - this.rate = rate; - } - - public void setRateAlter(float rate) { - this.rateAlter = rate; - } - -// public Double amountFromInternalUnits(Long internalAmount) { -// // TODO java.lang.NullPointerException: Attempt to invoke virtual method 'long java.lang.Long.longValue()' on a null object reference -// return ((double) internalAmount) / -// getBlockchain().getMultiplier(); -// } -// -// public Double amountFromInternalUnits(Integer internalAmount) { -// return ((double) internalAmount) / getBlockchain().getMultiplier(); -// } -// -// // TODO разобраться с балансами, привести к единому интерфейсу -// public Long internalUnitsFromString(String caption) { -// try { -// return FormatUtil.ConvertStringToLong(caption); -// } catch (Exception e) { -// return null; -// } -// } -// -// public String getAmountDescription(Double amount) { -// String output = FormatUtil.DoubleToString(amount); -// return output + " " + getBlockchain().getCurrency(); -// } - - public boolean getAmountEquivalentDescriptionAvailable() { - return rate > 0; - } - - - public void clearInfo() { - setIsBalanceEqual(false); - setBalanceReceived(false); - setValidationNodeDescription(""); - minFee = null; - maxFee = null; - normalFee = null; - rate = 0f; - rateAlter = 0f; - sentTransactionsCount = 0; - resolvedPayIdAddress = null; - } - -// private AtomicInteger failedBalanceRequestCounter; -// -// public int incFailedBalanceRequestCounter() { -// if (failedBalanceRequestCounter == null) -// failedBalanceRequestCounter = new AtomicInteger(0); -// return failedBalanceRequestCounter.incrementAndGet(); -// } -// -// public void resetFailedBalanceRequestCounter() { -// failedBalanceRequestCounter = new AtomicInteger(0); -// } -// -// public int getFailedBalanceRequestCounter() { -// if (failedBalanceRequestCounter == null) -// return 0; -// return failedBalanceRequestCounter.get(); -// } - - private Boolean balanceEqual; - - public Boolean isBalanceEqual() { - return balanceEqual; - } - - public void setIsBalanceEqual(boolean isEqual) { - balanceEqual = isEqual; - } - - private String validationNodeDescription = ""; - - public String getValidationNodeDescription() { - return validationNodeDescription; - } - - public void setValidationNodeDescription(String validationNodeDescription) { - this.validationNodeDescription = validationNodeDescription; - } - - public CoinEngine.Amount minFee = null; - public CoinEngine.Amount normalFee = null; - public CoinEngine.Amount maxFee = null; - - private int sentTransactionsCount = 0; - - public int getSentTransactionsCount() { - return sentTransactionsCount; - } - - public void setSentTransactionsCount(int sentTransactionsCount) { - this.sentTransactionsCount = sentTransactionsCount; - } - - public void incSentTransactionsCount() { - sentTransactionsCount++; - } - - private String resolvedPayIdAddress = null; - - public String getResolvedPayIdAddress() { - return resolvedPayIdAddress; - } - - public void setResolvedPayIdAddress(String resolvedPayIdAddress) { - this.resolvedPayIdAddress = resolvedPayIdAddress; - } - -} diff --git a/app/src/main/java/com/tangem/wallet/CoinEngine.java b/app/src/main/java/com/tangem/wallet/CoinEngine.java deleted file mode 100644 index 9e30d2df6f..0000000000 --- a/app/src/main/java/com/tangem/wallet/CoinEngine.java +++ /dev/null @@ -1,410 +0,0 @@ -package com.tangem.wallet; - -import android.net.Uri; -import android.text.InputFilter; - -import com.tangem.data.Blockchain; -import com.tangem.tangem_card.reader.CardProtocol; -import com.tangem.tangem_card.tasks.SignTask; - -import java.io.IOException; -import java.math.BigDecimal; -import java.math.BigInteger; -import java.math.RoundingMode; -import java.net.URL; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import java.text.DecimalFormat; -import java.text.DecimalFormatSymbols; -import java.util.Locale; - -import co.nstant.in.cbor.CborException; - -/** - * Created by Ilia on 15.02.2018. - */ - -public abstract class CoinEngine { - - public static class InternalAmount extends BigDecimal { - private String currency; - - public InternalAmount() { - super(0); - currency = ""; - } - - public InternalAmount(String amountString, String currency) { - super(amountString.replace(',', '.')); - this.currency = currency; - } - - public InternalAmount(long amount, String currency) { - super(amount); - this.currency = currency; - } - - public InternalAmount(BigDecimal amount, String currency) { - super(amount.unscaledValue(), amount.scale()); - this.currency = currency; - } - - public InternalAmount(BigInteger amount, String currency) { - super(new BigDecimal(amount).unscaledValue(), new BigDecimal(amount).scale()); - this.currency = currency; - } - - public boolean notZero() { - return compareTo(BigDecimal.ZERO) > 0; - } - - public boolean isZero() { - return compareTo(BigDecimal.ZERO) == 0; - } - - public String getCurrency() { - return currency; - } - - public String toValueString(int decimals) { - DecimalFormatSymbols symbols = new DecimalFormatSymbols(Locale.US); - symbols.setDecimalSeparator('.'); - DecimalFormat df = new DecimalFormat(); - df.setDecimalFormatSymbols(symbols); - df.setMaximumFractionDigits(decimals); - - df.setMinimumFractionDigits(0); - - df.setGroupingUsed(false); - - BigDecimal bd = new BigDecimal(unscaledValue(), scale()); - bd.setScale(decimals, ROUND_DOWN); - return df.format(bd); - } - - public String toValueString() { - return toValueString(scale()); - } - - @Override - public String toString() { - return super.toString(); - } - - } - - public static class Amount extends BigDecimal { - private String currency; - - public Amount() { - super(0); - currency = ""; - } - - public Amount(String amountString, String currency) { - super(amountString.replace(',', '.')); - this.currency = currency; - } - - public Amount(Long amount, String currency) { - super(amount); - this.currency = currency; - } - - public Amount(BigDecimal amount, String currency) { - super(amount.unscaledValue(), amount.scale()); - this.currency = currency; - } - - public String getCurrency() { - return currency; - } - - @Override - public String toString() { - return super.toString() + " " + currency; - } - - public boolean notZero() { - return compareTo(BigDecimal.ZERO) > 0; - } - - public String toDescriptionString(int decimals) { - return toValueString(decimals) + " " + currency; - } - - public String toValueString(int decimals) { - DecimalFormatSymbols symbols = new DecimalFormatSymbols(Locale.US); - symbols.setDecimalSeparator('.'); - DecimalFormat df = new DecimalFormat(); - df.setDecimalFormatSymbols(symbols); - df.setMaximumFractionDigits(decimals); - - df.setMinimumFractionDigits(0); - - df.setGroupingUsed(false); - - BigDecimal bd = new BigDecimal(unscaledValue(), scale()); - bd.setScale(decimals, ROUND_DOWN); - return df.format(bd); - } - - public String toValueString() { - return toValueString(scale()); - } - - public String toEquivalentString(double rateValue) { - if (rateValue > 0) { - BigDecimal biRate = new BigDecimal(rateValue); - BigDecimal exchangeCurs = biRate.multiply(this); - exchangeCurs = exchangeCurs.setScale(2, RoundingMode.DOWN); - return "≈ USD  " + exchangeCurs.toString(); - } else { - return ""; - } - } - - public boolean isZero() { - return compareTo(BigDecimal.ZERO) == 0; - } - - @Override - public Amount setScale(int newScale) { - return new Amount(super.setScale(newScale), currency); - } - } - - protected TangemContext ctx; - - public CoinEngine() { - - } - - public CoinEngine(TangemContext ctx) { - this.ctx = ctx; - } - - public abstract boolean awaitingConfirmation(); - - public abstract boolean hasBalanceInfo(); - - public abstract boolean isBalanceNotZero(); - - // TODO - return string message - public abstract boolean isExtractPossible(); - - public abstract Uri getWalletExplorerUri(); - - public abstract Uri getShareWalletUri(); - - public Uri getShareWalletUriEx(){ - String scheme = ctx.getBlockchain().getUriScheme(); - if (scheme != null) - return Uri.parse(scheme + ":" + getShareWalletUri()); - else - return getShareWalletUri(); - } - - public abstract boolean checkNewTransactionAmount(Amount amount); - - public abstract boolean checkNewTransactionAmountAndFee(Amount amount, Amount fee, Boolean isFeeIncluded); - - public abstract boolean validateBalance(BalanceValidator balanceValidator); - - public abstract Amount getBalance(); - - public abstract String getBalanceHTML(); - - public abstract String getBalanceCurrency(); - - public abstract InputFilter[] getAmountInputFilters(); - - public abstract String evaluateFeeEquivalent(String fee); - - public abstract String getFeeCurrency(); - - public abstract boolean isNeedCheckNode(); - - public abstract String getBalanceEquivalent(); - - public abstract boolean validateAddress(String address); - - public abstract String calculateAddress(byte[] pkUncompressed) throws NoSuchProviderException, NoSuchAlgorithmException, CborException, IOException, Exception; - - public abstract Amount convertToAmount(InternalAmount internalAmount) throws Exception; - - public abstract Amount convertToAmount(String strAmount, String currency); - - public abstract InternalAmount convertToInternalAmount(Amount amount) throws Exception; - - public abstract InternalAmount convertToInternalAmount(byte[] bytes) throws Exception; - - public abstract byte[] convertToByteArray(InternalAmount internalAmount) throws Exception; - - public abstract CoinData createCoinData(); - - public abstract String getUnspentInputsDescription(); - - public String getOfflineBalanceHTML() { - return ""; - } - -// public String getOfflineBalanceHTML() { -// InternalAmount offlineInternalAmount = convertToInternalAmount(ctx.getCard().getOfflineBalance()); -// Amount offlineAmount = convertToAmount(offlineInternalAmount); -// return offlineAmount.toDescriptionString(getDecimals()); -// } - - public void defineWallet() throws CardProtocol.TangemException { - try { - String wallet = calculateAddress(ctx.getCard().getWalletPublicKey()); - ctx.getCoinData().setWallet(wallet); - } catch (Exception e) { - ctx.getCoinData().setWallet("ERROR"); - throw new CardProtocol.TangemException("Can't define wallet address"); - } - - } - - /** - * Create instance of {@link SignTask.TransactionToSign} used for transaction signing and sending - * - * Transaction processing sequence: - * 1. User enter transaction attributes - * 2. Application create instance of {@link SignTask.TransactionToSign} by call {@see constructTransaction} - * 3. Application set notification when transaction were prepared {@see setOnNeedSendTransaction} and init {@link SignTask} - * 4. User tap card and card sign transaction - * 5. Application receive {@link OnNeedSendTransaction} notification with prepared raw transaction - * 6. Application show user information that transaction ready for sending and init sending procedure by call {@see requestSendTransaction} - * 7. Application receive notification of sending result through {@link CoinEngine.BlockchainRequestsCallbacks} and show result to user - * - * @param amountValue - amount of desired transaction - * @param feeValue - fee amount of desired transaction - * @param IncFee - true if fee amount is included in amountValue (amountValue is total amount of transaction) - * @param targetAddress - target address of transaction - * @return instance of {@link SignTask.TransactionToSign} - * @throws Exception if something goes wrong - */ - public abstract SignTask.TransactionToSign constructTransaction(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) throws Exception; - - /** - * Interface used to notify main application when new transaction is prepared to send - */ - public interface OnNeedSendTransaction { - void onTransactionPrepared(byte[] txForSend); - } - - protected OnNeedSendTransaction onNeedSendTransaction; - - - /** - * Set notification callback when new transaction is prepared to send - */ - public void setOnNeedSendTransaction(OnNeedSendTransaction onNeedSendTransaction) { - this.onNeedSendTransaction = onNeedSendTransaction; - } - - protected void notifyOnNeedSendTransaction(byte[] txForSend) throws Exception { - if (onNeedSendTransaction == null) - throw new Exception("Transaction was signed but no callback defined to send!"); - onNeedSendTransaction.onTransactionPrepared(txForSend); - } - - /** - * Interface used to notify/querying application during processing sequence of request to blockchain nodes/servers - */ - public interface BlockchainRequestsCallbacks { - /** - * Notification that the all requests in sequence completed - * Call after a last request completed - * If occurred error return in {@link TangemContext} {@see TangemContext.getError()} - * - * @param success -* - */ - void onComplete(Boolean success); - - /** - * Notification that a new part of data received and it's possible to update view - * May call when some request in the sequence completed but there are still a few requests left - */ - void onProgress(); - - /** - * Return flag that allow to add new or re-requests in the sequence - * Call between requests or when request fail and before re-request - * - * @return true if not need terminate (e.g. activity is online) - */ - boolean allowAdvance(); - } - - /** - * Start sequence of request to blockchain nodes needed to get balance and other information (for example unspent transaction) needed to - * show current state of wallet and prepare new withdrawal transaction - * Save result in {@link CoinData} - * If occurred error can be get at onComplete callback in {@link TangemContext}.getError() - * @param blockchainRequestsCallbacks - notifications - * @throws Exception if something goes wrong - */ - public abstract void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) throws Exception; - - /** - * Start sequence of request to blockchain nodes needed to get fee amount for a new transaction - * Save result in {@link CoinData} minFee, maxFee, normalFee - * @param blockchainRequestsCallbacks - notifications - * @throws Exception if something goes wrong - */ - public abstract void requestFee(BlockchainRequestsCallbacks blockchainRequestsCallbacks, String targetAddress, Amount amount) throws Exception; - - /** - * Start sequence of request to blockchain nodes needed to send new transaction - * If occurred error can be get at onComplete callback in {@link TangemContext}.getError() - * @param blockchainRequestsCallbacks - notifications - * @throws Exception if something goes wrong - */ - public abstract void requestSendTransaction(BlockchainRequestsCallbacks blockchainRequestsCallbacks, byte[] txForSend) throws Exception; - - - /** - * @return true if blockchain need multiple lines to show balance (e.g. need show additional balance information, Token count for example) - */ - public boolean needMultipleLinesForBalance() { - return false; - } - - /** - * @return true to allow user select fee level - min, normal or priority - */ - public boolean allowSelectFeeLevel() { - return true; - } - - /** - * @return true to allow user select include or exclude fee - */ - public boolean allowSelectFeeInclusion() { - return true; - } - - public boolean isNftToken() { - return false; - } - - - public int pendingTransactionTimeoutInSeconds() { return 30; } - - protected boolean validatePayId(String payId) { - String[] addressParts = payId.split("\\$"); - - if (addressParts.length != 2) { - return false; - } - String addressURL = "https://" + addressParts[1] + "/" + addressParts[0]; - try { - new URL(addressURL).toURI(); - return true; - } catch (Exception e) { - return false; - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/CoinEngineFactory.kt b/app/src/main/java/com/tangem/wallet/CoinEngineFactory.kt deleted file mode 100644 index c1829aa910..0000000000 --- a/app/src/main/java/com/tangem/wallet/CoinEngineFactory.kt +++ /dev/null @@ -1,154 +0,0 @@ -package com.tangem.wallet - -import android.util.Log -import com.tangem.data.Blockchain -import com.tangem.wallet.bch.BtcCashEngine -import com.tangem.wallet.binance.BinanceAssetEngine -import com.tangem.wallet.binance.BinanceEngine -import com.tangem.wallet.btc.BtcEngine -import com.tangem.wallet.btcmultisig.BtcMultisigEngine -import com.tangem.wallet.cardano.CardanoData -import com.tangem.wallet.cardano.CardanoEngine -import com.tangem.wallet.cardano.CardanoShelleyEngine -import com.tangem.wallet.ducatus.DucatusEngine -import com.tangem.wallet.eos.EosEngine -import com.tangem.wallet.eth.EthEngine -import com.tangem.wallet.ethID.EthIdEngine -import com.tangem.wallet.flowDemo.FlowDemoEngine -import com.tangem.wallet.ltc.LtcEngine -import com.tangem.wallet.matic.MaticTokenEngine -import com.tangem.wallet.nftToken.NftTokenEngine -import com.tangem.wallet.rsk.RskEngine -import com.tangem.wallet.rsk.RskTokenEngine -import com.tangem.wallet.token.TokenEngine -import com.tangem.wallet.tezos.TezosEngine -import com.tangem.wallet.tokenEmv.TokenEmvEngine -import com.tangem.wallet.xlm.XlmAssetEngine -import com.tangem.wallet.xlm.XlmEngine -import com.tangem.wallet.xlmTag.XlmTagEngine -import com.tangem.wallet.xrp.XrpEngine - -/** - * Factory for create specific engine - * - * @param - * Blockchain - * @param TangemContext - * - */ - -object CoinEngineFactory { - private val TAG = CoinEngineFactory::class.java.simpleName - - fun create(blockchain: Blockchain): CoinEngine? { - return when (blockchain) { - Blockchain.Bitcoin, Blockchain.BitcoinTestNet -> BtcEngine() - Blockchain.BitcoinCash -> BtcCashEngine() - Blockchain.Ethereum, Blockchain.EthereumTestNet -> EthEngine() - Blockchain.EthereumId -> EthIdEngine() - Blockchain.Token -> TokenEngine() - Blockchain.NftToken -> NftTokenEngine() - Blockchain.Litecoin -> LtcEngine() - Blockchain.Rootstock -> RskEngine() - Blockchain.RootstockToken -> RskTokenEngine() - Blockchain.Cardano -> CardanoEngine() - Blockchain.CardanoShelley -> CardanoShelleyEngine() - Blockchain.Ripple -> XrpEngine() - Blockchain.Binance, Blockchain.BinanceTestNet -> BinanceEngine() - Blockchain.BinanceAsset -> BinanceAssetEngine() - Blockchain.Matic, Blockchain.MaticTestNet -> MaticTokenEngine() - Blockchain.StellarTestNet, Blockchain.Stellar -> XlmEngine() - Blockchain.StellarAsset -> XlmAssetEngine() - Blockchain.StellarTag -> XlmTagEngine() - Blockchain.Eos -> EosEngine() - Blockchain.Ducatus -> DucatusEngine() - Blockchain.Tezos -> TezosEngine() - Blockchain.BitcoinDual -> BtcMultisigEngine() - Blockchain.FlowDemo -> FlowDemoEngine() - Blockchain.TokenEmv -> TokenEmvEngine() - else -> null - } - } - - fun create(context: TangemContext): CoinEngine? { - var result: CoinEngine? - try { - result = if (Blockchain.BitcoinCash == context.blockchain) - BtcCashEngine(context) - else if (Blockchain.Bitcoin == context.blockchain || Blockchain.BitcoinTestNet == context.blockchain) - BtcEngine(context) - else if (Blockchain.Ethereum == context.blockchain || Blockchain.EthereumTestNet == context.blockchain) - EthEngine(context) - else if (Blockchain.EthereumId == context.blockchain) - EthIdEngine(context) - else if (Blockchain.Token == context.blockchain) - TokenEngine(context) - else if (Blockchain.NftToken == context.blockchain) - NftTokenEngine(context) - else if (Blockchain.Litecoin == context.blockchain) - LtcEngine(context) - else if (Blockchain.Rootstock == context.blockchain) - RskEngine(context) - else if (Blockchain.RootstockToken == context.blockchain) - RskTokenEngine(context) - else if (Blockchain.Cardano == context.blockchain) - CardanoEngine(context) - else if (Blockchain.CardanoShelley == context.blockchain) - CardanoShelleyEngine(context) - else if (Blockchain.Ripple == context.blockchain) - XrpEngine(context) - else if (Blockchain.Binance == context.blockchain || Blockchain.BinanceTestNet == context.blockchain) - BinanceEngine(context) - else if (Blockchain.BinanceAsset == context.blockchain) - BinanceAssetEngine(context) - else if (Blockchain.Matic == context.blockchain || Blockchain.MaticTestNet == context.blockchain) - MaticTokenEngine(context) - else if (Blockchain.Stellar == context.blockchain || Blockchain.StellarTestNet == context.blockchain) - XlmEngine(context) - else if (Blockchain.StellarAsset == context.blockchain) - XlmAssetEngine(context) - else if (Blockchain.StellarTag == context.blockchain) - XlmTagEngine(context) - else if (Blockchain.Eos == context.blockchain) - EosEngine(context) - else if (Blockchain.Ducatus == context.blockchain) - DucatusEngine(context) - else if (Blockchain.Tezos == context.blockchain) - TezosEngine(context) - else if (Blockchain.BitcoinDual == context.blockchain) - BtcMultisigEngine(context) - else if (Blockchain.FlowDemo == context.blockchain) - FlowDemoEngine(context) - else if (Blockchain.TokenEmv == context.blockchain) - TokenEmvEngine(context) - else - return null - } catch (e: Exception) { - e.printStackTrace() - result = null - Log.e(TAG, "Can't create CoinEngine!") - } - return result - } - - fun createCardano(context: TangemContext): CoinEngine? { - var result: CoinEngine? - try { - result = CardanoEngine(context)//EthEngine(context)// - - } catch (e: Exception) { - e.printStackTrace() - result = null - Log.e(TAG, "Can't create Cardano CoinEngine!") - } - return result - } - - fun createCardanoData(): CoinData? { - return CardanoData()//EthData()// CardanoData() - } - - fun isCardano(blockchainID: String): Boolean { - return blockchainID==Blockchain.Cardano.id//Ethereum.id - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/Digest.java b/app/src/main/java/com/tangem/wallet/Digest.java deleted file mode 100644 index b3924d61c7..0000000000 --- a/app/src/main/java/com/tangem/wallet/Digest.java +++ /dev/null @@ -1,114 +0,0 @@ -package com.tangem.wallet; - -/** - * Created by Ilia on 18.12.2017. - */ - -public interface Digest{ - - /** - * Insert one more input data byte. - * - * @param in the input byte - */ - void update(byte in); - - /** - * Insert some more bytes. - * - * @param inbuf the data bytes - */ - void update(byte[] inbuf); - - /** - * Insert some more bytes. - * - * @param inbuf the data buffer - * @param off the data offset in {@code inbuf} - * @param len the data length (in bytes) - */ - void update(byte[] inbuf, int off, int len); - - /** - * Finalize the current hash computation and return the hash value - * in a newly-allocated array. The object is resetted. - * - * @return the hash output - */ - byte[] digest(); - - /** - * Input some bytes, then finalize the current hash computation - * and return the hash value in a newly-allocated array. The object - * is resetted. - * - * @param inbuf the input data - * @return the hash output - */ - byte[] digest(byte[] inbuf); - - /** - * Finalize the current hash computation and store the hash value - * in the provided output buffer. The {@code len} parameter - * contains the maximum number of bytes that should be written; - * no more bytes than the natural hash function output length will - * be produced. If {@code len} is smaller than the natural - * hash output length, the hash output is truncated to its first - * {@code len} bytes. The object is resetted. - * - * @param outbuf the output buffer - * @param off the output offset within {@code outbuf} - * @param len the requested hash output length (in bytes) - * @return the number of bytes actually written in {@code outbuf} - */ - int digest(byte[] outbuf, int off, int len); - - /** - * Get the natural hash function output length (in bytes). - * - * @return the digest output length (in bytes) - */ - int getDigestLength(); - - /** - * Reset the object: this makes it suitable for a new hash - * computation. The current computation, if any, is discarded. - */ - void reset(); - - /** - * Clone the current state. The returned object evolves independantly - * of this object. - * - * @return the clone - */ - Digest copy(); - - /** - *

Return the "block length" for the hash function. This - * value is naturally defined for iterated hash functions - * (Merkle-Damgard). It is used in HMAC (that's what the - * HMAC specification - * names the "{@code B}" parameter).

- * - *

If the function is "block-less" then this function may - * return {@code -n} where {@code n} is an integer such that the - * block length for HMAC ("{@code B}") will be inferred from the - * key length, by selecting the smallest multiple of {@code n} - * which is no smaller than the key length. For instance, for - * the Fugue-xxx hash functions, this function returns -4: the - * virtual block length B is the HMAC key length, rounded up to - * the next multiple of 4.

- * - * @return the internal block length (in bytes), or {@code -n} - */ - int getBlockLength(); - - /** - *

Get the display name for this function (e.g. {@code "SHA-1"} - * for SHA-1).

- * - * @see Object - */ - String toString(); -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/DigestEngine.java b/app/src/main/java/com/tangem/wallet/DigestEngine.java deleted file mode 100644 index 8dd130353d..0000000000 --- a/app/src/main/java/com/tangem/wallet/DigestEngine.java +++ /dev/null @@ -1,220 +0,0 @@ -package com.tangem.wallet; - - -import java.security.MessageDigest; - -/** - * Created by Ilia on 18.12.2017. - */ - -public abstract class DigestEngine extends MessageDigest implements Digest { - - /** - * Reset the hash algorithm state. - */ - protected abstract void engineReset(); - - /** - * Process one block of data. - * - * @param data the data block - */ - protected abstract void processBlock(byte[] data); - - /** - * Perform the final padding and store the result in the - * provided buffer. This method shall call {@link #flush} - * and then {@link #update} with the appropriate padding - * data in order to get the full input data. - * - * @param buf the output buffer - * @param off the output offset - */ - protected abstract void doPadding(byte[] buf, int off); - - /** - * This function is called at object creation time; the - * implementation should use it to perform initialization tasks. - * After this method is called, the implementation should be ready - * to process data or meaningfully honour calls such as - * {@link #engineGetDigestLength} - */ - protected abstract void doInit(); - - private int digestLen, blockLen, inputLen; - private byte[] inputBuf, outputBuf; - private long blockCount; - - /** - * Instantiate the engine. - */ - public DigestEngine(String alg) - { - super(alg); - doInit(); - digestLen = engineGetDigestLength(); - blockLen = getInternalBlockLength(); - inputBuf = new byte[blockLen]; - outputBuf = new byte[digestLen]; - inputLen = 0; - blockCount = 0; - } - - private void adjustDigestLen() - { - if (digestLen == 0) { - digestLen = engineGetDigestLength(); - outputBuf = new byte[digestLen]; - } - } - - public byte[] digest() - { - adjustDigestLen(); - byte[] result = new byte[digestLen]; - digest(result, 0, digestLen); - return result; - } - - public byte[] digest(byte[] input) - { - update(input, 0, input.length); - return digest(); - } - - public int digest(byte[] buf, int offset, int len) - { - adjustDigestLen(); - if (len >= digestLen) { - doPadding(buf, offset); - reset(); - return digestLen; - } else { - doPadding(outputBuf, 0); - System.arraycopy(outputBuf, 0, buf, offset, len); - reset(); - return len; - } - } - - public void reset() - { - engineReset(); - inputLen = 0; - blockCount = 0; - } - - public void update(byte input) - { - inputBuf[inputLen ++] = (byte)input; - if (inputLen == blockLen) { - processBlock(inputBuf); - blockCount ++; - inputLen = 0; - } - } - - public void update(byte[] input) - { - update(input, 0, input.length); - } - - public void update(byte[] input, int offset, int len) - { - while (len > 0) { - int copyLen = blockLen - inputLen; - if (copyLen > len) - copyLen = len; - System.arraycopy(input, offset, inputBuf, inputLen, - copyLen); - offset += copyLen; - inputLen += copyLen; - len -= copyLen; - if (inputLen == blockLen) { - processBlock(inputBuf); - blockCount ++; - inputLen = 0; - } - } - } - - /** - * Get the internal block length. This is the length (in - * bytes) of the array which will be passed as parameter to - * {@link #processBlock}. The default implementation of this - * method calls {@link #getBlockLength} and returns the same - * value. Overriding this method is useful when the advertised - * block length (which is used, for instance, by HMAC) is - * suboptimal with regards to internal buffering needs. - * - * @return the internal block length (in bytes) - */ - protected int getInternalBlockLength() - { - return getBlockLength(); - } - - /** - * Flush internal buffers, so that less than a block of data - * may at most be upheld. - * - * @return the number of bytes still unprocessed after the flush - */ - protected final int flush() - { - return inputLen; - } - - /** - * Get a reference to an internal buffer with the same size - * than a block. The contents of that buffer are defined only - * immediately after a call to {@link #flush()}: if - * {@link #flush()} return the value {@code n}, then the - * first {@code n} bytes of the array returned by this method - * are the {@code n} bytes of input data which are still - * unprocessed. The values of the remaining bytes are - * undefined and may be altered at will. - * - * @return a block-sized internal buffer - */ - protected final byte[] getBlockBuffer() - { - return inputBuf; - } - - /** - * Get the "block count": this is the number of times the - * {@link #processBlock} method has been invoked for the - * current hash operation. That counter is incremented - * after the call to {@link #processBlock}. - * - * @return the block count - */ - protected long getBlockCount() - { - return blockCount; - } - - /** - * This function copies the internal buffering state to some - * other instance of a class extending {@code DigestEngine}. - * It returns a reference to the copy. This method is intended - * to be called by the implementation of the {@link #copy} - * method. - * - * @param dest the copy - * @return the value {@code dest} - */ - protected Digest copyState(DigestEngine dest) - { - dest.inputLen = inputLen; - dest.blockCount = blockCount; - System.arraycopy(inputBuf, 0, dest.inputBuf, 0, - inputBuf.length); - adjustDigestLen(); - dest.adjustDigestLen(); - System.arraycopy(outputBuf, 0, dest.outputBuf, 0, - outputBuf.length); - return dest; - } -} diff --git a/app/src/main/java/com/tangem/wallet/ECDSASignatureETH.java b/app/src/main/java/com/tangem/wallet/ECDSASignatureETH.java deleted file mode 100644 index a4b9741a1d..0000000000 --- a/app/src/main/java/com/tangem/wallet/ECDSASignatureETH.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.tangem.wallet; - -import java.math.BigInteger; - -/** - * Created by Ilia on 07.01.2018. - */ - -public class ECDSASignatureETH { - /** - * The two components of the signature. - */ - public final BigInteger r, s; - public byte v; - - /** - * Constructs a signature with the given components. Does NOT automatically canonicalise the signature. - * - * @param r - - * @param s - - */ - public ECDSASignatureETH(BigInteger r, BigInteger s) { - this.r = r; - this.s = s; - } - - /** - *t - * @param r - * @param s - * @return - - */ - private static ECDSASignatureETH fromComponents(byte[] r, byte[] s) { - return new ECDSASignatureETH(new BigInteger(1, r), new BigInteger(1, s)); - } - - /** - * - * @param r - - * @param s - - * @param v - - * @return - - */ - public static ECDSASignatureETH fromComponents(byte[] r, byte[] s, byte v) { - ECDSASignatureETH signature = fromComponents(r, s); - signature.v = v; - return signature; - } - - -} - diff --git a/app/src/main/java/com/tangem/wallet/EthTransaction.java b/app/src/main/java/com/tangem/wallet/EthTransaction.java deleted file mode 100644 index 48e86ecf6a..0000000000 --- a/app/src/main/java/com/tangem/wallet/EthTransaction.java +++ /dev/null @@ -1,225 +0,0 @@ -package com.tangem.wallet; - -import android.util.Log; - -import com.tangem.util.ByteUtil; -import com.tangem.util.CryptoUtil; - -import org.bitcoinj.core.ECKey; -import org.bitcoinj.core.Sha256Hash; -import org.spongycastle.util.BigIntegers; -import org.spongycastle.util.encoders.Hex; - -import java.math.BigInteger; -import java.util.Arrays; - -import static com.tangem.util.ByteUtil.EMPTY_BYTE_ARRAY; - -/** - * Created by Ilia on 07.01.2018. - */ - -public class EthTransaction { - byte[] nonce; - byte[] gasPrice; - byte[] gasLimit; - byte[] receiveAddress; - byte[] value; - byte[] data; - Integer chainId; - byte[] rlpRaw; - public ECDSASignatureETH signature; - byte[] rlpEncoded; - - private static final int CHAIN_ID_INC = 35; - private static final int LOWER_REAL_V = 27; - - public static EthTransaction create(String to, BigInteger amount, BigInteger nonce, BigInteger gasPrice, BigInteger gasLimit, Integer chainId) { - return new EthTransaction(BigIntegers.asUnsignedByteArray(nonce), - BigIntegers.asUnsignedByteArray(gasPrice), - BigIntegers.asUnsignedByteArray(gasLimit), - Hex.decode(to), - BigIntegers.asUnsignedByteArray(amount), - null, - chainId); - } - - public static EthTransaction create(String to, BigInteger amount, BigInteger nonce, BigInteger gasPrice, BigInteger gasLimit, Integer chainId, byte[] data) { - return new EthTransaction(BigIntegers.asUnsignedByteArray(nonce), - BigIntegers.asUnsignedByteArray(gasPrice), - BigIntegers.asUnsignedByteArray(gasLimit), - Hex.decode(to), - BigIntegers.asUnsignedByteArray(amount), - data, - chainId); - } - - public EthTransaction(byte[] nonce, byte[] gasPrice, byte[] gasLimit, byte[] receiveAddress, byte[] value, byte[] data, Integer chainId) { - this.nonce = nonce; - this.gasPrice = gasPrice; - this.gasLimit = gasLimit; - this.receiveAddress = receiveAddress; - if (ByteUtil.isSingleZero(value)) { - this.value = EMPTY_BYTE_ARRAY; - } else { - this.value = value; - } - this.data = data; - this.chainId = chainId; - - if (receiveAddress == null) { - this.receiveAddress = ByteUtil.EMPTY_BYTE_ARRAY; - } - } - - public enum ChainEnum { - Mainnet(1), - Morden(2), - Ropsten(3), - Rinkeby(4), - Rootstock_mainnet(30), - Rootstock_testnet(31), - Kovan(42), - Ethereum_Classic_mainnet(61), - Ethereum_Classic_testnet(62), - Geth_private_chains(1337), - Matic_Testnet(8995); - - private int value; - - ChainEnum(int value) { - this.value = value; - } - - public int getValue() { - return value; - } - } - - public byte[] getRawHash() { - - byte[] plainMsg = this.getEncodedRaw(); - Keccak256 kec = new Keccak256(); - return kec.digest(plainMsg); - } - - public byte[] getHash() { - - byte[] plainMsg = this.getEncoded(); - Keccak256 kec = new Keccak256(); - return kec.digest(plainMsg); - } - - public static int BruteRecoveryID2(ECDSASignatureETH sig, byte[] messageHash, byte[] thisKey) { - Log.e("ETH_KZ", BTCUtils.toHex(thisKey)); - int recId = -1; - for (int i = 0; i < 4; i++) { - byte[] recK = CryptoUtil.recoverPubBytesFromSignature(i, sig, messageHash); - - if (recK == null) { - continue; - } - - Log.e("ETH_k " + String.valueOf(i), BTCUtils.toHex(recK)); - if (Arrays.equals(recK, thisKey)) { - recId = i; - recId += 27; - break; - } - } - return recId; - } - - public int BruteRecoveryID(ECKey.ECDSASignature sig, Sha256Hash messageHash, byte[] thisKey) { - Log.e("ETH_KZ", BTCUtils.toHex(thisKey)); - int recId = -1; - for (int i = 0; i < 4; i++) { - ECKey k = ECKey.recoverFromSignature(i, sig, messageHash, false); - - if (k == null) - continue; - byte[] recK = k.getPubKey(); - Log.e("ETH_k " + String.valueOf(i), BTCUtils.toHex(recK)); - if (k != null && Arrays.equals(recK, thisKey)) { - recId = i; - break; - } - } - return recId; - } - - // signed TX - public byte[] getEncoded() { - - // parse null as 0 for nonce - byte[] nonce = null; - if (this.nonce == null || this.nonce.length == 1 && this.nonce[0] == 0) { - nonce = RLP.encodeElement(null); - } else { - nonce = RLP.encodeElement(this.nonce); - } - byte[] gasPrice = RLP.encodeElement(this.gasPrice); - byte[] gasLimit = RLP.encodeElement(this.gasLimit); - byte[] receiveAddress = RLP.encodeElement(this.receiveAddress); - byte[] value = RLP.encodeElement(this.value); - byte[] data = RLP.encodeElement(this.data); - - byte[] v, r, s; - - if (signature != null) { - int encodeV; - if (chainId == null) { - encodeV = signature.v; - } else { - encodeV = signature.v - LOWER_REAL_V; - encodeV += chainId * 2 + CHAIN_ID_INC; - } - v = RLP.encodeInt(encodeV); - r = RLP.encodeElement(BigIntegers.asUnsignedByteArray(signature.r)); - s = RLP.encodeElement(BigIntegers.asUnsignedByteArray(signature.s)); - } else { - // Since EIP-155 use chainId for v - v = chainId == null ? RLP.encodeElement(EMPTY_BYTE_ARRAY) : RLP.encodeInt(chainId); - r = RLP.encodeElement(EMPTY_BYTE_ARRAY); - s = RLP.encodeElement(EMPTY_BYTE_ARRAY); - } - - this.rlpEncoded = RLP.encodeList(nonce, gasPrice, gasLimit, - receiveAddress, value, data, v, r, s); - - //this.hash = this.getHash(); - - return rlpEncoded; - } - - // unsigned TX - public byte[] getEncodedRaw() { - // parse null as 0 for nonce - byte[] nonce = null; - if (this.nonce == null || this.nonce.length == 1 && this.nonce[0] == 0) { - nonce = RLP.encodeElement(null); - } else { - nonce = RLP.encodeElement(this.nonce); - } - byte[] gasPrice = RLP.encodeElement(this.gasPrice); - byte[] gasLimit = RLP.encodeElement(this.gasLimit); - byte[] receiveAddress = RLP.encodeElement(this.receiveAddress); - byte[] value = RLP.encodeElement(this.value); - byte[] data = RLP.encodeElement(this.data); - - // Since EIP-155 use chainId for v - if (chainId == null) { - rlpRaw = RLP.encodeList(nonce, gasPrice, gasLimit, receiveAddress, - value, data); - } else { - byte[] v, r, s; - v = RLP.encodeInt(chainId); - r = RLP.encodeElement(EMPTY_BYTE_ARRAY); - s = RLP.encodeElement(EMPTY_BYTE_ARRAY); - rlpRaw = RLP.encodeList(nonce, gasPrice, gasLimit, receiveAddress, - value, data, v, r, s); - } - return rlpRaw; - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/Keccak256.java b/app/src/main/java/com/tangem/wallet/Keccak256.java deleted file mode 100644 index bd8c2b5ded..0000000000 --- a/app/src/main/java/com/tangem/wallet/Keccak256.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.tangem.wallet; - -/** - * Created by Ilia on 18.12.2017. - */ - -public class Keccak256 extends KeccakCore { - - /** - * Create the engine. - */ - public Keccak256() - { - super("eth-keccak-256"); - } - - public Digest copy() - { - return copyState(new Keccak256()); - } - - public int engineGetDigestLength() - { - return 32; - } - - @Override - protected byte[] engineDigest() { - return null; - } - - @Override - protected void engineUpdate(byte arg0) { - } - - @Override - protected void engineUpdate(byte[] arg0, int arg1, int arg2) { - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/KeccakCore.java b/app/src/main/java/com/tangem/wallet/KeccakCore.java deleted file mode 100644 index 015b33bb49..0000000000 --- a/app/src/main/java/com/tangem/wallet/KeccakCore.java +++ /dev/null @@ -1,546 +0,0 @@ -package com.tangem.wallet; - - -/** - * Created by Ilia on 18.12.2017. - */ -abstract class KeccakCore extends DigestEngine { - KeccakCore(String alg) - { - super(alg); - } - - private long[] A; - private byte[] tmpOut; - - private static final long[] RC = { - 0x0000000000000001L, 0x0000000000008082L, - 0x800000000000808AL, 0x8000000080008000L, - 0x000000000000808BL, 0x0000000080000001L, - 0x8000000080008081L, 0x8000000000008009L, - 0x000000000000008AL, 0x0000000000000088L, - 0x0000000080008009L, 0x000000008000000AL, - 0x000000008000808BL, 0x800000000000008BL, - 0x8000000000008089L, 0x8000000000008003L, - 0x8000000000008002L, 0x8000000000000080L, - 0x000000000000800AL, 0x800000008000000AL, - 0x8000000080008081L, 0x8000000000008080L, - 0x0000000080000001L, 0x8000000080008008L - }; - - /** - * Encode the 64-bit word {@code val} into the array - * {@code buf} at offset {@code off}, in little-endian - * convention (least significant byte first). - * - * @param val the value to encode - * @param buf the destination buffer - * @param off the destination offset - */ - private static void encodeLELong(long val, byte[] buf, int off) - { - buf[off + 0] = (byte)val; - buf[off + 1] = (byte)(val >>> 8); - buf[off + 2] = (byte)(val >>> 16); - buf[off + 3] = (byte)(val >>> 24); - buf[off + 4] = (byte)(val >>> 32); - buf[off + 5] = (byte)(val >>> 40); - buf[off + 6] = (byte)(val >>> 48); - buf[off + 7] = (byte)(val >>> 56); - } - - /** - * Decode a 64-bit little-endian word from the array {@code buf} - * at offset {@code off}. - * - * @param buf the source buffer - * @param off the source offset - * @return the decoded value - */ - private static long decodeLELong(byte[] buf, int off) - { - return (buf[off + 0] & 0xFFL) - | ((buf[off + 1] & 0xFFL) << 8) - | ((buf[off + 2] & 0xFFL) << 16) - | ((buf[off + 3] & 0xFFL) << 24) - | ((buf[off + 4] & 0xFFL) << 32) - | ((buf[off + 5] & 0xFFL) << 40) - | ((buf[off + 6] & 0xFFL) << 48) - | ((buf[off + 7] & 0xFFL) << 56); - } - - protected void engineReset() - { - doReset(); - } - - protected void processBlock(byte[] data) - { - /* Input block */ - for (int i = 0; i < data.length; i += 8) - A[i >>> 3] ^= decodeLELong(data, i); - - long t0, t1, t2, t3, t4; - long tt0, tt1, tt2, tt3, tt4; - long t, kt; - long c0, c1, c2, c3, c4, bnn; - - /* - * Unrolling four rounds kills performance big time - * on Intel x86 Core2, in both 32-bit and 64-bit modes - * (less than 1 MB/s instead of 55 MB/s on x86-64). - * Unrolling two rounds appears to be fine. - */ - for (int j = 0; j < 24; j += 2) { - - tt0 = A[ 1] ^ A[ 6]; - tt1 = A[11] ^ A[16]; - tt0 ^= A[21] ^ tt1; - tt0 = (tt0 << 1) | (tt0 >>> 63); - tt2 = A[ 4] ^ A[ 9]; - tt3 = A[14] ^ A[19]; - tt0 ^= A[24]; - tt2 ^= tt3; - t0 = tt0 ^ tt2; - - tt0 = A[ 2] ^ A[ 7]; - tt1 = A[12] ^ A[17]; - tt0 ^= A[22] ^ tt1; - tt0 = (tt0 << 1) | (tt0 >>> 63); - tt2 = A[ 0] ^ A[ 5]; - tt3 = A[10] ^ A[15]; - tt0 ^= A[20]; - tt2 ^= tt3; - t1 = tt0 ^ tt2; - - tt0 = A[ 3] ^ A[ 8]; - tt1 = A[13] ^ A[18]; - tt0 ^= A[23] ^ tt1; - tt0 = (tt0 << 1) | (tt0 >>> 63); - tt2 = A[ 1] ^ A[ 6]; - tt3 = A[11] ^ A[16]; - tt0 ^= A[21]; - tt2 ^= tt3; - t2 = tt0 ^ tt2; - - tt0 = A[ 4] ^ A[ 9]; - tt1 = A[14] ^ A[19]; - tt0 ^= A[24] ^ tt1; - tt0 = (tt0 << 1) | (tt0 >>> 63); - tt2 = A[ 2] ^ A[ 7]; - tt3 = A[12] ^ A[17]; - tt0 ^= A[22]; - tt2 ^= tt3; - t3 = tt0 ^ tt2; - - tt0 = A[ 0] ^ A[ 5]; - tt1 = A[10] ^ A[15]; - tt0 ^= A[20] ^ tt1; - tt0 = (tt0 << 1) | (tt0 >>> 63); - tt2 = A[ 3] ^ A[ 8]; - tt3 = A[13] ^ A[18]; - tt0 ^= A[23]; - tt2 ^= tt3; - t4 = tt0 ^ tt2; - - A[ 0] = A[ 0] ^ t0; - A[ 5] = A[ 5] ^ t0; - A[10] = A[10] ^ t0; - A[15] = A[15] ^ t0; - A[20] = A[20] ^ t0; - A[ 1] = A[ 1] ^ t1; - A[ 6] = A[ 6] ^ t1; - A[11] = A[11] ^ t1; - A[16] = A[16] ^ t1; - A[21] = A[21] ^ t1; - A[ 2] = A[ 2] ^ t2; - A[ 7] = A[ 7] ^ t2; - A[12] = A[12] ^ t2; - A[17] = A[17] ^ t2; - A[22] = A[22] ^ t2; - A[ 3] = A[ 3] ^ t3; - A[ 8] = A[ 8] ^ t3; - A[13] = A[13] ^ t3; - A[18] = A[18] ^ t3; - A[23] = A[23] ^ t3; - A[ 4] = A[ 4] ^ t4; - A[ 9] = A[ 9] ^ t4; - A[14] = A[14] ^ t4; - A[19] = A[19] ^ t4; - A[24] = A[24] ^ t4; - A[ 5] = (A[ 5] << 36) | (A[ 5] >>> (64 - 36)); - A[10] = (A[10] << 3) | (A[10] >>> (64 - 3)); - A[15] = (A[15] << 41) | (A[15] >>> (64 - 41)); - A[20] = (A[20] << 18) | (A[20] >>> (64 - 18)); - A[ 1] = (A[ 1] << 1) | (A[ 1] >>> (64 - 1)); - A[ 6] = (A[ 6] << 44) | (A[ 6] >>> (64 - 44)); - A[11] = (A[11] << 10) | (A[11] >>> (64 - 10)); - A[16] = (A[16] << 45) | (A[16] >>> (64 - 45)); - A[21] = (A[21] << 2) | (A[21] >>> (64 - 2)); - A[ 2] = (A[ 2] << 62) | (A[ 2] >>> (64 - 62)); - A[ 7] = (A[ 7] << 6) | (A[ 7] >>> (64 - 6)); - A[12] = (A[12] << 43) | (A[12] >>> (64 - 43)); - A[17] = (A[17] << 15) | (A[17] >>> (64 - 15)); - A[22] = (A[22] << 61) | (A[22] >>> (64 - 61)); - A[ 3] = (A[ 3] << 28) | (A[ 3] >>> (64 - 28)); - A[ 8] = (A[ 8] << 55) | (A[ 8] >>> (64 - 55)); - A[13] = (A[13] << 25) | (A[13] >>> (64 - 25)); - A[18] = (A[18] << 21) | (A[18] >>> (64 - 21)); - A[23] = (A[23] << 56) | (A[23] >>> (64 - 56)); - A[ 4] = (A[ 4] << 27) | (A[ 4] >>> (64 - 27)); - A[ 9] = (A[ 9] << 20) | (A[ 9] >>> (64 - 20)); - A[14] = (A[14] << 39) | (A[14] >>> (64 - 39)); - A[19] = (A[19] << 8) | (A[19] >>> (64 - 8)); - A[24] = (A[24] << 14) | (A[24] >>> (64 - 14)); - bnn = ~A[12]; - kt = A[ 6] | A[12]; - c0 = A[ 0] ^ kt; - kt = bnn | A[18]; - c1 = A[ 6] ^ kt; - kt = A[18] & A[24]; - c2 = A[12] ^ kt; - kt = A[24] | A[ 0]; - c3 = A[18] ^ kt; - kt = A[ 0] & A[ 6]; - c4 = A[24] ^ kt; - A[ 0] = c0; - A[ 6] = c1; - A[12] = c2; - A[18] = c3; - A[24] = c4; - bnn = ~A[22]; - kt = A[ 9] | A[10]; - c0 = A[ 3] ^ kt; - kt = A[10] & A[16]; - c1 = A[ 9] ^ kt; - kt = A[16] | bnn; - c2 = A[10] ^ kt; - kt = A[22] | A[ 3]; - c3 = A[16] ^ kt; - kt = A[ 3] & A[ 9]; - c4 = A[22] ^ kt; - A[ 3] = c0; - A[ 9] = c1; - A[10] = c2; - A[16] = c3; - A[22] = c4; - bnn = ~A[19]; - kt = A[ 7] | A[13]; - c0 = A[ 1] ^ kt; - kt = A[13] & A[19]; - c1 = A[ 7] ^ kt; - kt = bnn & A[20]; - c2 = A[13] ^ kt; - kt = A[20] | A[ 1]; - c3 = bnn ^ kt; - kt = A[ 1] & A[ 7]; - c4 = A[20] ^ kt; - A[ 1] = c0; - A[ 7] = c1; - A[13] = c2; - A[19] = c3; - A[20] = c4; - bnn = ~A[17]; - kt = A[ 5] & A[11]; - c0 = A[ 4] ^ kt; - kt = A[11] | A[17]; - c1 = A[ 5] ^ kt; - kt = bnn | A[23]; - c2 = A[11] ^ kt; - kt = A[23] & A[ 4]; - c3 = bnn ^ kt; - kt = A[ 4] | A[ 5]; - c4 = A[23] ^ kt; - A[ 4] = c0; - A[ 5] = c1; - A[11] = c2; - A[17] = c3; - A[23] = c4; - bnn = ~A[ 8]; - kt = bnn & A[14]; - c0 = A[ 2] ^ kt; - kt = A[14] | A[15]; - c1 = bnn ^ kt; - kt = A[15] & A[21]; - c2 = A[14] ^ kt; - kt = A[21] | A[ 2]; - c3 = A[15] ^ kt; - kt = A[ 2] & A[ 8]; - c4 = A[21] ^ kt; - A[ 2] = c0; - A[ 8] = c1; - A[14] = c2; - A[15] = c3; - A[21] = c4; - A[ 0] = A[ 0] ^ RC[j + 0]; - - tt0 = A[ 6] ^ A[ 9]; - tt1 = A[ 7] ^ A[ 5]; - tt0 ^= A[ 8] ^ tt1; - tt0 = (tt0 << 1) | (tt0 >>> 63); - tt2 = A[24] ^ A[22]; - tt3 = A[20] ^ A[23]; - tt0 ^= A[21]; - tt2 ^= tt3; - t0 = tt0 ^ tt2; - - tt0 = A[12] ^ A[10]; - tt1 = A[13] ^ A[11]; - tt0 ^= A[14] ^ tt1; - tt0 = (tt0 << 1) | (tt0 >>> 63); - tt2 = A[ 0] ^ A[ 3]; - tt3 = A[ 1] ^ A[ 4]; - tt0 ^= A[ 2]; - tt2 ^= tt3; - t1 = tt0 ^ tt2; - - tt0 = A[18] ^ A[16]; - tt1 = A[19] ^ A[17]; - tt0 ^= A[15] ^ tt1; - tt0 = (tt0 << 1) | (tt0 >>> 63); - tt2 = A[ 6] ^ A[ 9]; - tt3 = A[ 7] ^ A[ 5]; - tt0 ^= A[ 8]; - tt2 ^= tt3; - t2 = tt0 ^ tt2; - - tt0 = A[24] ^ A[22]; - tt1 = A[20] ^ A[23]; - tt0 ^= A[21] ^ tt1; - tt0 = (tt0 << 1) | (tt0 >>> 63); - tt2 = A[12] ^ A[10]; - tt3 = A[13] ^ A[11]; - tt0 ^= A[14]; - tt2 ^= tt3; - t3 = tt0 ^ tt2; - - tt0 = A[ 0] ^ A[ 3]; - tt1 = A[ 1] ^ A[ 4]; - tt0 ^= A[ 2] ^ tt1; - tt0 = (tt0 << 1) | (tt0 >>> 63); - tt2 = A[18] ^ A[16]; - tt3 = A[19] ^ A[17]; - tt0 ^= A[15]; - tt2 ^= tt3; - t4 = tt0 ^ tt2; - - A[ 0] = A[ 0] ^ t0; - A[ 3] = A[ 3] ^ t0; - A[ 1] = A[ 1] ^ t0; - A[ 4] = A[ 4] ^ t0; - A[ 2] = A[ 2] ^ t0; - A[ 6] = A[ 6] ^ t1; - A[ 9] = A[ 9] ^ t1; - A[ 7] = A[ 7] ^ t1; - A[ 5] = A[ 5] ^ t1; - A[ 8] = A[ 8] ^ t1; - A[12] = A[12] ^ t2; - A[10] = A[10] ^ t2; - A[13] = A[13] ^ t2; - A[11] = A[11] ^ t2; - A[14] = A[14] ^ t2; - A[18] = A[18] ^ t3; - A[16] = A[16] ^ t3; - A[19] = A[19] ^ t3; - A[17] = A[17] ^ t3; - A[15] = A[15] ^ t3; - A[24] = A[24] ^ t4; - A[22] = A[22] ^ t4; - A[20] = A[20] ^ t4; - A[23] = A[23] ^ t4; - A[21] = A[21] ^ t4; - A[ 3] = (A[ 3] << 36) | (A[ 3] >>> (64 - 36)); - A[ 1] = (A[ 1] << 3) | (A[ 1] >>> (64 - 3)); - A[ 4] = (A[ 4] << 41) | (A[ 4] >>> (64 - 41)); - A[ 2] = (A[ 2] << 18) | (A[ 2] >>> (64 - 18)); - A[ 6] = (A[ 6] << 1) | (A[ 6] >>> (64 - 1)); - A[ 9] = (A[ 9] << 44) | (A[ 9] >>> (64 - 44)); - A[ 7] = (A[ 7] << 10) | (A[ 7] >>> (64 - 10)); - A[ 5] = (A[ 5] << 45) | (A[ 5] >>> (64 - 45)); - A[ 8] = (A[ 8] << 2) | (A[ 8] >>> (64 - 2)); - A[12] = (A[12] << 62) | (A[12] >>> (64 - 62)); - A[10] = (A[10] << 6) | (A[10] >>> (64 - 6)); - A[13] = (A[13] << 43) | (A[13] >>> (64 - 43)); - A[11] = (A[11] << 15) | (A[11] >>> (64 - 15)); - A[14] = (A[14] << 61) | (A[14] >>> (64 - 61)); - A[18] = (A[18] << 28) | (A[18] >>> (64 - 28)); - A[16] = (A[16] << 55) | (A[16] >>> (64 - 55)); - A[19] = (A[19] << 25) | (A[19] >>> (64 - 25)); - A[17] = (A[17] << 21) | (A[17] >>> (64 - 21)); - A[15] = (A[15] << 56) | (A[15] >>> (64 - 56)); - A[24] = (A[24] << 27) | (A[24] >>> (64 - 27)); - A[22] = (A[22] << 20) | (A[22] >>> (64 - 20)); - A[20] = (A[20] << 39) | (A[20] >>> (64 - 39)); - A[23] = (A[23] << 8) | (A[23] >>> (64 - 8)); - A[21] = (A[21] << 14) | (A[21] >>> (64 - 14)); - bnn = ~A[13]; - kt = A[ 9] | A[13]; - c0 = A[ 0] ^ kt; - kt = bnn | A[17]; - c1 = A[ 9] ^ kt; - kt = A[17] & A[21]; - c2 = A[13] ^ kt; - kt = A[21] | A[ 0]; - c3 = A[17] ^ kt; - kt = A[ 0] & A[ 9]; - c4 = A[21] ^ kt; - A[ 0] = c0; - A[ 9] = c1; - A[13] = c2; - A[17] = c3; - A[21] = c4; - bnn = ~A[14]; - kt = A[22] | A[ 1]; - c0 = A[18] ^ kt; - kt = A[ 1] & A[ 5]; - c1 = A[22] ^ kt; - kt = A[ 5] | bnn; - c2 = A[ 1] ^ kt; - kt = A[14] | A[18]; - c3 = A[ 5] ^ kt; - kt = A[18] & A[22]; - c4 = A[14] ^ kt; - A[18] = c0; - A[22] = c1; - A[ 1] = c2; - A[ 5] = c3; - A[14] = c4; - bnn = ~A[23]; - kt = A[10] | A[19]; - c0 = A[ 6] ^ kt; - kt = A[19] & A[23]; - c1 = A[10] ^ kt; - kt = bnn & A[ 2]; - c2 = A[19] ^ kt; - kt = A[ 2] | A[ 6]; - c3 = bnn ^ kt; - kt = A[ 6] & A[10]; - c4 = A[ 2] ^ kt; - A[ 6] = c0; - A[10] = c1; - A[19] = c2; - A[23] = c3; - A[ 2] = c4; - bnn = ~A[11]; - kt = A[ 3] & A[ 7]; - c0 = A[24] ^ kt; - kt = A[ 7] | A[11]; - c1 = A[ 3] ^ kt; - kt = bnn | A[15]; - c2 = A[ 7] ^ kt; - kt = A[15] & A[24]; - c3 = bnn ^ kt; - kt = A[24] | A[ 3]; - c4 = A[15] ^ kt; - A[24] = c0; - A[ 3] = c1; - A[ 7] = c2; - A[11] = c3; - A[15] = c4; - bnn = ~A[16]; - kt = bnn & A[20]; - c0 = A[12] ^ kt; - kt = A[20] | A[ 4]; - c1 = bnn ^ kt; - kt = A[ 4] & A[ 8]; - c2 = A[20] ^ kt; - kt = A[ 8] | A[12]; - c3 = A[ 4] ^ kt; - kt = A[12] & A[16]; - c4 = A[ 8] ^ kt; - A[12] = c0; - A[16] = c1; - A[20] = c2; - A[ 4] = c3; - A[ 8] = c4; - A[ 0] = A[ 0] ^ RC[j + 1]; - t = A[ 5]; - A[ 5] = A[18]; - A[18] = A[11]; - A[11] = A[10]; - A[10] = A[ 6]; - A[ 6] = A[22]; - A[22] = A[20]; - A[20] = A[12]; - A[12] = A[19]; - A[19] = A[15]; - A[15] = A[24]; - A[24] = A[ 8]; - A[ 8] = t; - t = A[ 1]; - A[ 1] = A[ 9]; - A[ 9] = A[14]; - A[14] = A[ 2]; - A[ 2] = A[13]; - A[13] = A[23]; - A[23] = A[ 4]; - A[ 4] = A[21]; - A[21] = A[16]; - A[16] = A[ 3]; - A[ 3] = A[17]; - A[17] = A[ 7]; - A[ 7] = t; - } - } - - protected void doPadding(byte[] out, int off) - { - int ptr = flush(); - byte[] buf = getBlockBuffer(); - if ((ptr + 1) == buf.length) { - buf[ptr] = (byte)0x81; - } else { - buf[ptr] = (byte)0x01; - for (int i = ptr + 1; i < (buf.length - 1); i ++) - buf[i] = 0; - buf[buf.length - 1] = (byte)0x80; - } - processBlock(buf); - A[ 1] = ~A[ 1]; - A[ 2] = ~A[ 2]; - A[ 8] = ~A[ 8]; - A[12] = ~A[12]; - A[17] = ~A[17]; - A[20] = ~A[20]; - int dlen = engineGetDigestLength(); - for (int i = 0; i < dlen; i += 8) - encodeLELong(A[i >>> 3], tmpOut, i); - System.arraycopy(tmpOut, 0, out, off, dlen); - } - - protected void doInit() - { - A = new long[25]; - tmpOut = new byte[(engineGetDigestLength() + 7) & ~7]; - doReset(); - } - - public int getBlockLength() - { - return 200 - 2 * engineGetDigestLength(); - } - - private final void doReset() - { - for (int i = 0; i < 25; i ++) - A[i] = 0; - A[ 1] = 0xFFFFFFFFFFFFFFFFL; - A[ 2] = 0xFFFFFFFFFFFFFFFFL; - A[ 8] = 0xFFFFFFFFFFFFFFFFL; - A[12] = 0xFFFFFFFFFFFFFFFFL; - A[17] = 0xFFFFFFFFFFFFFFFFL; - A[20] = 0xFFFFFFFFFFFFFFFFL; - } - - - protected Digest copyState(KeccakCore dst) - { - System.arraycopy(A, 0, dst.A, 0, 25); - return super.copyState(dst); - } - - public String toString() - { - return "Keccak-" + (engineGetDigestLength() << 3); - } -} diff --git a/app/src/main/java/com/tangem/wallet/RLP.java b/app/src/main/java/com/tangem/wallet/RLP.java deleted file mode 100644 index 7ec159ead9..0000000000 --- a/app/src/main/java/com/tangem/wallet/RLP.java +++ /dev/null @@ -1,228 +0,0 @@ -package com.tangem.wallet; - -/** - * Created by Ilia on 07.01.2018. - */ - -import java.util.Arrays; - -import static com.tangem.util.ByteUtil.isNullOrZeroArray; -import static com.tangem.util.ByteUtil.isSingleZero; - -public class RLP { - public static final byte[] EMPTY_ELEMENT_RLP = encodeElement(new byte[0]); - - /** - * Allow for content up to size of 2^64 bytes * - */ - private static final double MAX_ITEM_LENGTH = Math.pow(256, 8); - - /** - * Reason for threshold according to Vitalik Buterin: - * - 56 bytes maximizes the benefit of both options - * - if we went with 60 then we would have only had 4 slots for long strings - * so RLP would not have been able to store objects above 4gb - * - if we went with 48 then RLP would be fine for 2^128 space, but that's way too much - * - so 56 and 2^64 space seems like the right place to put the cutoff - * - also, that's where Bitcoin's varint does the cutof - */ - private static final int SIZE_THRESHOLD = 56; - - /** RLP encoding rules are defined as follows: */ - - /* - * For a single byte whose value is in the [0x00, 0x7f] range, that byte is - * its own RLP encoding. - */ - - /** - * [0x80] - * If a string is 0-55 bytes long, the RLP encoding consists of a single - * byte with value 0x80 plus the length of the string followed by the - * string. The range of the first byte is thus [0x80, 0xb7]. - */ - private static final int OFFSET_SHORT_ITEM = 0x80; - - /** - * [0xb7] - * If a string is more than 55 bytes long, the RLP encoding consists of a - * single byte with value 0xb7 plus the length of the length of the string - * in binary form, followed by the length of the string, followed by the - * string. For example, a length-1024 string would be encoded as - * \xb9\x04\x00 followed by the string. The range of the first byte is thus - * [0xb8, 0xbf]. - */ - private static final int OFFSET_LONG_ITEM = 0xb7; - - /** - * [0xc0] - * If the total payload of a list (i.e. the combined length of all its - * items) is 0-55 bytes long, the RLP encoding consists of a single byte - * with value 0xc0 plus the length of the list followed by the concatenation - * of the RLP encodings of the items. The range of the first byte is thus - * [0xc0, 0xf7]. - */ - private static final int OFFSET_SHORT_LIST = 0xc0; - - public static byte[] encodeByte(byte singleByte) { - if ((singleByte & 0xFF) == 0) { - return new byte[]{(byte) OFFSET_SHORT_ITEM}; - } else if ((singleByte & 0xFF) <= 0x7F) { - return new byte[]{singleByte}; - } else { - return new byte[]{(byte) (OFFSET_SHORT_ITEM + 1), singleByte}; - } - } - - public static byte[] encodeShort(short singleShort) { - if ((singleShort & 0xFF) == singleShort) - return encodeByte((byte) singleShort); - else { - return new byte[]{(byte) (OFFSET_SHORT_ITEM + 2), - (byte) (singleShort >> 8 & 0xFF), - (byte) (singleShort >> 0 & 0xFF)}; - } - } - - public static byte[] encodeInt(int singleInt) { - if ((singleInt & 0xFF) == singleInt) - return encodeByte((byte) singleInt); - else if ((singleInt & 0xFFFF) == singleInt) - return encodeShort((short) singleInt); - else if ((singleInt & 0xFFFFFF) == singleInt) - return new byte[]{(byte) (OFFSET_SHORT_ITEM + 3), - (byte) (singleInt >>> 16), - (byte) (singleInt >>> 8), - (byte) singleInt}; - else { - return new byte[]{(byte) (OFFSET_SHORT_ITEM + 4), - (byte) (singleInt >>> 24), - (byte) (singleInt >>> 16), - (byte) (singleInt >>> 8), - (byte) singleInt}; - } - } - - private static final int OFFSET_LONG_LIST = 0xf7; - - public static byte[] encodeElement2(byte[] srcData) { - if (srcData == null) - return new byte[]{(byte) OFFSET_SHORT_ITEM}; - else if (srcData.length == 1 && (srcData[0] & 0xFF) < 0x80) { - return srcData; - } else if (srcData.length < SIZE_THRESHOLD) { - // length = 8X - byte length = (byte) (OFFSET_SHORT_ITEM + srcData.length); - byte[] data = Arrays.copyOf(srcData, srcData.length + 1); - System.arraycopy(data, 0, data, 1, srcData.length); - data[0] = length; - - return data; - } else { - // length of length = BX - // prefix = [BX, [length]] - int tmpLength = srcData.length; - byte byteNum = 0; - while (tmpLength != 0) { - ++byteNum; - tmpLength = tmpLength >> 8; - } - byte[] lenBytes = new byte[byteNum]; - for (int i = 0; i < byteNum; ++i) { - lenBytes[byteNum - 1 - i] = (byte) ((srcData.length >> (8 * i)) & 0xFF); - } - // first byte = F7 + bytes.length - byte[] data = Arrays.copyOf(srcData, srcData.length + 1 + byteNum); - System.arraycopy(data, 0, data, 1 + byteNum, srcData.length); - data[0] = (byte) (OFFSET_LONG_ITEM + byteNum); - System.arraycopy(lenBytes, 0, data, 1, lenBytes.length); - - return data; - } - } - public static byte[] encodeElement(byte[] srcData) { - - if (isNullOrZeroArray(srcData)) - return new byte[]{(byte) OFFSET_SHORT_ITEM}; - else if (isSingleZero(srcData)) - return srcData; - else if (srcData.length == 1 && (srcData[0] & 0xFF) < 0x80) { - return srcData; - } else if (srcData.length < SIZE_THRESHOLD) { - // length = 8X - byte length = (byte) (OFFSET_SHORT_ITEM + srcData.length); - byte[] data = Arrays.copyOf(srcData, srcData.length + 1); - System.arraycopy(data, 0, data, 1, srcData.length); - data[0] = length; - - return data; - } else { - // length of length = BX - // prefix = [BX, [length]] - int tmpLength = srcData.length; - byte byteNum = 0; - while (tmpLength != 0) { - ++byteNum; - tmpLength = tmpLength >> 8; - } - byte[] lenBytes = new byte[byteNum]; - for (int i = 0; i < byteNum; ++i) { - lenBytes[byteNum - 1 - i] = (byte) ((srcData.length >> (8 * i)) & 0xFF); - } - // first byte = F7 + bytes.length - byte[] data = Arrays.copyOf(srcData, srcData.length + 1 + byteNum); - System.arraycopy(data, 0, data, 1 + byteNum, srcData.length); - data[0] = (byte) (OFFSET_LONG_ITEM + byteNum); - System.arraycopy(lenBytes, 0, data, 1, lenBytes.length); - - return data; - } - } - - - public static byte[] encodeList(byte[]... elements) { - - if (elements == null) { - return new byte[]{(byte) OFFSET_SHORT_LIST}; - } - - int totalLength = 0; - for (byte[] element1 : elements) { - totalLength += element1.length; - } - - byte[] data; - int copyPos; - if (totalLength < SIZE_THRESHOLD) { - - data = new byte[1 + totalLength]; - data[0] = (byte) (OFFSET_SHORT_LIST + totalLength); - copyPos = 1; - } else { - // length of length = BX - // prefix = [BX, [length]] - int tmpLength = totalLength; - byte byteNum = 0; - while (tmpLength != 0) { - ++byteNum; - tmpLength = tmpLength >> 8; - } - tmpLength = totalLength; - byte[] lenBytes = new byte[byteNum]; - for (int i = 0; i < byteNum; ++i) { - lenBytes[byteNum - 1 - i] = (byte) ((tmpLength >> (8 * i)) & 0xFF); - } - // first byte = F7 + bytes.length - data = new byte[1 + lenBytes.length + totalLength]; - data[0] = (byte) (OFFSET_LONG_LIST + byteNum); - System.arraycopy(lenBytes, 0, data, 1, lenBytes.length); - - copyPos = lenBytes.length + 1; - } - for (byte[] element : elements) { - System.arraycopy(element, 0, data, copyPos, element.length); - copyPos += element.length; - } - return data; - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/RLPElement.java b/app/src/main/java/com/tangem/wallet/RLPElement.java deleted file mode 100644 index ae82526c2d..0000000000 --- a/app/src/main/java/com/tangem/wallet/RLPElement.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.tangem.wallet; - -import java.io.Serializable; - -/** - * Created by Ilia on 07.01.2018. - */ - -public interface RLPElement extends Serializable { - - byte[] getRLPData(); -} diff --git a/app/src/main/java/com/tangem/wallet/RLPList.java b/app/src/main/java/com/tangem/wallet/RLPList.java deleted file mode 100644 index 14164502b3..0000000000 --- a/app/src/main/java/com/tangem/wallet/RLPList.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.tangem.wallet; - -import java.util.ArrayList; - -/** - * Created by Ilia on 07.01.2018. - */ - -public class RLPList extends ArrayList implements RLPElement { - - byte[] rlpData; - - public void setRLPData(byte[] rlpData) { - this.rlpData = rlpData; - } - - public byte[] getRLPData() { - return rlpData; - } - - public static void recursivePrint(RLPElement element) { - - if (element == null) - throw new RuntimeException("RLPElement object can't be null"); - if (element instanceof RLPList) { - - RLPList rlpList = (RLPList) element; - System.out.print("["); - for (RLPElement singleElement : rlpList) - recursivePrint(singleElement); - System.out.print("]"); - } else { - String hex = BTCUtils.toHex(element.getRLPData()); - System.out.print(hex + ", "); - } - } -} diff --git a/app/src/main/java/com/tangem/wallet/TangemContext.java b/app/src/main/java/com/tangem/wallet/TangemContext.java deleted file mode 100644 index 9d3c4d5194..0000000000 --- a/app/src/main/java/com/tangem/wallet/TangemContext.java +++ /dev/null @@ -1,207 +0,0 @@ -package com.tangem.wallet; - -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; - -import com.google.firebase.crashlytics.FirebaseCrashlytics; -import com.tangem.Constant; -import com.tangem.data.Blockchain; -import com.tangem.tangem_card.data.TangemCard; -import com.tangem.tangem_sdk.data.TangemCardExtensionsKt; -import com.tangem.util.AnalyticsParam; - -public class TangemContext { - // public static final String EXTRA_BLOCKCHAIN_DATA = "BLOCKCHAIN_DATA"; - private Context context; - private TangemCard card; - private CoinData coinData; - private String error; - private String message; - - public TangemContext() { - - } - - public TangemContext(TangemCard card) { - - this.card = card; - } - - public Blockchain getBlockchain() { - if (card == null) return Blockchain.Unknown; - Blockchain blockchain = Blockchain.fromId(card.getBlockchainID()); - if ((blockchain == Blockchain.Ethereum || blockchain == Blockchain.EthereumTestNet) && card.isToken()) { - if (card.getTokenSymbol().startsWith("NFT:")) { - return Blockchain.NftToken; - } else { - return Blockchain.Token; - } - } - if (blockchain == Blockchain.Ethereum && card.isIDCard()) { - return Blockchain.EthereumId; - } - if ((blockchain == Blockchain.Rootstock) && card.isToken()) { - return Blockchain.RootstockToken; - } - if (blockchain == Blockchain.Stellar && card.isToken()) { - return Blockchain.StellarAsset; - } - if (blockchain == Blockchain.Binance && card.isToken()) { - return Blockchain.BinanceAsset; - } - return blockchain; - } - -// public void setBlockchain(Blockchain blockchain) { -// if (card == null) return; -// card.setBlockchainID(blockchain.getID()); -// } - -// private String blockchainName = ""; - - public String getBlockchainName() { - Blockchain blockchain = getBlockchain(); - if (blockchain == Blockchain.Token || blockchain == Blockchain.RootstockToken) { - return card.getTokenSymbol() + "
" + getBlockchain().getOfficialName() + " smart contract token"; - } - if (blockchain == Blockchain.NftToken) { - return card.getTokenSymbol().substring(4) + "
" + getBlockchain().getOfficialName() + " non-fungible token"; - } - if (blockchain == Blockchain.StellarAsset || blockchain == Blockchain.BinanceAsset) { - return card.getTokenSymbol() + "
" + getBlockchain().getOfficialName() + " asset"; - } - if (blockchain == Blockchain.StellarTag) { - return "TANGEM TAG
" + getBlockchain().getOfficialName() + " non-fungible token "; - } - return blockchain.getOfficialName(); - } - - public Context getContext() { - return context; - } - - public void setContext(Context context) { - this.context = context; - } - - public TangemCard getCard() { - return card; - } - - public void setCard(TangemCard card) { - this.card = card; - } - - public CoinData getCoinData() { - return coinData; - } - - public void setCoinData(CoinData coinData) { - this.coinData = coinData; - } - - public void setError(String error) { - if (error != null) { - FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance(); - crashlytics.setCustomKey(AnalyticsParam.BLOCKCHAIN.getParam(), getBlockchainName()); - crashlytics.recordException(new Exception(error)); - } - this.error = error; - } - - public void setError(int valueId) { - this.error = getString(valueId); - } - - public String getError() { - return error; - } - - public boolean hasError() { - return error != null && !error.isEmpty(); - } - - public void setMessage(String value) { - this.message = value; - } - - public void setMessage(int valueId) { - this.message = getString(valueId); - } - - public String getMessage() { - return message; - } - - - public static TangemContext loadFromBundle(Context context, Bundle bundle) { - TangemContext tangemContext = new TangemContext(); - tangemContext.setContext(context); - - if (bundle.containsKey(TangemCardExtensionsKt.EXTRA_TANGEM_CARD_UID)) { - tangemContext.card = new TangemCard(bundle.getString(TangemCardExtensionsKt.EXTRA_TANGEM_CARD_UID)); - TangemCardExtensionsKt.loadFromBundle(tangemContext.card, bundle.getBundle(TangemCardExtensionsKt.EXTRA_TANGEM_CARD)); - } - - if (tangemContext.getBlockchain() != null) { - if (bundle.containsKey(Constant.EXTRA_BLOCKCHAIN_DATA)) { - tangemContext.coinData = CoinData.fromBundle(tangemContext.getBlockchain(), bundle.getBundle(Constant.EXTRA_BLOCKCHAIN_DATA)); - } else { - tangemContext.coinData = CoinEngineFactory.INSTANCE.create(tangemContext).createCoinData(); - } - } - tangemContext.error = bundle.getString("Error"); - tangemContext.message = bundle.getString("Message"); - - return tangemContext; - } - - public void saveToBundle(Bundle intent) { - - if (card != null) { - intent.putString(TangemCardExtensionsKt.EXTRA_TANGEM_CARD_UID, card.getUID()); - intent.putBundle(TangemCardExtensionsKt.EXTRA_TANGEM_CARD, TangemCardExtensionsKt.getAsBundle(card)); - } - - if (coinData != null) { - intent.putBundle(Constant.EXTRA_BLOCKCHAIN_DATA, coinData.asBundle()); - } - - intent.putString("Error", error); - intent.putString("Message", message); - } - - public void saveToIntent(Intent intent) { - - if (card != null) { - intent.putExtra(TangemCardExtensionsKt.EXTRA_TANGEM_CARD_UID, card.getUID()); - intent.putExtra(TangemCardExtensionsKt.EXTRA_TANGEM_CARD, TangemCardExtensionsKt.getAsBundle(card)); - } - - if (coinData != null) { - intent.putExtra(Constant.EXTRA_BLOCKCHAIN_DATA, coinData.asBundle()); - } - - intent.putExtra("Error", error); - intent.putExtra("Message", message); - } - - public String getString(int stringId) { - if (context != null) return getContext().getResources().getString(stringId); - return "context.resources.string[" + stringId + "]"; - } - - public void setDenomination(byte[] denomination) { - try { - CoinEngine engine = CoinEngineFactory.INSTANCE.create(getBlockchain()); - CoinEngine.InternalAmount internalAmount = engine.convertToInternalAmount(denomination); - CoinEngine.Amount amount = engine.convertToAmount(internalAmount); - card.setDenomination(denomination, amount.toString()); - } catch (Exception e) { - e.printStackTrace(); - card.setDenomination(denomination, "N/A"); - } - } - -} diff --git a/app/src/main/java/com/tangem/wallet/Transaction.java b/app/src/main/java/com/tangem/wallet/Transaction.java deleted file mode 100644 index 07dc9b42e5..0000000000 --- a/app/src/main/java/com/tangem/wallet/Transaction.java +++ /dev/null @@ -1,635 +0,0 @@ -package com.tangem.wallet; - -/** - * Created by Ilia on 29.09.2017. - */ - -import com.tangem.wallet.btc.BitcoinException; -import com.tangem.wallet.btc.BitcoinInputStream; -import com.tangem.wallet.btc.BitcoinOutputStream; -import com.tangem.util.CryptoUtil; - -import java.io.ByteArrayOutputStream; -import java.io.EOFException; -import java.io.IOException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.Arrays; -import java.util.Stack; - -@SuppressWarnings("WeakerAccess") -public final class Transaction { - public final int version; - public final Input[] inputs; - public final Output[] outputs; - public final int lockTime; - - public Transaction(byte[] rawBytes) throws BitcoinException { - if (rawBytes == null) { - throw new BitcoinException(BitcoinException.ERR_NO_INPUT, "empty input"); - } - BitcoinInputStream bais = null; - try { - bais = new BitcoinInputStream(rawBytes); - version = bais.readInt32(); - if (version != 1 && version != 2 && version != 3 && version != -1273714314) { - throw new BitcoinException(BitcoinException.ERR_UNSUPPORTED, "Unsupported TX version", version); - } - - - int inputsCount = 0; - int first = bais.readByte(); - if(first == 0) - { - int skip = bais.readByte(); - inputsCount = bais.readByte(); - } - else - { - inputsCount = first; - } - //int inputsCount = (int) bais.readVarInt(); TODO: - inputs = new Input[inputsCount]; - for (int i = 0; i < inputsCount; i++) { - OutPoint outPoint = new OutPoint(BTCUtils.reverse(bais.readChars(32)), bais.readInt32()); - byte[] script = bais.readChars((int) bais.readVarInt()); - int sequence = bais.readInt32(); - inputs[i] = new Input(outPoint, new Script(script), sequence); - } - int outputsCount = (int) bais.readVarInt(); - outputs = new Output[outputsCount]; - for (int i = 0; i < outputsCount; i++) { - long value = bais.readInt64(); - long scriptSize = bais.readVarInt(); - if (scriptSize < 0 || scriptSize > 10_000_000) { - throw new BitcoinException(BitcoinException.ERR_BAD_FORMAT, "Script size for output " + i + - " is strange (" + scriptSize + " bytes)."); - } - byte[] script = bais.readChars((int) scriptSize); - outputs[i] = new Output(value, new Script(script)); - } - lockTime = bais.readInt32(); - } catch (EOFException e) { - throw new BitcoinException(BitcoinException.ERR_BAD_FORMAT, "TX incomplete"); - } catch (IOException e) { - throw new IllegalArgumentException("Unable to read TX"); - } catch (Error e) { - throw new IllegalArgumentException("Unable to read TX: " + e); - } finally { - if (bais != null) { - try { - bais.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - } - - public Transaction(Input[] inputs, Output[] outputs, int lockTime) { - this.version = 1; - this.inputs = inputs; - this.outputs = outputs; - this.lockTime = lockTime; - } - - public byte[] getBytes() { - BitcoinOutputStream baos = new BitcoinOutputStream(); - try { - baos.writeInt32(version); - baos.writeVarInt(inputs.length); - for (Input input : inputs) { - baos.write(BTCUtils.reverse(input.outPoint.hash)); - baos.writeInt32(input.outPoint.index); - int scriptLen = input.script == null ? 0 : input.script.bytes.length; - baos.writeVarInt(scriptLen); - if (scriptLen > 0) { - baos.write(input.script.bytes); - } - baos.writeInt32(input.sequence); - } - baos.writeVarInt(outputs.length); - for (Output output : outputs) { - baos.writeInt64(output.value); - int scriptLen = output.script == null ? 0 : output.script.bytes.length; - baos.writeVarInt(scriptLen); - if (scriptLen > 0) { - baos.write(output.script.bytes); - } - } - baos.writeInt32(lockTime); - } catch (IOException e) { - e.printStackTrace(); - } finally { - try { - baos.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - return baos.toByteArray(); - - } - - @Override - public String toString() { - return "{" + - "\n\"inputs\":\n" + printAsJsonArray(inputs) + - ",\n\"outputs\":\n" + printAsJsonArray(outputs) + - ",\n\"lockTime\":\"" + lockTime + "\"}\n"; - } - - private String printAsJsonArray(Object[] a) { - if (a == null) { - return "null"; - } - if (a.length == 0) { - return "[]"; - } - int iMax = a.length - 1; - StringBuilder sb = new StringBuilder(); - sb.append('['); - for (int i = 0; ; i++) { - sb.append(String.valueOf(a[i])); - if (i == iMax) - return sb.append(']').toString(); - sb.append(",\n"); - } - } - - public static class Input { - public final OutPoint outPoint; - public final Script script; - public final int sequence; - - public Input(OutPoint outPoint, Script script, int sequence) { - this.outPoint = outPoint; - this.script = script; - this.sequence = sequence; - } - - @Override - public String toString() { - return "{\n\"outPoint\":" + outPoint + ",\n\"script\":\"" + script + "\",\n\"sequence\":\"" + Integer.toHexString(sequence) + "\"\n}\n"; - } - } - - public static class OutPoint { - public final byte[] hash;//32-byte hash of the transaction from which we want to redeem an output - public final int index;//Four-byte field denoting the output index we want to redeem from the transaction with the above hash (output number 2 = output index 1) - - public OutPoint(byte[] hash, int index) { - this.hash = hash; - this.index = index; - } - - @Override - public String toString() { - return "{" + "\"hash\":\"" + BTCUtils.toHex(hash) + "\", \"index\":\"" + index + "\"}"; - } - } - - public static class Output { - public final long value; - public final Script script; - - public Output(long value, Script script) { - this.value = value; - this.script = script; - } - - @Override - public String toString() { - return "{\n\"value\":\"" + value * 1e-8 + "\",\"script\":\"" + script + "\"\n}"; - } - } - - public static final class Script { - - public static class ScriptInvalidException extends Exception { - public ScriptInvalidException() { - } - - public ScriptInvalidException(String s) { - super(s); - } - } - - public static final byte OP_FALSE = 0; - public static final byte OP_TRUE = 0x51; - public static final byte OP_PUSHDATA1 = 0x4c; - public static final byte OP_PUSHDATA2 = 0x4d; - public static final byte OP_PUSHDATA4 = 0x4e; - public static final byte OP_DUP = 0x76;//Duplicates the top stack item. - public static final byte OP_DROP = 0x75; - public static final byte OP_HASH160 = (byte) 0xA9;//The input is hashed twice: first with SHA-256 and then with RIPEMD-160. - public static final byte OP_VERIFY = 0x69;//Marks transaction as invalid if top stack value is not true. True is removed, but false is not. - public static final byte OP_EQUAL = (byte) 0x87;//Returns 1 if the inputs are exactly equal, 0 otherwise. - public static final byte OP_EQUALVERIFY = (byte) 0x88;//Same as OP_EQUAL, but runs OP_VERIFY afterward. - public static final byte OP_CHECKSIG = (byte) 0xAC;//The entire transaction's outputs, inputs, and script (from the most recently-executed OP_CODESEPARATOR to the end) are hashed. The signature used by OP_CHECKSIG must be a valid signature for this hash and public key. If it is, 1 is returned, 0 otherwise. - public static final byte OP_CHECKSIGVERIFY = (byte) 0xAD; - public static final byte OP_NOP = 0x61; - - public static final byte SIGHASH_ALL = 1; - - public final byte[] bytes; - - public Script(byte[] rawBytes) { - bytes = rawBytes; - } - - public Script(byte[] data1, byte[] data2) { - ByteArrayOutputStream baos = new ByteArrayOutputStream(data1.length + data2.length + 2); - try { - writeBytes(data1, baos); - writeBytes(data2, baos); - baos.close(); - } catch (IOException e) { - throw new RuntimeException(e); - } - bytes = baos.toByteArray(); - } - - private static void writeBytes(byte[] data, ByteArrayOutputStream baos) throws IOException { - if (data.length < OP_PUSHDATA1) { - baos.write(data.length); - } else if (data.length < 0xff) { - baos.write(OP_PUSHDATA1); - baos.write(data.length); - } else if (data.length < 0xffff) { - baos.write(OP_PUSHDATA2); - baos.write(data.length & 0xff); - baos.write((data.length >> 8) & 0xff); - } else { - baos.write(OP_PUSHDATA4); - baos.write(data.length & 0xff); - baos.write((data.length >> 8) & 0xff); - baos.write((data.length >> 16) & 0xff); - baos.write((data.length >>> 24) & 0xff); - } - baos.write(data); - } - - public void run(Stack stack) throws ScriptInvalidException { - run(0, null, stack); - } - - public void run(int inputIndex, Transaction tx, Stack stack) throws ScriptInvalidException { - for (int pos = 0; pos < bytes.length; pos++) { - switch (bytes[pos]) { - case OP_NOP: - break; - case OP_DROP: - if (stack.isEmpty()) { - throw new IllegalArgumentException("stack empty on OP_DROP"); - } - stack.pop(); - break; - case OP_DUP: - if (stack.isEmpty()) { - throw new IllegalArgumentException("stack empty on OP_DUP"); - } - stack.push(stack.peek()); - break; - case OP_HASH160: - if (stack.isEmpty()) { - throw new IllegalArgumentException("stack empty on OP_HASH160"); - } - stack.push(CryptoUtil.sha256ripemd160(stack.pop())); - break; - case OP_EQUAL: - case OP_EQUALVERIFY: - if (stack.size() < 2) { - throw new IllegalArgumentException("not enough elements to perform OP_EQUAL"); - } - stack.push(new byte[]{(byte) (Arrays.equals(stack.pop(), stack.pop()) ? 1 : 0)}); - if (bytes[pos] == OP_EQUALVERIFY) { - if (verifyFails(stack)) { - throw new ScriptInvalidException("wrong address"); - } - } - break; - case OP_VERIFY: - if (verifyFails(stack)) { - throw new ScriptInvalidException(); - } - break; - case OP_CHECKSIG: - case OP_CHECKSIGVERIFY: - byte[] publicKey = stack.pop(); - byte[] signatureAndHashType = stack.pop(); - if (signatureAndHashType[signatureAndHashType.length - 1] != SIGHASH_ALL) { - throw new IllegalArgumentException("I cannot check this sig type: " + signatureAndHashType[signatureAndHashType.length - 1]); - } - byte[] signature = new byte[signatureAndHashType.length - 1]; - System.arraycopy(signatureAndHashType, 0, signature, 0, signature.length); - byte[] hash = hashTransaction(inputIndex, bytes, tx); - //boolean valid = BTCUtils.verify(publicKey, signature, hash); - if (bytes[pos] == OP_CHECKSIG) { - stack.push(new byte[]{(byte) (1)}); - } else { - if (verifyFails(stack)) { - throw new ScriptInvalidException("Bad signature"); - } - if (!stack.empty()) { - throw new ScriptInvalidException("Bad signature - superfluous scriptSig operations"); - } - } - break; - case OP_FALSE: - stack.push(new byte[]{0}); - break; - case OP_TRUE: - stack.push(new byte[]{1}); - break; - default: - int op = bytes[pos] & 0xff; - int len; - if (op < OP_PUSHDATA1) { - len = op; - byte[] data = new byte[len]; - System.arraycopy(bytes, pos + 1, data, 0, len); - stack.push(data); - pos += data.length; - } else if (op == OP_PUSHDATA1) { - len = bytes[pos + 1] & 0xff; - byte[] data = new byte[len]; - System.arraycopy(bytes, pos + 1, data, 0, len); - stack.push(data); - pos += 1 + data.length; - } else { - throw new IllegalArgumentException("I cannot read this data: " + Integer.toHexString(bytes[pos])); - } - break; - } - } - } - - public static byte[] hashTransaction(int inputIndex, byte[] subscript, Transaction tx) { - Input[] unsignedInputs = new Input[tx.inputs.length]; - for (int i = 0; i < tx.inputs.length; i++) { - Input txInput = tx.inputs[i]; - if (i == inputIndex) { - unsignedInputs[i] = new Input(txInput.outPoint, new Script(subscript), txInput.sequence); - } else { - unsignedInputs[i] = new Input(txInput.outPoint, new Script(new byte[0]), txInput.sequence); - } - } - Transaction unsignedTransaction = new Transaction(unsignedInputs, tx.outputs, tx.lockTime); - return hashTransactionForSigning(unsignedTransaction); - } - - public static byte[] hashTransactionForSigning(Transaction unsignedTransaction) { - byte[] txUnsignedBytes = unsignedTransaction.getBytes(); - BitcoinOutputStream baos = new BitcoinOutputStream(); - try { - baos.write(txUnsignedBytes); - baos.writeInt32(Script.SIGHASH_ALL); - baos.close(); - } catch (Exception e) { - throw new RuntimeException(e); - } - return CryptoUtil.doubleSha256(baos.toByteArray()); - } - - public static boolean verifyFails(Stack stack) { - byte[] input; - boolean valid; - input = stack.pop(); - if (input.length == 0 || (input.length == 1 && input[0] == OP_FALSE)) { - //false - stack.push(new byte[]{OP_FALSE}); - valid = false; - } else { - //true - valid = true; - } - return !valid; - } - - - @Override - public String toString() { - return convertBytesToReadableString(bytes); - } - - //converts something like "OP_DUP OP_HASH160 ba507bae8f1643d2556000ca26b9301b9069dc6b OP_EQUALVERIFY OP_CHECKSIG" into bytes - public static byte[] convertReadableStringToBytes(String readableString) { - String[] tokens = readableString.trim().split("\\s+"); - ByteArrayOutputStream os = new ByteArrayOutputStream(); - for (String token : tokens) { - switch (token) { - case "OP_NOP": - os.write(OP_NOP); - break; - case "OP_DROP": - os.write(OP_DROP); - break; - case "OP_DUP": - os.write(OP_DUP); - break; - case "OP_HASH160": - os.write(OP_HASH160); - break; - case "OP_EQUAL": - os.write(OP_EQUAL); - break; - case "OP_EQUALVERIFY": - os.write(OP_EQUALVERIFY); - break; - case "OP_VERIFY": - os.write(OP_VERIFY); - break; - case "OP_CHECKSIG": - os.write(OP_CHECKSIG); - break; - case "OP_CHECKSIGVERIFY": - os.write(OP_CHECKSIGVERIFY); - break; - case "OP_FALSE": - os.write(OP_FALSE); - break; - case "OP_TRUE": - os.write(OP_TRUE); - break; - default: - if (token.startsWith("OP_")) { - throw new IllegalArgumentException("I don't know this operation: " + token); - } - byte[] data = BTCUtils.fromHex(token); - if (data == null) { - throw new IllegalArgumentException("I don't know what's this: " + token); - } - if (data.length < OP_PUSHDATA1) { - os.write(data.length); - try { - os.write(data); - } catch (IOException e) { - throw new RuntimeException("ByteArrayOutputStream behaves weird: " + e); - } - } else if (data.length <= 255) { - os.write(OP_PUSHDATA1); - os.write(data.length); - try { - os.write(data); - } catch (IOException e) { - throw new RuntimeException("ByteArrayOutputStream behaves weird: " + e); - } - } else { - throw new IllegalArgumentException("OP_PUSHDATA2 & OP_PUSHDATA4 are not supported"); - } - break; - } - } - try { - os.close(); - } catch (IOException e) { - e.printStackTrace(); - } - return os.toByteArray(); - } - - public static String convertBytesToReadableString(byte[] bytes) { - StringBuilder sb = new StringBuilder(); - for (int pos = 0; pos < bytes.length; pos++) { - if (sb.length() > 0) { - sb.append(' '); - } - switch (bytes[pos]) { - case OP_NOP: - sb.append("OP_NOP"); - break; - case OP_DROP: - sb.append("OP_DROP"); - break; - case OP_DUP: - sb.append("OP_DUP"); - break; - case OP_HASH160: - sb.append("OP_HASH160"); - break; - case OP_EQUAL: - sb.append("OP_EQUAL"); - break; - case OP_EQUALVERIFY: - sb.append("OP_EQUALVERIFY"); - break; - case OP_VERIFY: - sb.append("OP_VERIFY"); - break; - case OP_CHECKSIG: - sb.append("OP_CHECKSIG"); - break; - case OP_CHECKSIGVERIFY: - sb.append("OP_CHECKSIGVERIFY"); - break; - case OP_FALSE: - sb.append("OP_FALSE"); - break; - case OP_TRUE: - sb.append("OP_TRUE"); - break; - default: - int op = bytes[pos] & 0xff; - int len; - if (op < OP_PUSHDATA1) { - len = op; - byte[] data = new byte[len]; - System.arraycopy(bytes, pos + 1, data, 0, len); - sb.append(BTCUtils.toHex(data)); - pos += data.length; - } else if (op == OP_PUSHDATA1) { - len = bytes[pos + 1] & 0xff; - byte[] data = new byte[len]; - System.arraycopy(bytes, pos + 1, data, 0, len);//FIXME I suspect there is off by one error... - sb.append(BTCUtils.toHex(data)); - pos += 1 + data.length; - } else { - throw new IllegalArgumentException("I cannot read this data: " + Integer.toHexString(bytes[pos]) + " at " + pos); - } - break; - } - } - return sb.toString(); - } - - @Override - public boolean equals(Object o) { - return this == o || !(o == null || getClass() != o.getClass()) && Arrays.equals(bytes, ((Script) o).bytes); - } - - @Override - public int hashCode() { - return Arrays.hashCode(bytes); - } - - public static Script buildOutput(String address) throws BitcoinException { - //noinspection TryWithIdenticalCatches - byte[] addressWithCheckSumAndNetworkCode = Base58.decodeBase58(address); - if (addressWithCheckSumAndNetworkCode[0] == 0 || addressWithCheckSumAndNetworkCode[0] == 111 || addressWithCheckSumAndNetworkCode[0] == 48 || addressWithCheckSumAndNetworkCode[0] == 49) { //0 for BTC/BCH 1 address | 48 for LTC L address | 49 for Ducatus - return buildOutputP2H(address); - } - - if(addressWithCheckSumAndNetworkCode[0] == 5 || addressWithCheckSumAndNetworkCode[0] == (byte)0xc4 || addressWithCheckSumAndNetworkCode[0] == 50) { //5 for BTC/BCH/LTC 3 address | 50 for LTC M address - return buildOutputP2SH(address); - } - - throw new BitcoinException(BitcoinException.ERR_UNSUPPORTED, "Unknown address type", address); - } - public static Script buildOutputP2SH(String address) throws BitcoinException { - try { - byte[] addressWithCheckSumAndNetworkCode = Base58.decodeBase58(address); - if (addressWithCheckSumAndNetworkCode[0] != 5 && addressWithCheckSumAndNetworkCode[0] != (byte)0xc4 && addressWithCheckSumAndNetworkCode[0] != 50) { - throw new BitcoinException(BitcoinException.ERR_UNSUPPORTED, "Unknown address type", address); - } - - byte[] bareAddress = new byte[20]; - System.arraycopy(addressWithCheckSumAndNetworkCode, 1, bareAddress, 0, bareAddress.length); - - ByteArrayOutputStream buf = new ByteArrayOutputStream(23); - buf.write(OP_HASH160); - writeBytes(bareAddress, buf); - buf.write(OP_EQUAL); - return new Script(buf.toByteArray()); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - - public static Script buildOutputP2H(String address) throws BitcoinException { - //noinspection TryWithIdenticalCatches - try { - byte[] addressWithCheckSumAndNetworkCode = Base58.decodeBase58(address); - if (addressWithCheckSumAndNetworkCode[0] != 0 && addressWithCheckSumAndNetworkCode[0] != 111 && addressWithCheckSumAndNetworkCode[0] != 48 && addressWithCheckSumAndNetworkCode[0] != 49) { - throw new BitcoinException(BitcoinException.ERR_UNSUPPORTED, "Unknown address type", address); - } - - byte[] bareAddress = new byte[20]; - System.arraycopy(addressWithCheckSumAndNetworkCode, 1, bareAddress, 0, bareAddress.length); - - MessageDigest digestSha = MessageDigest.getInstance("SHA-256"); - digestSha.update(addressWithCheckSumAndNetworkCode, 0, addressWithCheckSumAndNetworkCode.length - 4); - - byte[] calculatedDigest = digestSha.digest(digestSha.digest()); - for (int i = 0; i < 4; i++) { - if (calculatedDigest[i] != addressWithCheckSumAndNetworkCode[addressWithCheckSumAndNetworkCode.length - 4 + i]) { - throw new BitcoinException(BitcoinException.ERR_BAD_FORMAT, "Bad address", address); - } - } - - ByteArrayOutputStream buf = new ByteArrayOutputStream(25); - buf.write(OP_DUP); - buf.write(OP_HASH160); - writeBytes(bareAddress, buf); - buf.write(OP_EQUALVERIFY); - buf.write(OP_CHECKSIG); - return new Script(buf.toByteArray()); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(e); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - } -} diff --git a/app/src/main/java/com/tangem/wallet/UnspentOutputInfo.java b/app/src/main/java/com/tangem/wallet/UnspentOutputInfo.java deleted file mode 100644 index d2f790b24e..0000000000 --- a/app/src/main/java/com/tangem/wallet/UnspentOutputInfo.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.tangem.wallet; - -/** - * Created by Ilia on 29.09.2017. - */ - -@SuppressWarnings("WeakerAccess") -public class UnspentOutputInfo { - public final byte[] txHash; - public final Transaction.Script script; - public final long value; - public final int outputIndex; - public final long confirmations; - public String txHashForBuild; - public byte[] scriptForBuild; - - public UnspentOutputInfo(byte[] txHash, Transaction.Script script, long value, int outputIndex, long confirmations, String hashForBuild, byte[] sign) { - this.txHash = txHash; - this.script = script; - this.value = value; - this.outputIndex = outputIndex; - this.confirmations = confirmations; - this.txHashForBuild = hashForBuild; - this.scriptForBuild = sign; - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/bch/BitcoinCashAddressDecodedParts.java b/app/src/main/java/com/tangem/wallet/bch/BitcoinCashAddressDecodedParts.java deleted file mode 100644 index 104c19c31f..0000000000 --- a/app/src/main/java/com/tangem/wallet/bch/BitcoinCashAddressDecodedParts.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.tangem.wallet.bch; - -// Helper class for CashAddr - -public class BitcoinCashAddressDecodedParts { - - String prefix; - - BitcoinCashAddressType addressType; - - byte[] hash; - - public String getPrefix() { - return prefix; - } - - public void setPrefix(String prefix) { - this.prefix = prefix; - } - - public BitcoinCashAddressType getAddressType() { - return addressType; - } - - public void setAddressType(BitcoinCashAddressType addressType) { - this.addressType = addressType; - } - - public byte[] getHash() { - return hash; - } - - public void setHash(byte[] hash) { - this.hash = hash; - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/bch/BitcoinCashAddressType.java b/app/src/main/java/com/tangem/wallet/bch/BitcoinCashAddressType.java deleted file mode 100644 index 1beab277b6..0000000000 --- a/app/src/main/java/com/tangem/wallet/bch/BitcoinCashAddressType.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.tangem.wallet.bch; - - -/** - * Copyright (c) 2018 Tobias Brandt - * - * Distributed under the MIT software license, see the accompanying file LICENSE - * or http://www.opensource.org/licenses/mit-license.php. - */ -public enum BitcoinCashAddressType { - - P2PKH((byte) 0), P2SH((byte) 8); - - private final byte versionByte; - - BitcoinCashAddressType(byte versionByte) { - this.versionByte = versionByte; - } - - public byte getVersionByte() { - return versionByte; - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/bch/BitcoinCashBase32.java b/app/src/main/java/com/tangem/wallet/bch/BitcoinCashBase32.java deleted file mode 100644 index 5e1a6a8c5d..0000000000 --- a/app/src/main/java/com/tangem/wallet/bch/BitcoinCashBase32.java +++ /dev/null @@ -1,74 +0,0 @@ -package com.tangem.wallet.bch; - -import java.util.HashMap; -import java.util.Map; - -/** - * Copyright (c) 2018 Tobias Brandt - * - * Distributed under the MIT software license, see the accompanying file LICENSE - * or http://www.opensource.org/licenses/mit-license.php. - */ -public class BitcoinCashBase32 { - - public static final String CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"; - - private static final char[] CHARS = CHARSET.toCharArray(); - - private static Map charPositionMap; - static { - charPositionMap = new HashMap<>(); - for (int i = 0; i < CHARS.length; i++) { - charPositionMap.put(CHARS[i], i); - } - if (charPositionMap.size() != 32) { - throw new RuntimeException("The charset must contain 32 unique characters."); - } - } - - /** - * Encode a byte array as base32 string. This method assumes that all bytes - * are only from 0-31 - * - * @param byteArray - * @return - */ - public static String encode(byte[] byteArray) { - StringBuffer sb = new StringBuffer(); - - for (int i = 0; i < byteArray.length; i++) { - int val = (int) byteArray[i]; - - if (val < 0 || val > 31) { - throw new RuntimeException("This method assumes that all bytes are only from 0-31. Was: " + val); - } - - sb.append(CHARS[val]); - } - - return sb.toString(); - } - - /** - * Decode a base32 string back to the byte array representation - * - * @param base32String - * @return - */ - public static byte[] decode(String base32String) { - byte[] bytes = new byte[base32String.length()]; - - char[] charArray = base32String.toCharArray(); - for (int i = 0; i < charArray.length; i++) { - Integer position = charPositionMap.get(charArray[i]); - if (position == null) { - throw new RuntimeException("There seems to be an invalid char: " + charArray[i]); - } - bytes[i] = (byte) ((int) position); - } - - return bytes; - } -} - - \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/bch/BitcoinCashBitArrayConverter.java b/app/src/main/java/com/tangem/wallet/bch/BitcoinCashBitArrayConverter.java deleted file mode 100644 index 3f92f9445c..0000000000 --- a/app/src/main/java/com/tangem/wallet/bch/BitcoinCashBitArrayConverter.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.tangem.wallet.bch; -/** - * Copyright (c) 2018 Tobias Brandt - * - * Copyright (c) 2017 Pieter Wuille - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ -public class BitcoinCashBitArrayConverter { - - public static byte[] convertBits(byte[] bytes8Bits, int from, int to, boolean strictMode) { - int length = (int) (strictMode ? Math.floor((double) bytes8Bits.length * from / to) - : Math.ceil((double) bytes8Bits.length * from / to)); - int mask = ((1 << to) - 1) & 0xff; - byte[] result = new byte[length]; - int index = 0; - int accumulator = 0; - int bits = 0; - for (int i = 0; i < bytes8Bits.length; i++) { - byte value = bytes8Bits[i]; - accumulator = (((accumulator & 0xff) << from) | (value & 0xff)); - bits += from; - while (bits >= to) { - bits -= to; - result[index] = (byte) ((accumulator >> bits) & mask); - ++index; - } - } - if (!strictMode) { - if (bits > 0) { - result[index] = (byte) ((accumulator << (to - bits)) & mask); - ++index; - } - } else { - if (!(bits < from && ((accumulator << (to - bits)) & mask) == 0)) { - throw new RuntimeException("Strict mode was used but input couldn't be converted without padding"); - } - } - - return result; - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/bch/BitcoinCashNode.kt b/app/src/main/java/com/tangem/wallet/bch/BitcoinCashNode.kt deleted file mode 100644 index 3355580efe..0000000000 --- a/app/src/main/java/com/tangem/wallet/bch/BitcoinCashNode.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.tangem.wallet.bch - -enum class BitcoinCashNode(val host: String, val port: Int, val proto: String) { - N_001("electrumx.hillsideinternet.com", 50002, "ssl"), - N_002("blackie.c3-soft.com", 50002, "ssl"), - N_003("electrum.imaginary.cash", 50002, "ssl"), -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/bch/BtcCashEngine.java b/app/src/main/java/com/tangem/wallet/bch/BtcCashEngine.java deleted file mode 100644 index 5694d0ad4a..0000000000 --- a/app/src/main/java/com/tangem/wallet/bch/BtcCashEngine.java +++ /dev/null @@ -1,730 +0,0 @@ -package com.tangem.wallet.bch; - -import android.net.Uri; -import android.text.InputFilter; -import android.util.Log; - -import com.tangem.App; -import com.tangem.data.network.ServerApiBlockchair; -import com.tangem.data.network.ServerApiPayId; -import com.tangem.data.network.model.BlockchairAddressData; -import com.tangem.data.network.model.BlockchairAddressResponse; -import com.tangem.data.network.model.BlockchairStatsResponse; -import com.tangem.data.network.model.BlockchairTransactionResponse; -import com.tangem.data.network.model.BlockchairUnspentOutput; -import com.tangem.data.network.model.PayIdAddress; -import com.tangem.data.network.model.PayIdResponse; -import com.tangem.tangem_card.data.TangemCard; -import com.tangem.tangem_card.reader.CardProtocol; -import com.tangem.tangem_card.tasks.SignTask; -import com.tangem.tangem_card.util.Util; -import com.tangem.util.CryptoUtil; -import com.tangem.util.DecimalDigitsInputFilter; -import com.tangem.util.DerEncodingUtil; -import com.tangem.wallet.BCHUtils; -import com.tangem.wallet.BTCUtils; -import com.tangem.wallet.BalanceValidator; -import com.tangem.wallet.CoinData; -import com.tangem.wallet.CoinEngine; -import com.tangem.wallet.R; -import com.tangem.wallet.TangemContext; -import com.tangem.wallet.Transaction; -import com.tangem.wallet.UnspentOutputInfo; -import com.tangem.wallet.btc.BtcData; -import com.tangem.wallet.btc.Unspents; - -import java.io.ByteArrayOutputStream; -import java.math.BigDecimal; -import java.math.BigInteger; -import java.math.RoundingMode; -import java.nio.ByteBuffer; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import java.util.ArrayList; -import java.util.Arrays; - -import io.reactivex.CompletableObserver; -import io.reactivex.SingleObserver; -import io.reactivex.observers.DisposableCompletableObserver; -import io.reactivex.observers.DisposableSingleObserver; - -public class BtcCashEngine extends CoinEngine { - - private static final String TAG = BtcCashEngine.class.getSimpleName(); - public BtcData coinData = null; - - public BtcCashEngine(TangemContext context) throws Exception { - super(context); - if (context.getCoinData() == null) { - coinData = new BtcData(); - context.setCoinData(coinData); - } else if (context.getCoinData() instanceof BtcData) { - coinData = (BtcData) context.getCoinData(); - } else { - throw new Exception("Invalid type of Blockchain data for BtcEngine"); - } - } - - public BtcCashEngine() { - } - - private static int getDecimals() { - return 8; - } - - private void checkBlockchainDataExists() throws Exception { - if (coinData == null) throw new Exception("No blockchain data"); - } - - @Override - public boolean awaitingConfirmation() { - if (coinData == null) return false; - return coinData.getBalanceUnconfirmed() != 0 || coinData.isHasUnconfirmed() || App.pendingTransactionsStorage.hasTransactions(ctx.getCard()); - } - - @Override - public String getBalanceHTML() { - Amount balance = getBalance(); - if (balance != null) { - return balance.toDescriptionString(getDecimals()); - } else { - return ""; - } - } - - @Override - public String getBalanceCurrency() { - return "BCH"; - } - - @Override - public boolean isBalanceNotZero() { - if (coinData == null) return false; - if (coinData.getBalanceInInternalUnits() == null) return false; - return coinData.getBalanceInInternalUnits().notZero(); - } - - @Override - public boolean hasBalanceInfo() { - if (coinData == null) return false; - return coinData.hasBalanceInfo(); - } - - @Override - public boolean isExtractPossible() { - if (!hasBalanceInfo()) { - ctx.setMessage(R.string.loaded_wallet_error_obtaining_blockchain_data); - } else if (!isBalanceNotZero()) { - ctx.setMessage(R.string.general_wallet_empty); - } else if (awaitingConfirmation()) { - ctx.setMessage(R.string.loaded_wallet_message_wait); - } else if (coinData.getUnspentTransactions().size() == 0) { - ctx.setMessage(R.string.loaded_wallet_message_refresh); - } else { - return true; - } - return false; - } - - @Override - public String getFeeCurrency() { - return "BCH"; - } - - @Override - public boolean validateAddress(String address) { - if (address != null && address.contains("$")) { // PayID - return validatePayId(address); - } - - return CashAddr.isValidCashAddress(address); - } - - @Override - public boolean isNeedCheckNode() { - return true; - } - - @Override - public Uri getWalletExplorerUri() { - return Uri.parse("https://blockchair.com/bitcoin-cash/address/" + ctx.getCoinData().getWallet()); - } - - @Override - public Uri getShareWalletUri() { - return Uri.parse(ctx.getCoinData().getWallet()); - } - - @Override - public InputFilter[] getAmountInputFilters() { - return new InputFilter[]{new DecimalDigitsInputFilter(getDecimals())}; - } - - @Override - public boolean checkNewTransactionAmount(Amount amount) { - if (coinData == null) return false; - if (amount.compareTo(convertToAmount(coinData.getBalanceInInternalUnits())) > 0) { - return false; - } - return true; - } - - @Override - public boolean checkNewTransactionAmountAndFee(Amount amountValue, Amount feeValue, Boolean isIncludeFee) { - InternalAmount fee; - InternalAmount amount; - - try { - checkBlockchainDataExists(); - amount = convertToInternalAmount(amountValue); - fee = convertToInternalAmount(feeValue); - } catch (Exception e) { - e.printStackTrace(); - return false; - } - - if (fee == null || amount == null) - return false; - - if (fee.isZero() || amount.isZero()) - return false; - - if (isIncludeFee && (amount.compareTo(coinData.getBalanceInInternalUnits()) > 0 || amount.compareTo(fee) < 0)) - return false; - - if (!isIncludeFee && amount.add(fee).compareTo(coinData.getBalanceInInternalUnits()) > 0) - return false; - - return true; - } - - @Override - public boolean validateBalance(BalanceValidator balanceValidator) { - if (((ctx.getCard().getOfflineBalance() == null) && !ctx.getCoinData().isBalanceReceived()) || (!ctx.getCoinData().isBalanceReceived() && (ctx.getCard().getRemainingSignatures() != ctx.getCard().getMaxSignatures()))) { - balanceValidator.setScore(0); - balanceValidator.setFirstLine(R.string.balance_validator_first_line_unknown_balance); - balanceValidator.setSecondLine(R.string.balance_validator_second_line_unverified_balance); - return false; - } - - if (coinData.getBalanceUnconfirmed() != 0 || coinData.isHasUnconfirmed()) { - balanceValidator.setScore(0); - balanceValidator.setFirstLine(R.string.balance_validator_first_line_transaction_in_progress); - balanceValidator.setSecondLine(R.string.balance_validator_second_line_wait_for_confirmation); - return false; - } - - if (coinData.isBalanceReceived() && coinData.isBalanceEqual()) { - balanceValidator.setScore(100); - balanceValidator.setFirstLine(R.string.balance_validator_first_line_verified_balance); - balanceValidator.setSecondLine(R.string.balance_validator_second_line_confirmed_in_blockchain); - if (coinData.getBalanceInInternalUnits().isZero()) { - balanceValidator.setFirstLine(R.string.balance_validator_first_line_empty_wallet); - balanceValidator.setSecondLine(R.string.empty_string); - } - } - return true; - } - - @Override - public Amount getBalance() { - return convertToAmount(coinData.getBalanceInInternalUnits()); - } - - @Override - public String evaluateFeeEquivalent(String fee) { - if (!coinData.getAmountEquivalentDescriptionAvailable()) return ""; - try { - Amount feeAmount = new Amount(fee, getFeeCurrency()); - return feeAmount.toEquivalentString(coinData.getRate()); - } catch (Exception e) { - return ""; - } - } - - @Override - public String getBalanceEquivalent() { - if (coinData == null || !coinData.getAmountEquivalentDescriptionAvailable()) return ""; - Amount balance = getBalance(); - if (balance == null) return ""; - return balance.toEquivalentString(coinData.getRate()); - } - - @Override - public String calculateAddress(byte[] pubKey) throws NoSuchProviderException, NoSuchAlgorithmException { - - // CashAddr format - byte hash1[] = Util.calculateSHA256(pubKey); - byte hash2[] = Util.calculateRIPEMD160(hash1); - return CashAddr.toCashAddress(BitcoinCashAddressType.P2PKH, hash2); - - } - - public String calculateLegacyAddress(byte[] pkUncompressed) throws NoSuchProviderException, NoSuchAlgorithmException { - - // Legacy format (BTC) - byte netSelectionByte = (byte) 0x00; - - byte hash1[] = Util.calculateSHA256(pkUncompressed); - byte hash2[] = Util.calculateRIPEMD160(hash1); - - ByteBuffer BB = ByteBuffer.allocate(hash2.length + 1); - - BB.put(netSelectionByte); - BB.put(hash2); - - byte hash3[] = Util.calculateSHA256(BB.array()); - byte hash4[] = Util.calculateSHA256(hash3); - - BB = ByteBuffer.allocate(hash2.length + 5); - BB.put(netSelectionByte); - BB.put(hash2); - BB.put(hash4[0]); - BB.put(hash4[1]); - BB.put(hash4[2]); - BB.put(hash4[3]); - - return org.bitcoinj.core.Base58.encode(BB.array()); - } - - public String convertToLegacyAddress(String cashAddr) throws NoSuchProviderException, NoSuchAlgorithmException { - - BitcoinCashAddressDecodedParts bcadp = CashAddr.decodeCashAddress(cashAddr); - byte netSelectionByte = (byte) 0x00; - byte hash2[] = bcadp.hash; - - ByteBuffer BB = ByteBuffer.allocate(hash2.length + 1); - - BB.put(netSelectionByte); - BB.put(hash2); - - byte hash3[] = Util.calculateSHA256(BB.array()); - byte hash4[] = Util.calculateSHA256(hash3); - - BB = ByteBuffer.allocate(hash2.length + 5); - BB.put(netSelectionByte); - BB.put(hash2); - BB.put(hash4[0]); - BB.put(hash4[1]); - BB.put(hash4[2]); - BB.put(hash4[3]); - - return org.bitcoinj.core.Base58.encode(BB.array()); - } - - @Override - public Amount convertToAmount(InternalAmount internalAmount) { - BigDecimal d = internalAmount.divide(new BigDecimal("100000000")); - return new Amount(d, getBalanceCurrency()); - } - - @Override - public Amount convertToAmount(String strAmount, String currency) { - return new Amount(strAmount, currency); - } - - @Override - public InternalAmount convertToInternalAmount(Amount amount) throws Exception { - BigDecimal d = amount.multiply(new BigDecimal("100000000")); - return new InternalAmount(d, getBalanceCurrency()); - } - - @Override - public InternalAmount convertToInternalAmount(byte[] bytes) { - if (bytes == null) return null; - byte[] reversed = new byte[bytes.length]; - for (int i = 0; i < bytes.length; i++) reversed[i] = bytes[bytes.length - i - 1]; - return new InternalAmount(Util.byteArrayToLong(reversed), "Satoshi"); - } - - @Override - public byte[] convertToByteArray(InternalAmount internalAmount) throws Exception { - byte[] bytes = Util.longToByteArray(internalAmount.longValueExact()); - byte[] reversed = new byte[bytes.length]; - for (int i = 0; i < bytes.length; i++) reversed[i] = bytes[bytes.length - i - 1]; - return reversed; - } - - @Override - public CoinData createCoinData() { - return new BtcData(); - } - - @Override - public String getUnspentInputsDescription() { - Unspents unspents = coinData.getUnspentInputsDescription(); - if (unspents == null) { - return ""; - } else { - return String.format( - ctx.getContext().getString(R.string.details_unspents_number), - unspents.getUnspetns(), - unspents.getGatheredUnspents()); - } - } - - @Override - public void defineWallet() throws CardProtocol.TangemException { - try { - String wallet = calculateAddress(ctx.getCard().getWalletPublicKeyRar()); - ctx.getCoinData().setWallet(wallet); - } catch (Exception e) { - ctx.getCoinData().setWallet("ERROR"); - throw new CardProtocol.TangemException("Can't define wallet address"); - } - - } - - - @Override - public SignTask.TransactionToSign constructTransaction(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) throws Exception { - checkBlockchainDataExists(); - - String destination; - //PayID - if (coinData.getResolvedPayIdAddress() != null) { - destination = coinData.getResolvedPayIdAddress(); - } else { - destination = targetAddress; - } - - String srcLegacyAddress = convertToLegacyAddress(ctx.getCoinData().getWallet()); - String destLegacyAddress = convertToLegacyAddress(destination); - byte[] pbKey = ctx.getCard().getWalletPublicKeyRar(); //ALWAYS USING COMPRESS KEY - - final ArrayList unspentOutputs = new ArrayList<>(); - for (BtcData.UnspentTransaction utxo : coinData.getUnspentTransactions()) { - unspentOutputs.add(new UnspentOutputInfo(BTCUtils.fromHex(utxo.txID), new Transaction.Script(BTCUtils.fromHex(utxo.script)), utxo.amount, utxo.outputN, -1, utxo.txID, null)); - } - - long fullAmount = 0; - for (int i = 0; i < unspentOutputs.size(); ++i) { - fullAmount += unspentOutputs.get(i).value; - } - - - long fees = convertToInternalAmount(feeValue).longValueExact(); - long amount = convertToInternalAmount(amountValue).longValueExact(); - long change = fullAmount - amount; - if (IncFee) { - amount = amount - fees; - } else { - change = change - fees; - } - - if (amount + fees > fullAmount) { - throw new CardProtocol.TangemException_WrongAmount(String.format("Balance (%d) < change (%d) + amount (%d)", fullAmount, change, amount)); - } - - final long amountFinal = amount; - final long changeFinal = change; - - byte[][] txForSign = new byte[unspentOutputs.size()][]; - byte[][] bodyHash = new byte[unspentOutputs.size()][]; - byte[][] bodyDoubleHash = new byte[unspentOutputs.size()][]; - - for (int i = 0; i < unspentOutputs.size(); ++i) { - txForSign[i] = BCHUtils.buildTXForSign(srcLegacyAddress, destLegacyAddress, srcLegacyAddress, unspentOutputs, i, amount, change); - bodyHash[i] = Util.calculateSHA256(txForSign[i]); - bodyDoubleHash[i] = Util.calculateSHA256(bodyHash[i]); - } - - return new SignTask.TransactionToSign() { - - @Override - public boolean isSigningMethodSupported(TangemCard.SigningMethod signingMethod) { - return signingMethod == TangemCard.SigningMethod.Sign_Hash || signingMethod == TangemCard.SigningMethod.Sign_Raw; - } - - @Override - public byte[][] getHashesToSign() throws Exception { - byte[][] dataForSign = new byte[unspentOutputs.size()][]; - for (int i = 0; i < unspentOutputs.size(); ++i) { - dataForSign[i] = bodyDoubleHash[i]; - } - return dataForSign; - } - - @Override - public byte[] getRawDataToSign() throws Exception { - ByteArrayOutputStream bs = new ByteArrayOutputStream(); - for (int i = 0; i < txForSign.length; i++) { - if (i != 0 && txForSign[0].length != txForSign[i].length) - throw new Exception("Hashes length must be identical!"); - bs.write(txForSign[i]); - } - - return bs.toByteArray(); - } - - @Override - public String getHashAlgToSign() { - return "sha-256x2"; - } - - @Override - public byte[] getIssuerTransactionSignature(byte[] dataToSignByIssuer) throws Exception { - throw new Exception("Transaction validation by issuer not supported in this version!"); - } - - @Override - public byte[] onSignCompleted(byte[] signFromCard) throws Exception { - for (int i = 0; i < unspentOutputs.size(); ++i) { - BigInteger r = new BigInteger(1, Arrays.copyOfRange(signFromCard, i * 64, 32 + i * 64)); - BigInteger s = new BigInteger(1, Arrays.copyOfRange(signFromCard, 32 + i * 64, 64 + i * 64)); - s = CryptoUtil.toCanonicalised(s); - - unspentOutputs.get(i).scriptForBuild = DerEncodingUtil.packSignDerBitcoinCash(r, s, pbKey); - } - - byte[] txForSend = BCHUtils.buildTXForSend(destLegacyAddress, srcLegacyAddress, unspentOutputs, amountFinal, changeFinal); - - notifyOnNeedSendTransaction(txForSend); - return txForSend; - } - }; - } - - @Override - public void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) throws Exception { - final ServerApiBlockchair serverApiBlockchair = new ServerApiBlockchair(ctx.getBlockchain()); - - SingleObserver addressObserver = new DisposableSingleObserver() { - @Override - public void onSuccess(BlockchairAddressResponse addressResponse) { - try { - BlockchairAddressData addressData = addressResponse.getData().get(coinData.getWallet()); - - coinData.setBalanceConfirmed(addressData.getAddress().getBalance()); - coinData.setBalanceUnconfirmed(0L); - coinData.setBalanceReceived(true); - coinData.setValidationNodeDescription(serverApiBlockchair.getUrl()); - coinData.setSentTransactionsCount(addressData.getAddress().getOutputCount() - addressData.getAddress().getUnspentOutputCount()); - - for (BlockchairUnspentOutput utxo : addressData.getUnspentOutputs()) { - if (utxo.getBlock() != -1) { - BtcData.UnspentTransaction unspentTx = new BtcData.UnspentTransaction(); - unspentTx.txID = utxo.getTransactionHash(); - unspentTx.amount = utxo.getAmount(); - unspentTx.outputN = utxo.getIndex(); - coinData.getUnspentTransactions().add(unspentTx); - } else { - coinData.setHasUnconfirmed(true); - } - } - - if (addressData.getAddress().getBalance() != 0) { - blockchainRequestsCallbacks.onComplete(true); - } else { //check if there is an unconfirmed tx, which spent all funds - if (addressData.getTransactions().isEmpty()) { - blockchainRequestsCallbacks.onComplete(true); - } else { - requestIsTransactionConfirmed(addressData.getTransactions().get(0), blockchainRequestsCallbacks); - } - } - } catch (Exception e) { - e.printStackTrace(); - Log.e(TAG, "FAIL getAddress Exception"); - ctx.setError(e.getMessage()); - blockchainRequestsCallbacks.onComplete(false); - } - } - - @Override - public void onError(Throwable e) { - e.printStackTrace(); - Log.e(TAG, "FAIL getAddress Exception"); - ctx.setError(e.getMessage()); - blockchainRequestsCallbacks.onComplete(false); - } - }; - - serverApiBlockchair.getAddress(coinData.getWallet(), addressObserver); - } - - private void requestIsTransactionConfirmed(String transaction, BlockchainRequestsCallbacks blockchainRequestsCallbacks) { - final ServerApiBlockchair serverApiBlockchair = new ServerApiBlockchair(ctx.getBlockchain()); - - SingleObserver transactionObserver = new DisposableSingleObserver() { - @Override - public void onSuccess(BlockchairTransactionResponse transactionResponse) { - if (transactionResponse.getData().get(transaction).getTransaction().getBlock() == -1) { - coinData.setHasUnconfirmed(true); - } - blockchainRequestsCallbacks.onComplete(true); - } - - @Override - public void onError(Throwable e) { - e.printStackTrace(); - Log.e(TAG, "FAIL getTransaction Exception"); - ctx.setError(e.getMessage()); - blockchainRequestsCallbacks.onComplete(false); - } - }; - - serverApiBlockchair.getTransaction(transaction, transactionObserver); - } - - private Integer calculateEstimatedTransactionSize(String outputAddress, String outAmount) { - try { - SignTask.TransactionToSign ps = constructTransaction(new Amount(outAmount, getBalanceCurrency()), new Amount("0.00", getFeeCurrency()), true, outputAddress); - OnNeedSendTransaction onNeedSendTransactionBackup = onNeedSendTransaction; - onNeedSendTransaction = (tx) -> { - }; // empty function to bypass exception - - byte[][] hashesToSign = ps.getHashesToSign(); - byte[] signFromCard = new byte[64 * hashesToSign.length]; - Arrays.fill(signFromCard, (byte) 0x01); - byte[] txForSend = ps.onSignCompleted(signFromCard); - onNeedSendTransaction = onNeedSendTransactionBackup; - Log.e(TAG, "txForSend.length=" + String.valueOf(txForSend.length)); - return txForSend.length + 1; - } catch (Exception e) { - e.printStackTrace(); - Log.e(TAG, "Can't calculate transaction size -> use default!"); - return 256; - } - } - - private final static BigDecimal relayFee = new BigDecimal(0.00001); - - @Override - public void requestFee(BlockchainRequestsCallbacks blockchainRequestsCallbacks, String targetAddress, Amount amount) throws Exception { - - CompletableObserver payIdObserver = new DisposableCompletableObserver() { - @Override - public void onComplete() { - int calcSize = calculateEstimatedTransactionSize(coinData.getResolvedPayIdAddress(), amount.toValueString()); - Log.e(TAG, String.format("Estimated tx size %d", calcSize)); - - checkFee(blockchainRequestsCallbacks, calcSize); - - blockchainRequestsCallbacks.onComplete(true); - } - - @Override - public void onError(Throwable e) { - ctx.setError(e.getMessage()); - blockchainRequestsCallbacks.onComplete(false); - } - }; - - if (targetAddress.contains("$")) { // PayID - resolvePayID(targetAddress, payIdObserver); - } else { - int calcSize = calculateEstimatedTransactionSize(targetAddress, amount.toValueString()); - Log.e(TAG, String.format("Estimated tx size %d", calcSize)); - - checkFee(blockchainRequestsCallbacks, calcSize); - - blockchainRequestsCallbacks.onComplete(true); - } - } - - private void checkFee(BlockchainRequestsCallbacks blockchainRequestsCallbacks, int calcSize) { - coinData.minFee = null; - coinData.maxFee = null; - coinData.normalFee = null; - - final ServerApiBlockchair serverApiBlockchair = new ServerApiBlockchair(ctx.getBlockchain()); - - SingleObserver statsObserver = new DisposableSingleObserver() { - @Override - public void onSuccess(BlockchairStatsResponse blockchairStatsResponse) { - try { - int feeSatoshi = blockchairStatsResponse.getData().getFeePerByte() * calcSize; - BigDecimal fee = BigDecimal.valueOf(feeSatoshi).movePointLeft(getDecimals()); - if (fee.compareTo(relayFee) < 0) { - fee = relayFee; - } - Amount feeAmount = new Amount(fee.setScale(getDecimals(), RoundingMode.DOWN), getFeeCurrency()); - coinData.minFee = feeAmount; - coinData.normalFee = feeAmount; - coinData.maxFee = feeAmount; - blockchainRequestsCallbacks.onComplete(true); - } catch (Exception e) { - e.printStackTrace(); - Log.e(TAG, "FAIL getStats Exception"); - ctx.setError(e.getMessage()); - blockchainRequestsCallbacks.onComplete(false); - } - } - - @Override - public void onError(Throwable e) { - e.printStackTrace(); - Log.e(TAG, "FAIL getStats Exception"); - ctx.setError(e.getMessage()); - blockchainRequestsCallbacks.onComplete(false); - } - }; - - serverApiBlockchair.getStats(statsObserver); - } - - private void resolvePayID(String targetAddress, CompletableObserver observer) { - final ServerApiPayId serverApiPayId = new ServerApiPayId(); - - SingleObserver payIdObserver = new DisposableSingleObserver() { - @Override - public void onSuccess(PayIdResponse payIdResponse) { - try { - String resolvedAddress = null; - for (PayIdAddress address : payIdResponse.getAddresses()) { - if (address.getPaymentNetwork().equals(ctx.getBlockchain().getCurrency()) && - address.getEnvironment().equals("MAINNET")) { - resolvedAddress = address.getAddressDetails().getAddress(); - break; - } - } - if (validateAddress(resolvedAddress)) { - if (!resolvedAddress.equals(coinData.getWallet())) { - coinData.setResolvedPayIdAddress(resolvedAddress); - observer.onComplete(); - } else { - observer.onError(new Exception("Resolved PayID address equals source address")); - } - } else { - observer.onError(new Exception("Unknown address format in PayID response")); - } - } catch (Exception e) { - observer.onError(new Exception("Unknown response format on PayID request")); - } - } - - @Override - public void onError(Throwable e) { - Log.i(TAG, "onFail: " + "payID" + " " + e.getMessage()); - observer.onError(new Exception("PayID error:" + e.getMessage())); - } - }; - - serverApiPayId.getAddress(targetAddress, ctx.getBlockchain(), payIdObserver); - } - - @Override - public void requestSendTransaction(BlockchainRequestsCallbacks blockchainRequestsCallbacks, byte[] txForSend) throws Exception { - final ServerApiBlockchair serverApiBlockchair = new ServerApiBlockchair(ctx.getBlockchain()); - - CompletableObserver sendObserver = new DisposableCompletableObserver() { - @Override - public void onComplete() { - blockchainRequestsCallbacks.onComplete(true); - } - - @Override - public void onError(Throwable e) { - ctx.setError(e.getMessage()); - blockchainRequestsCallbacks.onComplete(false); - } - }; - - serverApiBlockchair.sendTransaction(Util.byteArrayToHexString(txForSend), sendObserver); - } - - @Override - public boolean allowSelectFeeLevel() { - return false; - } -} diff --git a/app/src/main/java/com/tangem/wallet/bch/CashAddr.java b/app/src/main/java/com/tangem/wallet/bch/CashAddr.java deleted file mode 100644 index 25c6851c16..0000000000 --- a/app/src/main/java/com/tangem/wallet/bch/CashAddr.java +++ /dev/null @@ -1,225 +0,0 @@ -package com.tangem.wallet.bch; - -import java.math.BigInteger; -import java.util.Arrays; - - -/** - * Copyright (c) 2018 Tobias Brandt - * - * Distributed under the MIT software license, see the accompanying file LICENSE - * or http://www.opensource.org/licenses/mit-license.php. - */ - - -public class CashAddr { - - public static final String CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"; - - private static final char[] CHARS = CHARSET.toCharArray(); - - - public static final String SEPARATOR = ":"; - - public static final String MAIN_NET_PREFIX = "bitcoincash"; - - public static final String TEST_NET_PREFIX = "bchtest"; - - public static final String ASSUMED_DEFAULT_PREFIX = MAIN_NET_PREFIX; - - private static final BigInteger[] POLYMOD_GENERATORS = new BigInteger[] { new BigInteger("98f2bc8e61", 16), - new BigInteger("79b76d99e2", 16), new BigInteger("f33e5fb3c4", 16), new BigInteger("ae2eabe2a8", 16), - new BigInteger("1e4f43e470", 16) }; - - private static final BigInteger POLYMOD_AND_CONSTANT = new BigInteger("07ffffffff", 16); - - public static String toCashAddress(BitcoinCashAddressType addressType, byte[] hash) { - String prefixString = MAIN_NET_PREFIX; - byte[] prefixBytes = getPrefixBytes(prefixString); - byte[] payloadBytes = concatenateByteArrays(new byte[] { addressType.getVersionByte() }, hash); - payloadBytes = convertBits(payloadBytes, 8, 5, false); - byte[] allChecksumInput = concatenateByteArrays( - concatenateByteArrays(concatenateByteArrays(prefixBytes, new byte[] { 0 }), payloadBytes), - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }); - byte[] checksumBytes = calculateChecksumBytesPolymod(allChecksumInput); - checksumBytes = convertBits(checksumBytes, 8, 5, true); - String cashAddress = BitcoinCashBase32.encode(concatenateByteArrays(payloadBytes, checksumBytes)); - return prefixString + SEPARATOR + cashAddress; - } - - public static BitcoinCashAddressDecodedParts decodeCashAddress(String bitcoinCashAddress) { - if (!isValidCashAddress(bitcoinCashAddress)) { - throw new RuntimeException("Address wasn't valid: " + bitcoinCashAddress); - } - - BitcoinCashAddressDecodedParts decoded = new BitcoinCashAddressDecodedParts(); - String[] addressParts = bitcoinCashAddress.split(SEPARATOR); - if (addressParts.length == 2) { - decoded.setPrefix(addressParts[0]); - } else { - decoded.setPrefix(MAIN_NET_PREFIX); - } - - byte[] addressData = BitcoinCashBase32.decode(addressParts[addressParts.length - 1]); - addressData = Arrays.copyOfRange(addressData, 0, addressData.length - 8); - addressData = BitcoinCashBitArrayConverter.convertBits(addressData, 5, 8, true); - byte versionByte = addressData[0]; - byte[] hash = Arrays.copyOfRange(addressData, 1, addressData.length); - - decoded.setAddressType(getAddressTypeFromVersionByte(versionByte)); - decoded.setHash(hash); - - return decoded; - } - - private static BitcoinCashAddressType getAddressTypeFromVersionByte(byte versionByte) { - for (BitcoinCashAddressType addressType : BitcoinCashAddressType.values()) { - if (addressType.getVersionByte() == versionByte) { - return addressType; - } - } - - throw new RuntimeException("Unknown version byte: " + versionByte); - } - - - - public static boolean isValidCashAddress(String bitcoinCashAddress ) { - try { - if (!isSingleCase(bitcoinCashAddress)) - return false; - - bitcoinCashAddress = bitcoinCashAddress.toLowerCase(); - String prefix; - - if (bitcoinCashAddress.contains(SEPARATOR)) { - String[] split = bitcoinCashAddress.split(SEPARATOR); - prefix = split[0]; - if (!prefix.equals(MAIN_NET_PREFIX)) {return false;} //for now we use main net only - bitcoinCashAddress = split[1]; - } else { - prefix = MAIN_NET_PREFIX; - } - if (!bitcoinCashAddress.startsWith("q")) {return false;} //for now we use P2PKH addresses only - - byte[] checksumData = concatenateByteArrays( - concatenateByteArrays(getPrefixBytes(prefix ), new byte[] { 0x00 }), - BitcoinCashBase32.decode(bitcoinCashAddress)); - - byte[] calculateChecksumBytesPolymod = calculateChecksumBytesPolymod(checksumData); - return new BigInteger(calculateChecksumBytesPolymod).compareTo(BigInteger.ZERO) == 0; - } catch (RuntimeException re) { - return false; - } - } - - - - private static boolean isSingleCase(String bitcoinCashAddress) { - if (bitcoinCashAddress.equals(bitcoinCashAddress.toLowerCase())) { - return true; - } - if (bitcoinCashAddress.equals(bitcoinCashAddress.toUpperCase())) { - return true; - } - - return false; - } - - /** - * @param checksumInput - * @return Returns a 40 bits checksum in form of 5 8-bit arrays. This still has - * to me mapped to 5-bit array representation - */ - private static byte[] calculateChecksumBytesPolymod(byte[] checksumInput) { - BigInteger c = BigInteger.ONE; - - for (int i = 0; i < checksumInput.length; i++) { - byte c0 = c.shiftRight(35).byteValue(); - c = c.and(POLYMOD_AND_CONSTANT).shiftLeft(5) - .xor(new BigInteger(String.format("%02x", checksumInput[i]), 16)); - - if ((c0 & 0x01) != 0) - c = c.xor(POLYMOD_GENERATORS[0]); - if ((c0 & 0x02) != 0) - c = c.xor(POLYMOD_GENERATORS[1]); - if ((c0 & 0x04) != 0) - c = c.xor(POLYMOD_GENERATORS[2]); - if ((c0 & 0x08) != 0) - c = c.xor(POLYMOD_GENERATORS[3]); - if ((c0 & 0x10) != 0) - c = c.xor(POLYMOD_GENERATORS[4]); - } - - byte[] checksum = c.xor(BigInteger.ONE).toByteArray(); - if (checksum.length == 5) { - return checksum; - } else { - byte[] newChecksumArray = new byte[5]; - - System.arraycopy(checksum, Math.max(0, checksum.length - 5), newChecksumArray, - Math.max(0, 5 - checksum.length), Math.min(5, checksum.length)); - - return newChecksumArray; - } - - } - - private static byte[] getPrefixBytes(String prefixString ) { - byte[] prefixBytes = new byte[prefixString.length()]; - - char[] charArray = prefixString.toCharArray(); - for (int i = 0; i < charArray.length; i++) { - prefixBytes[i] = (byte) (charArray[i] & 0x1f); - } - - return prefixBytes; - } - - private static byte[] concatenateByteArrays(byte[] first, byte[] second) { - byte[] concatenatedBytes = new byte[first.length + second.length]; - - System.arraycopy(first, 0, concatenatedBytes, 0, first.length); - System.arraycopy(second, 0, concatenatedBytes, first.length, second.length); - - return concatenatedBytes; - } - - private static byte[] convertBits(byte[] bytes8Bits, int from, int to, boolean strictMode) { - //Copyright (c) 2017 Pieter Wuille - - int length = (int) (strictMode ? Math.floor((double) bytes8Bits.length * from / to) - : Math.ceil((double) bytes8Bits.length * from / to)); - int mask = ((1 << to) - 1) & 0xff; - byte[] result = new byte[length]; - int index = 0; - int accumulator = 0; - int bits = 0; - for (int i = 0; i < bytes8Bits.length; i++) { - byte value = bytes8Bits[i]; - accumulator = (((accumulator & 0xff) << from) | (value & 0xff)); - bits += from; - while (bits >= to) { - bits -= to; - result[index] = (byte) ((accumulator >> bits) & mask); - ++index; - } - } - if (!strictMode) { - if (bits > 0) { - result[index] = (byte) ((accumulator << (to - bits)) & mask); - ++index; - } - } else { - if (!(bits < from && ((accumulator << (to - bits)) & mask) == 0)) { - throw new RuntimeException("Strict mode was used but input couldn't be converted without padding"); - } - } - - return result; - } - - - - -} diff --git a/app/src/main/java/com/tangem/wallet/binance/BinanceAssetData.java b/app/src/main/java/com/tangem/wallet/binance/BinanceAssetData.java deleted file mode 100644 index 6c06d4dd75..0000000000 --- a/app/src/main/java/com/tangem/wallet/binance/BinanceAssetData.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.tangem.wallet.binance; - -import android.os.Bundle; -import android.util.Log; - -import com.tangem.wallet.CoinEngine; - -public class BinanceAssetData extends BinanceData { - private String assetBalance; - private String assetSymbol; - - @Override - public void loadFromBundle(Bundle B) { - super.loadFromBundle(B); - - if (B.containsKey("AssetBalance")) assetBalance = B.getString("AssetBalance"); - else assetBalance = null; - if (B.containsKey("AssetSymbol")) assetSymbol = B.getString("AssetSymbol"); - else assetSymbol = null; - } - - @Override - public void saveToBundle(Bundle B) { - super.saveToBundle(B); - try { - if (assetBalance != null) B.putString("AssetBalance", assetBalance); - if (assetSymbol != null) B.putString("AssetSymbol", assetSymbol); - } catch (Exception e) { - Log.e("Can't save to bundle ", e.getMessage()); - } - } - - @Override - public void clearInfo() { - super.clearInfo(); - assetBalance = null; - } - - @Override - public boolean hasBalanceInfo() { - return super.hasBalanceInfo() || assetBalance != null; - } - - public CoinEngine.Amount getAssetBalance() { - return assetBalance == null ? null : new CoinEngine.Amount(assetBalance, assetSymbol); - } - - public void setAssetBalance(String assetBalance) { - this.assetBalance = assetBalance; - } - - public void setAssetSymbol(String assetSymbol) { - this.assetSymbol = assetSymbol; - } -} diff --git a/app/src/main/java/com/tangem/wallet/binance/BinanceAssetEngine.java b/app/src/main/java/com/tangem/wallet/binance/BinanceAssetEngine.java deleted file mode 100644 index 71953fc5cf..0000000000 --- a/app/src/main/java/com/tangem/wallet/binance/BinanceAssetEngine.java +++ /dev/null @@ -1,666 +0,0 @@ -package com.tangem.wallet.binance; - -import android.net.Uri; -import android.text.InputFilter; -import android.util.Log; - -import androidx.annotation.NonNull; - -import com.tangem.App; -import com.tangem.data.Blockchain; -import com.tangem.data.network.BinanceApi; -import com.tangem.data.network.Server; -import com.tangem.data.network.ServerApiBinance; -import com.tangem.data.network.ServerApiPayId; -import com.tangem.data.network.model.BinanceFees; -import com.tangem.data.network.model.PayIdAddress; -import com.tangem.data.network.model.PayIdResponse; -import com.tangem.tangem_card.data.TangemCard; -import com.tangem.tangem_card.reader.CardProtocol; -import com.tangem.tangem_card.tasks.SignTask; -import com.tangem.tangem_card.util.Util; -import com.tangem.util.CryptoUtil; -import com.tangem.util.DecimalDigitsInputFilter; -import com.tangem.wallet.BalanceValidator; -import com.tangem.wallet.CoinData; -import com.tangem.wallet.CoinEngine; -import com.tangem.wallet.R; -import com.tangem.wallet.TangemContext; -import com.tangem.wallet.binance.client.BinanceDexApiClientFactory; -import com.tangem.wallet.binance.client.BinanceDexApiRestClient; -import com.tangem.wallet.binance.client.BinanceDexEnvironment; -import com.tangem.wallet.binance.client.domain.broadcast.TransactionOption; -import com.tangem.wallet.binance.client.domain.broadcast.Transfer; -import com.tangem.wallet.binance.client.encoding.Bech32; -import com.tangem.wallet.binance.client.encoding.Crypto; -import com.tangem.wallet.binance.client.encoding.message.MessageType; -import com.tangem.wallet.binance.client.encoding.message.TransactionRequestAssemblerExtSign; -import com.tangem.wallet.binance.client.encoding.message.TransferMessage; - -import org.bitcoinj.core.Utils; - -import java.math.BigDecimal; -import java.math.BigInteger; -import java.math.RoundingMode; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.Arrays; -import java.util.List; - -import io.reactivex.CompletableObserver; -import io.reactivex.SingleObserver; -import io.reactivex.observers.DisposableCompletableObserver; -import io.reactivex.observers.DisposableSingleObserver; -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; -import retrofit2.Retrofit; -import retrofit2.converter.gson.GsonConverterFactory; - -public class BinanceAssetEngine extends CoinEngine { - private static final String TAG = BinanceAssetEngine.class.getSimpleName(); - - public BinanceAssetData coinData = null; - private BinanceDexApiRestClient client = null; - - public BinanceAssetEngine(TangemContext context) throws Exception { - super(context); - if (context.getCoinData() == null) { - coinData = new BinanceAssetData(); - context.setCoinData(coinData); - } else if (context.getCoinData() instanceof BinanceAssetData) { - coinData = (BinanceAssetData) context.getCoinData(); - } else { - throw new Exception("Invalid type of Blockchain data for " + TAG); - } - client = BinanceDexApiClientFactory.newInstance().newRestClient(BinanceDexEnvironment.PROD.getBaseUrl()); - coinData.setChainId("Binance-Chain-Tigris"); - } - - public BinanceAssetEngine() { - super(); - } - - private static int getDecimals() { - return 8; - } - - private void checkBlockchainDataExists() throws Exception { - if (coinData == null) throw new Exception("No blockchain data"); - } - - @Override - public boolean awaitingConfirmation() { - return App.pendingTransactionsStorage.hasTransactions(ctx.getCard()); - } - - @Override - public String getBalanceHTML() { - Amount balance = coinData.getBalance(); - Amount assetBalance = coinData.getAssetBalance(); - if (balance != null) { - if (assetBalance != null) { - return assetBalance.toDescriptionString(getDecimals()) + "
+ " + balance.toDescriptionString(getDecimals()) + " for fee"; - } - return new Amount(BigDecimal.ZERO, ctx.getCard().tokenSymbol).toDescriptionString(getDecimals()) + "
+ " + balance.toDescriptionString(getDecimals()) + " for fee"; - } else { - return ""; - } - } - - @Override - public String getBalanceCurrency() { - if (hasBalanceInfo()) { - if (coinData.getAssetBalance() != null) { - return ctx.getCard().tokenSymbol; - } else { - return getFeeCurrency(); - } - } else { - return getFeeCurrency(); - } - } - - @Override - public boolean isBalanceNotZero() { - if (!hasBalanceInfo()) - return false; - return (coinData.getBalance() != null && coinData.getBalance().notZero()) || - (coinData.getAssetBalance() != null && coinData.getAssetBalance().notZero()); - } - - @Override - public boolean hasBalanceInfo() { - if (coinData == null) return false; - return coinData.hasBalanceInfo() || coinData.isError404(); - } - - public boolean isExtractPossible() { - if (!hasBalanceInfo()) { - ctx.setMessage(R.string.loaded_wallet_error_obtaining_blockchain_data); - } else if (!isBalanceNotZero()) { - ctx.setMessage(R.string.general_wallet_empty); - } else if (awaitingConfirmation()) { - ctx.setMessage(R.string.loaded_wallet_message_wait); - } else if (coinData.getBalance() == null || coinData.getBalance().isZero()) { - ctx.setMessage(ctx.getString(R.string.confirm_transaction_error_not_enough_bnb_for_fee)); - } else { - return true; - } - return false; - } - - @Override - public String getFeeCurrency() { - return "BNB"; - } - - @Override - public boolean validateAddress(String address) { - if (address == null || address.isEmpty()) { - return false; - } - - if (address.contains("$")) { // PayID - return validatePayId(address); - } - - try { - Crypto.decodeAddress(address); - } catch (Exception e) { - return false; - } - - if (ctx.getBlockchain() == Blockchain.Binance && !address.startsWith("bnb1")) { - return false; - } - - if (ctx.getBlockchain() == Blockchain.BinanceTestNet && !address.startsWith("tbnb1")) { - return false; - } - - return true; - } - - @Override - public boolean isNeedCheckNode() { - return true; - } - - @Override - public Uri getWalletExplorerUri() { - if (ctx.getBlockchain() == Blockchain.Binance) { - return Uri.parse("https://explorer.binance.org/address/" + ctx.getCoinData().getWallet()); - } else if (ctx.getBlockchain() == Blockchain.BinanceTestNet) { - return Uri.parse("https://testnet-explorer.binance.org/address/" + ctx.getCoinData().getWallet()); - } else { - Log.e(TAG, "Invalid blockchain for BinanceEngine"); - return Uri.parse("https://explorer.binance.org/address/" + ctx.getCoinData().getWallet()); - } - } - - public Uri getShareWalletUri() { - return Uri.parse(ctx.getCoinData().getWallet()); - } - - @Override - public InputFilter[] getAmountInputFilters() { - return new InputFilter[]{new DecimalDigitsInputFilter(getDecimals())}; - } - - @Override - public boolean checkNewTransactionAmount(Amount amount) { - if (!hasBalanceInfo()) return false; - Amount balance; - try { - if (amount.getCurrency().equals(ctx.getCard().tokenSymbol)) { - balance = coinData.getAssetBalance(); - } else if (amount.getCurrency().equals(getFeeCurrency())) { - balance = coinData.getBalance(); - } else { - return false; - } - } catch (Exception e) { - e.printStackTrace(); - return false; - } - return amount.compareTo(balance) <= 0; - } - - @Override - public boolean checkNewTransactionAmountAndFee(Amount amount, Amount fee, Boolean isFeeIncluded) { - if (!hasBalanceInfo()) return false; - - try { - Amount balance = coinData.getBalance(); - - if (fee == null || amount == null || fee.isZero() || amount.isZero()) - return false; - - - if (amount.getCurrency().equals(ctx.getCard().tokenSymbol)) { - // token transaction - if (fee.compareTo(balance) > 0) - return false; - } else if (amount.getCurrency().equals(getFeeCurrency())) { - // standard ETH transaction - if (isFeeIncluded && (amount.compareTo(balance) > 0 || fee.compareTo(balance) > 0)) - return false; - - if (!isFeeIncluded && amount.add(fee).compareTo(balance) > 0) - return false; - } else { - return false; - } - } catch (Exception e) { - e.printStackTrace(); - return false; - } - - return true; - } - - @Override - public boolean validateBalance(BalanceValidator balanceValidator) { - try { - if (((ctx.getCard().getOfflineBalance() == null) && !ctx.getCoinData().isBalanceReceived()) || (!ctx.getCoinData().isBalanceReceived() && (ctx.getCard().getRemainingSignatures() != ctx.getCard().getMaxSignatures()))) { - - if (coinData.isError404()) { - balanceValidator.setScore(0); - balanceValidator.setFirstLine(R.string.balance_validator_first_line_no_account); - balanceValidator.setSecondLine(R.string.balance_validator_second_line_create_account); - } else { - balanceValidator.setScore(0); - balanceValidator.setFirstLine(R.string.balance_validator_first_line_unknown_balance); - balanceValidator.setSecondLine(R.string.balance_validator_second_line_unverified_balance); - return false; - } - } - - if (coinData.isBalanceReceived()) { - balanceValidator.setScore(100); - balanceValidator.setFirstLine(R.string.balance_validator_first_line_verified_balance); - balanceValidator.setSecondLine(R.string.balance_validator_second_line_confirmed_in_blockchain); - if (coinData.getBalance().isZero() && (coinData.getAssetBalance() == null || coinData.getAssetBalance().isZero())) { - balanceValidator.setFirstLine(R.string.balance_validator_first_line_empty_wallet); - balanceValidator.setSecondLine(R.string.empty_string); - } - } - - return true; - } catch (Exception e) { - e.printStackTrace(); - return false; - } - } - - @Override - public Amount getBalance() { - if (!hasBalanceInfo()) return null; - if (coinData.getAssetBalance() != null) { - return coinData.getAssetBalance(); - } else { - return coinData.getBalance(); - } - } - - @Override - public String evaluateFeeEquivalent(String fee) { - if (!coinData.getAmountEquivalentDescriptionAvailable()) return ""; - try { - Amount feeAmount = new Amount(fee, getFeeCurrency()); - return feeAmount.toEquivalentString(coinData.getRate()); - } catch (Exception e) { - return ""; - } - } - - @Override - public String getBalanceEquivalent() { - if (!hasBalanceInfo()) { - return ""; - } - try { - if (coinData.getAssetBalance().notZero()) { - return ""; - } else { - return coinData.getBalance().toEquivalentString(coinData.getRateAlter()); - } - } catch (Exception e) { - e.printStackTrace(); - return ""; - } - } - - public String calculateAddress(byte[] pkCompressed) throws Exception { - byte[] pubKeyHash = Utils.sha256hash160(pkCompressed); - return Bech32.encode("bnb", Crypto.convertBits(pubKeyHash, 0, pubKeyHash.length, 8, 5, false)); - } - - @Override - public Amount convertToAmount(InternalAmount internalAmount) { - return new Amount(internalAmount, getBalanceCurrency()); - } - - @Override - public Amount convertToAmount(String strAmount, String currency) { - return new Amount(strAmount, currency); - } - - @Override - public InternalAmount convertToInternalAmount(Amount amount) { - return new InternalAmount(amount, getBalanceCurrency()); - } - - @Override - public InternalAmount convertToInternalAmount(byte[] bytes) { - if (bytes == null) return null; - byte[] reversed = new byte[bytes.length]; - for (int i = 0; i < bytes.length; i++) reversed[i] = bytes[bytes.length - i - 1]; - return new InternalAmount(Util.byteArrayToLong(reversed), getBalanceCurrency()); - } - - @Override - public byte[] convertToByteArray(InternalAmount internalAmount) { - return Util.longToByteArray(internalAmount.longValueExact()); - } - - @Override - public CoinData createCoinData() { - return new BinanceAssetData(); - } - - @Override - public String getUnspentInputsDescription() { - return ""; - } - - @Override - public void defineWallet() throws CardProtocol.TangemException { - try { - String wallet = calculateAddress(ctx.getCard().getWalletPublicKeyRar()); - ctx.getCoinData().setWallet(wallet); - coinData.setAssetSymbol(ctx.getCard().tokenSymbol); - } catch (Exception e) { - ctx.getCoinData().setWallet("ERROR"); - throw new CardProtocol.TangemException("Can't define wallet address"); - } - } - - @Override - public SignTask.TransactionToSign constructTransaction(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) throws Exception { - checkBlockchainDataExists(); - - String destination; - //PayID - if (coinData.getResolvedPayIdAddress() != null) { - destination = coinData.getResolvedPayIdAddress(); - } else { - destination = targetAddress; - } - - String amount; - - if (IncFee && amountValue.getCurrency().equals(getFeeCurrency())) { //Coin transfer only - amount = amountValue.subtract(feeValue).setScale(getDecimals(), RoundingMode.DOWN).toPlainString(); - } else { - amount = amountValue.setScale(getDecimals(), RoundingMode.DOWN).toPlainString(); - } - - byte[] pubKey = ctx.getCard().getWalletPublicKeyRar(); - byte[] pubKeyPrefix = MessageType.PubKey.getTypePrefixBytes(); - byte[] pubKeyForSign = new byte[pubKey.length + pubKeyPrefix.length + 1]; - System.arraycopy(pubKeyPrefix, 0, pubKeyForSign, 0, pubKeyPrefix.length); - pubKeyForSign[pubKeyPrefix.length] = (byte) 33; - System.arraycopy(pubKey, 0, pubKeyForSign, pubKeyPrefix.length + 1, pubKey.length); - - Transfer transfer = new Transfer(); - transfer.setCoin(amountValue.getCurrency().equals(getFeeCurrency()) ? getFeeCurrency() : ctx.getCard().getContractAddress()); - transfer.setFromAddress(ctx.getCoinData().getWallet()); - transfer.setToAddress(destination); - transfer.setAmount(amount); - - TransactionOption options = TransactionOption.DEFAULT_INSTANCE; - - TransactionRequestAssemblerExtSign txAssembler = client.prepareTransfer(transfer, coinData, pubKeyForSign, options, true); - // TransactionRequestAssembler.buildTransfer as reference - TransferMessage msgBean = txAssembler.createTransferMessage(transfer); - byte[] msg = txAssembler.encodeTransferMessage(msgBean); - byte[] dataForSign = txAssembler.prepareForSign(msgBean); - - return new SignTask.TransactionToSign() { - - @Override - public boolean isSigningMethodSupported(TangemCard.SigningMethod signingMethod) { - return signingMethod == TangemCard.SigningMethod.Sign_Hash || signingMethod == TangemCard.SigningMethod.Sign_Raw; - } - - @Override - public byte[][] getHashesToSign() throws NoSuchAlgorithmException { - byte[][] hashForSign = new byte[1][]; - MessageDigest digest = MessageDigest.getInstance("SHA-256"); - hashForSign[0] = digest.digest(dataForSign); - return hashForSign; - } - - @Override - public byte[] getRawDataToSign() { - return dataForSign; - } - - @Override - public String getHashAlgToSign() { - return "sha-256"; - } - - @Override - public byte[] getIssuerTransactionSignature(byte[] dataToSignByIssuer) throws Exception { - throw new Exception("Transaction validation by issuer not supported in this version"); - } - - @Override - public byte[] onSignCompleted(byte[] signFromCard) throws Exception { - int size = signFromCard.length / 2; - BigInteger r = new BigInteger(1, Arrays.copyOfRange(signFromCard, 0, size)); - BigInteger s = new BigInteger(1, Arrays.copyOfRange(signFromCard, size, size * 2)); - s = CryptoUtil.toCanonicalised(s); - - byte[] resultSig = new byte[64]; - System.arraycopy(Utils.bigIntegerToBytes(r, 32), 0, resultSig, 0, 32); - System.arraycopy(Utils.bigIntegerToBytes(s, 32), 0, resultSig, 32, 32); - - // TransactionRequestAssembler.buildTransfer as reference - byte[] signature = txAssembler.encodeSignature(resultSig); - byte[] txForSend = txAssembler.encodeStdTx(msg, signature); - - notifyOnNeedSendTransaction(txForSend); - return txForSend; - } - }; - } - - public void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) { - try { - ServerApiBinance serverApiBinance = new ServerApiBinance(); - - ServerApiBinance.ResponseListener responseListener = new ServerApiBinance.ResponseListener() { - @Override - public void onSuccess() { - blockchainRequestsCallbacks.onComplete(true); - } - - @Override - public void onFail() { - blockchainRequestsCallbacks.onComplete(false); - } - }; - - serverApiBinance.setResponseListener(responseListener); - serverApiBinance.getBalance(ctx, client); - - coinData.setValidationNodeDescription(Server.ApiBinance.URL_BINANCE); - - } catch (Exception e) { - e.printStackTrace(); - Log.e(TAG, "FAIL Binance balance exception"); - ctx.setError(e.getMessage()); - blockchainRequestsCallbacks.onComplete(false); - } - } - - public void requestFee(BlockchainRequestsCallbacks blockchainRequestsCallbacks, String targetAddress, Amount amount) { - CompletableObserver payIdObserver = new DisposableCompletableObserver() { - @Override - public void onComplete() { - checkFee(blockchainRequestsCallbacks); - } - - @Override - public void onError(Throwable e) { - ctx.setError(e.getMessage()); - blockchainRequestsCallbacks.onComplete(false); - } - }; - - if (targetAddress.contains("$")) { // PayID - resolvePayID(targetAddress, payIdObserver); - } else { - checkFee(blockchainRequestsCallbacks); - } - } - - private void checkFee(BlockchainRequestsCallbacks blockchainRequestsCallbacks) { - try { - String baseUrl = Server.ApiBinance.Method.API_V1; - - Retrofit retrofitBinance = new Retrofit.Builder() - .baseUrl(baseUrl) - .addConverterFactory(GsonConverterFactory.create()) - .build(); - - BinanceApi binanceApi = retrofitBinance.create(BinanceApi.class); - Call> call = binanceApi.binanceFees(); - call.enqueue(new Callback>() { - @Override - public void onResponse(@NonNull Call> call, @NonNull Response> response) { - if (response.code() == 200) { - for (BinanceFees fee : response.body()) { - if (fee.getFixed_fee_params() != null) { - Long longFee = Long.valueOf(fee.getFixed_fee_params().getFee()); - Amount feeAmount = new Amount(BigDecimal.valueOf(longFee).divide(BigDecimal.valueOf(100000000)).setScale(8, RoundingMode.DOWN), getFeeCurrency()); - coinData.minFee = coinData.normalFee = coinData.maxFee = feeAmount; - Log.i(TAG, "requestFee onResponse " + response.code()); - blockchainRequestsCallbacks.onComplete(true); - } - } - } else { - ctx.setError(response.code()); - Log.e(TAG, "requestFee onResponse " + response.code()); - blockchainRequestsCallbacks.onComplete(false); - } - } - - @Override - public void onFailure(@NonNull Call> call, @NonNull Throwable t) { - ctx.setError(t.getMessage()); - Log.e(TAG, "requestFee onFailure " + t.getMessage()); - blockchainRequestsCallbacks.onComplete(false); - } - }); - } catch (Exception e) { - ctx.setError(e.getMessage()); - e.printStackTrace(); - Log.e(TAG, "FAIL Binance fee exception"); - blockchainRequestsCallbacks.onComplete(false); - } - } - - private void resolvePayID(String targetAddress, CompletableObserver observer) { - final ServerApiPayId serverApiPayId = new ServerApiPayId(); - - SingleObserver payIdObserver = new DisposableSingleObserver() { - @Override - public void onSuccess(PayIdResponse payIdResponse) { - try { - String resolvedAddress = null; - for (PayIdAddress address : payIdResponse.getAddresses()) { - if (address.getPaymentNetwork().equals(ctx.getBlockchain().getCurrency()) && - address.getEnvironment().equals("MAINNET")) { - resolvedAddress = address.getAddressDetails().getAddress(); - break; - } - } - if (validateAddress(resolvedAddress)) { - if (!resolvedAddress.equals(coinData.getWallet())) { - coinData.setResolvedPayIdAddress(resolvedAddress); - observer.onComplete(); - } else { - observer.onError(new Exception("Resolved PayID address equals source address")); - } - } else { - observer.onError(new Exception("Unknown address format in PayID response")); - } - } catch (Exception e) { - observer.onError(new Exception("Unknown response format on PayID request")); - } - } - - @Override - public void onError(Throwable e) { - Log.i(TAG, "onFail: " + "payID" + " " + e.getMessage()); - observer.onError(new Exception("PayID error:" + e.getMessage())); - } - }; - - serverApiPayId.getAddress(targetAddress, ctx.getBlockchain(), payIdObserver); - } - - public void requestSendTransaction(BlockchainRequestsCallbacks blockchainRequestsCallbacks, byte[] txForSend) { -// RequestBody requestBody = TransactionRequestAssemblerExtSign.createRequestBody(txForSend); - try { - ServerApiBinance serverApiBinance = new ServerApiBinance(); - - ServerApiBinance.ResponseListener responseListener = new ServerApiBinance.ResponseListener() { - @Override - public void onSuccess() { - ctx.setError(null); - blockchainRequestsCallbacks.onComplete(true); - } - - @Override - public void onFail() { - ctx.setError("Broadcast error"); - blockchainRequestsCallbacks.onComplete(false); - } - }; - - serverApiBinance.setResponseListener(responseListener); - serverApiBinance.sendTransaction(txForSend, client); - - } catch (Exception e) { - Log.e(TAG, e.getMessage()); - ctx.setError(e.getMessage()); - blockchainRequestsCallbacks.onComplete(false); - } - } - - @Override - public int pendingTransactionTimeoutInSeconds() { - return 9; - } - - @Override - public boolean allowSelectFeeLevel() { - return false; - } - - @Override - public boolean allowSelectFeeInclusion() { - return coinData.getAssetBalance() == null; - } - - @Override - public boolean needMultipleLinesForBalance() { - return true; - } -} diff --git a/app/src/main/java/com/tangem/wallet/binance/BinanceData.java b/app/src/main/java/com/tangem/wallet/binance/BinanceData.java deleted file mode 100644 index bfe3b630cf..0000000000 --- a/app/src/main/java/com/tangem/wallet/binance/BinanceData.java +++ /dev/null @@ -1,98 +0,0 @@ -package com.tangem.wallet.binance; - -import android.os.Bundle; -import android.util.Log; - -import com.tangem.wallet.CoinData; -import com.tangem.wallet.CoinEngine; - -public class BinanceData extends CoinData { - private String balance, chainId; - private Long sequence; - private Integer accountNumber; - private boolean error404 = false; - - @Override - public void loadFromBundle(Bundle B) { - super.loadFromBundle(B); - - if (B.containsKey("Balance")) balance = B.getString("Balance"); - else balance = null; - if (B.containsKey("ChainId")) chainId = B.getString("ChainId"); - else chainId = null; - if (B.containsKey("Sequence")) sequence = B.getLong("Sequence"); - else sequence = null; - if (B.containsKey("AccountNumber")) accountNumber = B.getInt("AccountNumber"); - else accountNumber = null; - if (B.containsKey("Error404")) error404 = B.getBoolean("Error404"); - else error404 = false; - } - - @Override - public void saveToBundle(Bundle B) { - super.saveToBundle(B); - try { - if (balance != null) B.putString("Balance", balance); - if (chainId != null) B.putString("ChainId", chainId); - if (sequence != null) B.putLong("Sequence", sequence); - if (accountNumber != null) B.putInt("AccountNumber", accountNumber); - if (error404) B.putBoolean("Error404", true); - } catch (Exception e) { - Log.e("Can't save to bundle ", e.getMessage()); - } - } - - @Override - public void clearInfo() { - super.clearInfo(); - balance = null; - chainId = null; - sequence = null; - accountNumber = null; - error404 = false; - } - - public CoinEngine.Amount getBalance() { - return balance == null ? null : new CoinEngine.Amount(balance, "BNB"); - } - - public void setBalance(String balance) { - this.balance = balance; - } - - public boolean hasBalanceInfo() { - return balance != null; - } - - public Integer getAccountNumber() { - return accountNumber; - } - - public void setAccountNumber(Integer accountNumber) { - this.accountNumber = accountNumber; - } - - public Long getSequence() { - return sequence; - } - - public void setSequence(Long sequence) { - this.sequence = sequence; - } - - public String getChainId() { - return chainId; - } - - public void setChainId(String chain_id) { - this.chainId = chain_id; - } - - public boolean isError404() { - return error404; - } - - public void setError404(boolean error404) { - this.error404 = error404; - } -} diff --git a/app/src/main/java/com/tangem/wallet/binance/BinanceEngine.java b/app/src/main/java/com/tangem/wallet/binance/BinanceEngine.java deleted file mode 100644 index 5fc513cb3a..0000000000 --- a/app/src/main/java/com/tangem/wallet/binance/BinanceEngine.java +++ /dev/null @@ -1,638 +0,0 @@ -package com.tangem.wallet.binance; - -import android.net.Uri; -import android.text.InputFilter; -import android.util.Log; - -import androidx.annotation.NonNull; - -import com.tangem.App; -import com.tangem.data.Blockchain; -import com.tangem.data.network.BinanceApi; -import com.tangem.data.network.Server; -import com.tangem.data.network.ServerApiBinance; -import com.tangem.data.network.ServerApiPayId; -import com.tangem.data.network.model.BinanceFees; -import com.tangem.data.network.model.PayIdAddress; -import com.tangem.data.network.model.PayIdResponse; -import com.tangem.tangem_card.data.TangemCard; -import com.tangem.tangem_card.reader.CardProtocol; -import com.tangem.tangem_card.tasks.SignTask; -import com.tangem.tangem_card.util.Util; -import com.tangem.util.CryptoUtil; -import com.tangem.util.DecimalDigitsInputFilter; -import com.tangem.wallet.BalanceValidator; -import com.tangem.wallet.CoinData; -import com.tangem.wallet.CoinEngine; -import com.tangem.wallet.R; -import com.tangem.wallet.TangemContext; -import com.tangem.wallet.binance.client.BinanceDexApiClientFactory; -import com.tangem.wallet.binance.client.BinanceDexApiRestClient; -import com.tangem.wallet.binance.client.BinanceDexEnvironment; -import com.tangem.wallet.binance.client.domain.broadcast.TransactionOption; -import com.tangem.wallet.binance.client.domain.broadcast.Transfer; -import com.tangem.wallet.binance.client.encoding.Bech32; -import com.tangem.wallet.binance.client.encoding.Crypto; -import com.tangem.wallet.binance.client.encoding.message.MessageType; -import com.tangem.wallet.binance.client.encoding.message.TransactionRequestAssemblerExtSign; -import com.tangem.wallet.binance.client.encoding.message.TransferMessage; - -import org.bitcoinj.core.Utils; - -import java.math.BigDecimal; -import java.math.BigInteger; -import java.math.RoundingMode; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.Arrays; -import java.util.List; - -import io.reactivex.CompletableObserver; -import io.reactivex.SingleObserver; -import io.reactivex.observers.DisposableCompletableObserver; -import io.reactivex.observers.DisposableSingleObserver; -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; -import retrofit2.Retrofit; -import retrofit2.converter.gson.GsonConverterFactory; - - -public class BinanceEngine extends CoinEngine { - private static final String TAG = BinanceEngine.class.getSimpleName(); - - public BinanceData coinData = null; - private BinanceDexApiRestClient client = null; - - public BinanceEngine(TangemContext context) throws Exception { - super(context); - if (context.getCoinData() == null) { - coinData = new BinanceData(); - context.setCoinData(coinData); - } else if (context.getCoinData() instanceof BinanceData) { - coinData = (BinanceData) context.getCoinData(); - } else { - throw new Exception("Invalid type of Blockchain data for " + TAG); - } - if (ctx.getBlockchain() == Blockchain.Binance) { - client = BinanceDexApiClientFactory.newInstance().newRestClient(BinanceDexEnvironment.PROD.getBaseUrl()); - coinData.setChainId("Binance-Chain-Tigris"); - } else if (ctx.getBlockchain() == Blockchain.BinanceTestNet) { - client = BinanceDexApiClientFactory.newInstance().newRestClient(BinanceDexEnvironment.TEST_NET.getBaseUrl()); - coinData.setChainId("Binance-Chain-Nile"); - } else { - throw new Exception("Invalid blockchain for BinanceEngine"); - } - } - - public BinanceEngine() { - super(); - } - - private static int getDecimals() { - return 8; - } - - private void checkBlockchainDataExists() throws Exception { - if (coinData == null) throw new Exception("No blockchain data"); - } - - @Override - public boolean awaitingConfirmation() { - return App.pendingTransactionsStorage.hasTransactions(ctx.getCard()); - } - - @Override - public String getBalanceHTML() { - Amount balance = getBalance(); - if (balance != null) { - return balance.toDescriptionString(getDecimals()); - } else { - return ""; - } - } - - @Override - public String getBalanceCurrency() { - return "BNB"; - } - - @Override - public boolean isBalanceNotZero() { - if (coinData == null) return false; - if (coinData.getBalance() == null) return false; - return coinData.getBalance().notZero(); - } - - @Override - public boolean hasBalanceInfo() { - if (coinData == null) return false; - return coinData.hasBalanceInfo() || coinData.isError404(); - } - - public boolean isExtractPossible() { - if (!hasBalanceInfo()) { - ctx.setMessage(R.string.loaded_wallet_error_obtaining_blockchain_data); - } else if (!isBalanceNotZero()) { - ctx.setMessage(R.string.general_wallet_empty); - } else if (awaitingConfirmation()) { - ctx.setMessage(R.string.loaded_wallet_message_wait); - } else { - return true; - } - return false; - } - - @Override - public String getFeeCurrency() { - return "BNB"; - } - - @Override - public boolean validateAddress(String address) { - if (address == null || address.isEmpty()) { - return false; - } - - if (address.contains("$")) { // PayID - return validatePayId(address); - } - - try { - Crypto.decodeAddress(address); - } catch (Exception e) { - return false; - } - - if (ctx.getBlockchain() == Blockchain.Binance && !address.startsWith("bnb1")) { - return false; - } - - if (ctx.getBlockchain() == Blockchain.BinanceTestNet && !address.startsWith("tbnb1")) { - return false; - } - - return true; - } - - @Override - public boolean isNeedCheckNode() { - return true; - } - - @Override - public Uri getWalletExplorerUri() { - if (ctx.getBlockchain() == Blockchain.Binance) { - return Uri.parse("https://explorer.binance.org/address/" + ctx.getCoinData().getWallet()); - } else if (ctx.getBlockchain() == Blockchain.BinanceTestNet) { - return Uri.parse("https://testnet-explorer.binance.org/address/" + ctx.getCoinData().getWallet()); - } else { - Log.e(TAG, "Invalid blockchain for BinanceEngine"); - return Uri.parse("https://explorer.binance.org/address/" + ctx.getCoinData().getWallet()); - } - } - - public Uri getShareWalletUri() { - return Uri.parse(ctx.getCoinData().getWallet()); - } - - @Override - public InputFilter[] getAmountInputFilters() { - return new InputFilter[]{new DecimalDigitsInputFilter(getDecimals())}; - } - - @Override - public boolean checkNewTransactionAmount(Amount amount) { - if (coinData == null) return false; - return amount.compareTo(coinData.getBalance()) <= 0; - } - - @Override - public boolean checkNewTransactionAmountAndFee(Amount amountValue, Amount feeValue, Boolean isIncludeFee) { - try { - checkBlockchainDataExists(); - } catch (Exception e) { - e.printStackTrace(); - return false; - } - - if (feeValue == null || amountValue == null) - return false; - - if (feeValue.isZero() || amountValue.isZero()) - return false; - - if (isIncludeFee && (amountValue.compareTo(coinData.getBalance()) > 0 || amountValue.compareTo(feeValue) < 0)) - return false; - - if (!isIncludeFee && amountValue.add(feeValue).compareTo(coinData.getBalance()) > 0) - return false; - - return true; - } - - @Override - public boolean validateBalance(BalanceValidator balanceValidator) { - try { - if (((ctx.getCard().getOfflineBalance() == null) && !ctx.getCoinData().isBalanceReceived()) || (!ctx.getCoinData().isBalanceReceived() && (ctx.getCard().getRemainingSignatures() != ctx.getCard().getMaxSignatures()))) { - - if(coinData.isError404()) { - balanceValidator.setScore(0); - balanceValidator.setFirstLine(R.string.balance_validator_first_line_no_account); - balanceValidator.setSecondLine(R.string.balance_validator_second_line_create_account); - } else { - balanceValidator.setScore(0); - balanceValidator.setFirstLine(R.string.balance_validator_first_line_unknown_balance); - balanceValidator.setSecondLine(R.string.balance_validator_second_line_unverified_balance); - return false; - } - } - - if (coinData.isBalanceReceived()) { - balanceValidator.setScore(100); - balanceValidator.setFirstLine(R.string.balance_validator_first_line_verified_balance); - balanceValidator.setSecondLine(R.string.balance_validator_second_line_confirmed_in_blockchain); - if (coinData.getBalance().isZero()) { - balanceValidator.setFirstLine(R.string.balance_validator_first_line_empty_wallet); - balanceValidator.setSecondLine(R.string.empty_string); - } - } - -// if ((ctx.getCard().getOfflineBalance() != null) && !coinData.isBalanceReceived() && ctx.getCard().getRemainingSignatures() == ctx.getCard().getMaxSignatures()) { -// balanceValidator.setScore(80); -// balanceValidator.setFirstLine(R.string.balance_validator_first_line_verified_offline); -// balanceValidator.setSecondLine(R.string.balance_validator_second_line_internet_to_get_balance); -// } - - return true; - } catch (Exception e) { - e.printStackTrace(); - return false; - } - } - - @Override - public Amount getBalance() { - if (!hasBalanceInfo()) return null; - return coinData.getBalance(); - } - - @Override - public String evaluateFeeEquivalent(String fee) { - if (!coinData.getAmountEquivalentDescriptionAvailable()) return ""; - try { - Amount feeAmount = new Amount(fee, getFeeCurrency()); - return feeAmount.toEquivalentString(coinData.getRate()); - } catch (Exception e) { - return ""; - } - } - - @Override - public String getBalanceEquivalent() { - if (coinData == null || !coinData.getAmountEquivalentDescriptionAvailable()) return ""; - Amount balance = getBalance(); - if (balance == null) return ""; - return balance.toEquivalentString(coinData.getRate()); - } - - public String calculateAddress(byte[] pkCompressed) throws Exception { - byte[] pubKeyHash = Utils.sha256hash160(pkCompressed); - - if (ctx.getBlockchain() == Blockchain.Binance) { - return Bech32.encode("bnb", Crypto.convertBits(pubKeyHash, 0, pubKeyHash.length, 8, 5, false)); - } else if (ctx.getBlockchain() == Blockchain.BinanceTestNet) { - return Bech32.encode("tbnb", Crypto.convertBits(pubKeyHash, 0, pubKeyHash.length, 8, 5, false)); - } else { - throw new Exception("Invalid blockchain for BinanceEngine"); - } - } - - @Override - public Amount convertToAmount(InternalAmount internalAmount) { - return new Amount(internalAmount, getBalanceCurrency()); - } - - @Override - public Amount convertToAmount(String strAmount, String currency) { - return new Amount(strAmount, currency); - } - - @Override - public InternalAmount convertToInternalAmount(Amount amount) { - return new InternalAmount(amount, getBalanceCurrency()); - } - - @Override - public InternalAmount convertToInternalAmount(byte[] bytes) { - if (bytes == null) return null; - byte[] reversed = new byte[bytes.length]; - for (int i = 0; i < bytes.length; i++) reversed[i] = bytes[bytes.length - i - 1]; - return new InternalAmount(Util.byteArrayToLong(reversed), getBalanceCurrency()); - } - - @Override - public byte[] convertToByteArray(InternalAmount internalAmount) { - return Util.longToByteArray(internalAmount.longValueExact()); - } - - @Override - public CoinData createCoinData() { - return new BinanceData(); - } - - @Override - public String getUnspentInputsDescription() { - return ""; - } - - @Override - public void defineWallet() throws CardProtocol.TangemException { - try { - String wallet = calculateAddress(ctx.getCard().getWalletPublicKeyRar()); - ctx.getCoinData().setWallet(wallet); - } catch (Exception e) { - ctx.getCoinData().setWallet("ERROR"); - throw new CardProtocol.TangemException("Can't define wallet address"); - } - } - - @Override - public SignTask.TransactionToSign constructTransaction(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) throws Exception { - checkBlockchainDataExists(); - - String destination; - //PayID - if (coinData.getResolvedPayIdAddress() != null) { - destination = coinData.getResolvedPayIdAddress(); - } else { - destination = targetAddress; - } - - String amount; - - if (IncFee) { - amount = amountValue.subtract(feeValue).setScale(getDecimals(), RoundingMode.DOWN).toPlainString(); - } else { - amount = amountValue.setScale(getDecimals(), RoundingMode.DOWN).toPlainString(); - } - - byte[] pubKey = ctx.getCard().getWalletPublicKeyRar(); - byte[] pubKeyPrefix = MessageType.PubKey.getTypePrefixBytes(); - byte[] pubKeyForSign = new byte[pubKey.length + pubKeyPrefix.length + 1]; - System.arraycopy(pubKeyPrefix, 0, pubKeyForSign, 0, pubKeyPrefix.length); - pubKeyForSign[pubKeyPrefix.length] = (byte) 33; - System.arraycopy(pubKey, 0, pubKeyForSign, pubKeyPrefix.length + 1, pubKey.length); - - Transfer transfer = new Transfer(); - transfer.setCoin("BNB"); - transfer.setFromAddress(ctx.getCoinData().getWallet()); - transfer.setToAddress(destination); - transfer.setAmount(amount); - - TransactionOption options = TransactionOption.DEFAULT_INSTANCE; - - TransactionRequestAssemblerExtSign txAssembler = client.prepareTransfer(transfer, coinData, pubKeyForSign, options, true); - // TransactionRequestAssembler.buildTransfer as reference - TransferMessage msgBean = txAssembler.createTransferMessage(transfer); - byte[] msg = txAssembler.encodeTransferMessage(msgBean); - byte[] dataForSign = txAssembler.prepareForSign(msgBean); - - return new SignTask.TransactionToSign() { - - @Override - public boolean isSigningMethodSupported(TangemCard.SigningMethod signingMethod) { - return signingMethod == TangemCard.SigningMethod.Sign_Hash || signingMethod == TangemCard.SigningMethod.Sign_Raw; - } - - @Override - public byte[][] getHashesToSign() throws NoSuchAlgorithmException { - byte[][] hashForSign = new byte[1][]; - MessageDigest digest = MessageDigest.getInstance("SHA-256"); - hashForSign[0] = digest.digest(dataForSign); - return hashForSign; - } - - @Override - public byte[] getRawDataToSign() { - return dataForSign; - } - - @Override - public String getHashAlgToSign() { - return "sha-256"; - } - - @Override - public byte[] getIssuerTransactionSignature(byte[] dataToSignByIssuer) throws Exception { - throw new Exception("Transaction validation by issuer not supported in this version"); - } - - @Override - public byte[] onSignCompleted(byte[] signFromCard) throws Exception { - int size = signFromCard.length / 2; - BigInteger r = new BigInteger(1, Arrays.copyOfRange(signFromCard, 0, size)); - BigInteger s = new BigInteger(1, Arrays.copyOfRange(signFromCard, size, size * 2)); - s = CryptoUtil.toCanonicalised(s); - - byte[] resultSig = new byte[64]; - System.arraycopy(Utils.bigIntegerToBytes(r, 32), 0, resultSig, 0, 32); - System.arraycopy(Utils.bigIntegerToBytes(s, 32), 0, resultSig, 32, 32); - - // TransactionRequestAssembler.buildTransfer as reference - byte[] signature = txAssembler.encodeSignature(resultSig); - byte[] txForSend = txAssembler.encodeStdTx(msg, signature); - - notifyOnNeedSendTransaction(txForSend); - return txForSend; - } - }; - } - - public void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) { - try { - ServerApiBinance serverApiBinance = new ServerApiBinance(); - - ServerApiBinance.ResponseListener responseListener = new ServerApiBinance.ResponseListener() { - @Override - public void onSuccess() { - blockchainRequestsCallbacks.onComplete(true); - } - - @Override - public void onFail() { - blockchainRequestsCallbacks.onComplete(false); - } - }; - - serverApiBinance.setResponseListener(responseListener); - serverApiBinance.getBalance(ctx, client); - - if (ctx.getBlockchain() == Blockchain.Binance) { - coinData.setValidationNodeDescription(Server.ApiBinance.URL_BINANCE); - } else if (ctx.getBlockchain() == Blockchain.BinanceTestNet) { - coinData.setValidationNodeDescription(Server.ApiBinanceTestnet.URL_BINANCE_TESTNET); - } else { - throw new Exception("Invalid blockchain for BinanceEngine"); - } - - } catch (Exception e) { - e.printStackTrace(); - Log.e(TAG, "FAIL Binance balance exception"); - ctx.setError(e.getMessage()); - blockchainRequestsCallbacks.onComplete(false); - } - } - - public void requestFee(BlockchainRequestsCallbacks blockchainRequestsCallbacks, String targetAddress, Amount amount) { - CompletableObserver payIdObserver = new DisposableCompletableObserver() { - @Override - public void onComplete() { - checkFee(blockchainRequestsCallbacks); - } - - @Override - public void onError(Throwable e) { - ctx.setError(e.getMessage()); - blockchainRequestsCallbacks.onComplete(false); - } - }; - - if (targetAddress.contains("$")) { // PayID - resolvePayID(targetAddress, payIdObserver); - } else { - checkFee(blockchainRequestsCallbacks); - } - } - - private void checkFee(BlockchainRequestsCallbacks blockchainRequestsCallbacks) { - try { - String baseUrl; - - if (ctx.getBlockchain() == Blockchain.Binance) { - baseUrl = Server.ApiBinance.Method.API_V1; - } else if (ctx.getBlockchain() == Blockchain.BinanceTestNet) { - baseUrl = Server.ApiBinanceTestnet.Method.API_V1; - } else { - throw new Exception("Invalid blockchain for BinanceEngine"); - } - - Retrofit retrofitBinance = new Retrofit.Builder() - .baseUrl(baseUrl) - .addConverterFactory(GsonConverterFactory.create()) - .build(); - - BinanceApi binanceApi = retrofitBinance.create(BinanceApi.class); - Call> call = binanceApi.binanceFees(); - call.enqueue(new Callback>() { - @Override - public void onResponse(@NonNull Call> call, @NonNull Response> response) { - if (response.code() == 200) { - for (BinanceFees fee : response.body()) { - if (fee.getFixed_fee_params() != null) { - Long longFee = Long.valueOf(fee.getFixed_fee_params().getFee()); - Amount feeAmount = new Amount(BigDecimal.valueOf(longFee).divide(BigDecimal.valueOf(100000000)).setScale(8, RoundingMode.DOWN), getFeeCurrency()); - coinData.minFee = coinData.normalFee = coinData.maxFee = feeAmount; - Log.i(TAG, "requestFee onResponse " + response.code()); - blockchainRequestsCallbacks.onComplete(true); - } - } - } else { - ctx.setError(response.code()); - Log.e(TAG, "requestFee onResponse " + response.code()); - blockchainRequestsCallbacks.onComplete(false); - } - } - - @Override - public void onFailure(@NonNull Call> call, @NonNull Throwable t) { - ctx.setError(t.getMessage()); - Log.e(TAG, "requestFee onFailure " + t.getMessage()); - blockchainRequestsCallbacks.onComplete(false); - } - }); - } catch (Exception e) { - ctx.setError(e.getMessage()); - e.printStackTrace(); - Log.e(TAG, "FAIL Binance fee exception"); - blockchainRequestsCallbacks.onComplete(false); - } - } - - private void resolvePayID(String targetAddress, CompletableObserver observer) { - final ServerApiPayId serverApiPayId = new ServerApiPayId(); - - SingleObserver payIdObserver = new DisposableSingleObserver() { - @Override - public void onSuccess(PayIdResponse payIdResponse) { - try { - String resolvedAddress = null; - for (PayIdAddress address : payIdResponse.getAddresses()) { - if (address.getPaymentNetwork().equals(ctx.getBlockchain().getCurrency()) && - address.getEnvironment().equals("MAINNET")) { - resolvedAddress = address.getAddressDetails().getAddress(); - break; - } - } - if (validateAddress(resolvedAddress)) { - if (!resolvedAddress.equals(coinData.getWallet())) { - coinData.setResolvedPayIdAddress(resolvedAddress); - observer.onComplete(); - } else { - observer.onError(new Exception("Resolved PayID address equals source address")); - } - } else { - observer.onError(new Exception("Unknown address format in PayID response")); - } - } catch (Exception e) { - observer.onError(new Exception("Unknown response format on PayID request")); - } - } - - @Override - public void onError(Throwable e) { - Log.i(TAG, "onFail: " + "payID" + " " + e.getMessage()); - observer.onError(new Exception("PayID error:" + e.getMessage())); - } - }; - - serverApiPayId.getAddress(targetAddress, ctx.getBlockchain(), payIdObserver); - } - - public void requestSendTransaction(BlockchainRequestsCallbacks blockchainRequestsCallbacks, byte[] txForSend) { -// RequestBody requestBody = TransactionRequestAssemblerExtSign.createRequestBody(txForSend); - try { - ServerApiBinance serverApiBinance = new ServerApiBinance(); - - ServerApiBinance.ResponseListener responseListener = new ServerApiBinance.ResponseListener() { - @Override - public void onSuccess() { - ctx.setError(null); - blockchainRequestsCallbacks.onComplete(true); - } - - @Override - public void onFail() { - ctx.setError("Broadcast error"); - blockchainRequestsCallbacks.onComplete(false); - } - }; - - serverApiBinance.setResponseListener(responseListener); - serverApiBinance.sendTransaction(txForSend, client); - - } catch (Exception e) { - Log.e(TAG, e.getMessage()); - ctx.setError(e.getMessage()); - blockchainRequestsCallbacks.onComplete(false); - } - } - - public int pendingTransactionTimeoutInSeconds() { - return 9; - } - - public boolean allowSelectFeeLevel() { - return false; - } -} diff --git a/app/src/main/java/com/tangem/wallet/binance/client/BinanceDexApi.java b/app/src/main/java/com/tangem/wallet/binance/client/BinanceDexApi.java deleted file mode 100644 index 95342b68bd..0000000000 --- a/app/src/main/java/com/tangem/wallet/binance/client/BinanceDexApi.java +++ /dev/null @@ -1,82 +0,0 @@ -package com.tangem.wallet.binance.client; - -import com.tangem.wallet.binance.client.domain.*; -import okhttp3.RequestBody; -import retrofit2.Call; -import retrofit2.http.*; - -import java.util.List; - -public interface BinanceDexApi { - @GET("/api/v1/time") - Call