From d9bf0ae303915d642205553101b85fd5480e9e28 Mon Sep 17 00:00:00 2001 From: James Brown Date: Sun, 10 Mar 2024 11:04:26 +1100 Subject: [PATCH 01/11] Upgrade to Gradle 8.4 --- app/build.gradle | 174 ++++---- .../app/service/AnalyticsService.java | 21 +- app/src/main/AndroidManifest.xml | 8 +- .../alphawallet/app/ui/AddTokenActivity.java | 3 - .../app/ui/ImportTokenActivity.java | 45 +- .../app/ui/RedeemAssetSelectActivity.java | 23 +- .../alphawallet/app/ui/WalletsActivity.java | 19 +- .../app/ui/widget/holder/WalletHolder.java | 29 +- .../ui/widget/holder/WalletSummaryHolder.java | 23 +- .../alphawallet/app/widget/AddWalletView.java | 62 +-- .../app/widget/QRCodeActionsView.java | 63 ++- .../app/widget/SignTransactionDialog.java | 2 +- .../app/widget/WalletFragmentActionsView.java | 43 +- .../activity_add_custom_rpc_network.xml | 149 +++---- .../res/layout/item_erc1155_asset_select.xml | 17 +- app/src/main/res/layout/item_token.xml | 250 +++++------ app/src/main/res/layout/item_wallet.xml | 155 +++---- .../main/res/layout/layout_password_input.xml | 155 +++---- build.gradle | 73 +--- dmz/build.gradle | 19 +- gradle.properties | 31 +- gradle/libs.versions.toml | 116 +++++ gradle/wrapper/gradle-wrapper.properties | 6 +- hardware_stub/build.gradle | 10 +- lib/build.gradle | 46 +- .../token/tools/VerifyXMLDSig.java | 73 ---- .../token/tools/XMLDSigVerifier.java | 401 ------------------ settings.gradle | 46 +- util/build.gradle | 2 +- 29 files changed, 845 insertions(+), 1219 deletions(-) create mode 100644 gradle/libs.versions.toml delete mode 100644 lib/src/main/java/com/alphawallet/token/tools/VerifyXMLDSig.java delete mode 100644 lib/src/main/java/com/alphawallet/token/tools/XMLDSigVerifier.java diff --git a/app/build.gradle b/app/build.gradle index 2d77ff9b7a..2be7b1a8f2 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,35 +1,21 @@ -//noinspection GradleDependency -// WARNING WARNING WARNING -// don't add any additional things here without first search "China" in this file - -buildscript { - repositories { - google() - mavenCentral() - maven { - url "https://plugins.gradle.org/m2/" - } - } - dependencies { - classpath "gradle.plugin.com.worker8.android_lint_reporter:android_lint_reporter:2.1.0" - classpath "io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.22.0" - classpath "com.dicedmelon.gradle:jacoco-android:0.1.5" - } + +plugins { + alias(libs.plugins.androidApplication) + id("com.worker8.android_lint_reporter") version "2.1.0" + id("io.gitlab.arturbosch.detekt") version "1.23.5" + id 'jacoco' } -// WARNING WARNING WARNING -// DON'T add any plugins that is Google Play Service or uses Google Play Service -// Search China in this file for the reason apply plugin: 'com.android.application' apply plugin: 'realm-android' apply plugin: 'kotlin-android' apply plugin: 'dagger.hilt.android.plugin' apply plugin: 'com.worker8.android_lint_reporter' apply plugin: 'io.gitlab.arturbosch.detekt' -apply plugin: 'com.dicedmelon.gradle.jacoco-android' +apply plugin: 'jacoco' jacoco { - toolVersion = "0.8.9" + toolVersion = "0.8.8" } tasks.withType(Test).configureEach { @@ -37,20 +23,6 @@ tasks.withType(Test).configureEach { jacoco.excludes = ['jdk.internal.*'] } -jacocoAndroidUnitTestReport { - csv.enabled false - html.enabled true - xml.enabled true -} - -jacocoAndroidUnitTestReport { - excludes += [ - '**/*Realm*.*', - '**/Generated*.*', - '**/*_*.*' - ] -} - detekt { toolVersion = "1.20.0-RC1" buildUponDefaultConfig = true // preconfigure defaults @@ -67,20 +39,25 @@ android_lint_reporter { } android { + namespace 'com.alphawallet.app' + compileSdk 34 + sourceSets { main { } } + defaultConfig { + applicationId "io.stormbird.wallet" + minSdk 24 + targetSdk 34 versionCode 256 versionName "3.77" - applicationId "io.stormbird.wallet" - minSdkVersion 24 - targetSdkVersion 33 - compileSdk 33 - testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' + android.buildFeatures.buildConfig true + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunnerArguments clearPackageData: 'true' def XInfuraAPI = "XInfuraAPI" //Put your Infura key here, NB with over 30 - 40 users this API key will rate limit, it's only here for bootstrapping a free build @@ -122,6 +99,7 @@ android { } } } + flavorDimensions.add("targetting") productFlavors { @@ -151,6 +129,7 @@ android { } } } + buildTypes { debug { minifyEnabled false @@ -181,9 +160,6 @@ android { targetCompatibility JavaVersion.VERSION_17 sourceCompatibility JavaVersion.VERSION_17 } - kotlinOptions { - jvmTarget = '17' - } externalNativeBuild { cmake { path "src/main/cpp/CMakeLists.txt" @@ -201,7 +177,9 @@ android { includeInApk false includeInBundle false } - namespace 'com.alphawallet.app' + buildFeatures { + viewBinding true + } lint { abortOnError false baseline file('./check/lint-baseline.xml') @@ -222,18 +200,23 @@ tasks.register("printVersionCode") { } } -dependencies { - implementation project(":lib") +tasks.register("jacocoAndroidUnitTestReport") { + reports { + csv.enabled false + html.enabled true + xml.enabled true + } + def fileFilter = ['**/R.class', '**/R$*.class', '**/*$ViewInjector*.*', '**/BuildConfig.*', '**/Manifest*.*', '**/*Realm*.*', '**/Generated*.*', '**/*_*.*'] + def debugTree = fileTree(dir: "**/", excludes: fileFilter) + def mainSrc = "${project.projectDir}/src/main/java" - // WARNING! WARNING! WARNING! WARNING! WARNING! WARNING! WARNING! WARNING! - // WARNING! Don't add dependency on Google Play Services without consulting - // WARNING! The China marketing team - // WARNING! Many Chinese Android phones execute (meaning terminate) any app that - // WARNING! users google gms summarily, like immune systems cleansing infections - // WARNING! WARNING! WARNING! WARNING! WARNING! WARNING! WARNING! WARNING! + sourceDirectories.setFrom(files([mainSrc])) + classDirectories.setFrom(files([debugTree])) +} + +dependencies { + implementation project(':lib') - // Ethereum client - //implementation "org.web3j:core:4.9.8" implementation fileTree(include: ['*.jar'], dir: 'libs') implementation fileTree(include: ['*.aar'], dir: 'libs') //NB: Downgrade jackson due to bug in 2.15 releases that makes it incompatible with Gradle 8 @@ -242,62 +225,64 @@ dependencies { implementation 'com.fasterxml.jackson.core:jackson-core' implementation 'com.fasterxml.jackson.core:jackson-databind' - implementation 'org.slf4j:slf4j-api:2.0.9' - implementation "androidx.core:core-splashscreen:1.0.1" + implementation libs.slf4j.api + implementation libs.core.splashscreen // Http client - implementation "com.squareup.okhttp3:okhttp:4.11.0" - implementation 'com.google.code.gson:gson:2.10.1' + implementation libs.okhttp + implementation libs.gson - implementation 'androidx.appcompat:appcompat:1.6.1' - implementation 'com.google.android.material:material:1.9.0' - implementation 'androidx.vectordrawable:vectordrawable:1.1.0' - implementation 'androidx.recyclerview:recyclerview:1.3.2' - implementation 'androidx.biometric:biometric:1.1.0' - implementation 'androidx.gridlayout:gridlayout:1.0.0' + implementation libs.appcompat + implementation libs.material //'com.google.android.material:material:1.9.0' + implementation libs.vectordrawable + implementation libs.recyclerview + implementation libs.biometric + implementation libs.gridlayout // Bar code scanning - implementation 'com.journeyapps:zxing-android-embedded:4.3.0' - implementation 'com.google.zxing:core:3.5.2' + implementation libs.zxing.android.embedded + implementation libs.core // Sugar - implementation 'androidx.constraintlayout:constraintlayout:2.1.4' + implementation libs.constraintlayout //coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.5' // ReactiveX - implementation 'io.reactivex.rxjava2:rxjava:2.2.21' - implementation "io.reactivex.rxjava2:rxandroid:2.1.1" + implementation libs.rxjava + implementation libs.rxandroid // Keyboard visibility - implementation 'net.yslibrary.keyboardvisibilityevent:keyboardvisibilityevent:3.0.0-RC3' + implementation libs.keyboardvisibilityevent // Dagger 2 // dagger-hilt - implementation "com.google.dagger:hilt-android:2.48" - annotationProcessor "com.google.dagger:hilt-compiler:2.48" + implementation libs.hilt.android + annotationProcessor libs.hilt.compiler // WebKit - for WebView Dark Mode (NB Can't be upgraded from 1.7.0 until migration to Gradle 8) - implementation 'androidx.webkit:webkit:1.7.0' + implementation libs.webkit //Use Leak Canary for debug builds only //debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7' // Image Loader - implementation 'com.github.bumptech.glide:glide:4.13.0' - annotationProcessor 'com.github.bumptech.glide:compiler:4.13.0' - implementation group: 'com.google.guava', name: 'guava', version: '30.1.1-android' - implementation 'com.trustwallet:wallet-core:3.2.18' + implementation libs.glide + annotationProcessor libs.compiler + implementation libs.guava + implementation libs.wallet.core + //noinspection UseTomlInstead implementation 'com.github.florent37:TutoShowcase:d8b91be8a2' - implementation 'com.github.google:flexbox-layout:2.0.1' - implementation 'com.github.salomonbrys.kotson:kotson:2.5.0' - implementation 'com.github.mailchimp:mailchimp-sdk-android:1.0.0' - implementation 'androidx.preference:preference-ktx:1.2.1' + implementation libs.flexbox.layout + implementation libs.kotson + implementation libs.mailchimp.sdk.android + implementation libs.preference.ktx //Timber - implementation 'com.jakewharton.timber:timber:5.0.1' + implementation libs.timber + //noinspection UseTomlInstead implementation platform('com.walletconnect:android-bom:1.13.1') implementation("com.walletconnect:android-core", { exclude group: 'org.web3j', module: '*' @@ -306,20 +291,20 @@ dependencies { exclude group: 'org.web3j', module: '*' }) - runtimeOnly 'androidx.work:work-runtime-ktx:2.8.1' + runtimeOnly libs.work.runtime.ktx //Analytics - analyticsImplementation 'com.google.android.play:core:1.10.3' + analyticsImplementation libs.play.core - analyticsImplementation 'com.google.android.play:core:1.10.3' - analyticsImplementation 'com.google.firebase:firebase-analytics:21.5.0' - analyticsImplementation 'com.mixpanel.android:mixpanel-android:5.8.4' - analyticsImplementation 'com.google.firebase:firebase-crashlytics:18.5.1' + analyticsImplementation libs.play.core + analyticsImplementation libs.firebase.analytics + analyticsImplementation libs.mixpanel.android + analyticsImplementation libs.firebase.crashlytics // Notifications: NB there appears to be an incompatibility in the newer builds of firebase-messaging. // Update when resolved. //noinspection GradleDependency - implementation 'com.google.firebase:firebase-messaging:21.1.0' + implementation libs.firebase.messaging // @@ -330,7 +315,7 @@ dependencies { androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0' androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.3.0' - androidTestImplementation 'androidx.browser:browser:1.5.0' + androidTestImplementation 'androidx.browser:browser:1.8.0' // Unit tests testImplementation 'junit:junit:4.13.2' @@ -338,7 +323,7 @@ dependencies { testImplementation group: 'org.powermock', name: 'powermock-module-junit4-rule-agent', version: '2.0.9' testImplementation group: 'org.powermock', name: 'powermock-module-junit4', version: '2.0.9' testImplementation group: 'org.powermock', name: 'powermock-api-mockito2', version: '2.0.9' - testImplementation group: 'org.json', name: 'json', version: '20220320' + testImplementation group: 'org.json', name: 'json', version: '20240303' // Component tests: Updating these appears to break the tests. testImplementation 'org.robolectric:robolectric:4.8.2' @@ -358,8 +343,5 @@ dependencies { androidTestImplementation('tools.fastlane:screengrab:2.1.1', { exclude group: 'com.android.support', module: 'support-annotations' }) -} - -// WARNING WARNING WARNING -// don't add any additional things here without first search "China" in this file +} diff --git a/app/src/analytics/java/com/alphawallet/app/service/AnalyticsService.java b/app/src/analytics/java/com/alphawallet/app/service/AnalyticsService.java index 4ec45b8fe5..c2558f9c9c 100644 --- a/app/src/analytics/java/com/alphawallet/app/service/AnalyticsService.java +++ b/app/src/analytics/java/com/alphawallet/app/service/AnalyticsService.java @@ -11,14 +11,13 @@ import com.alphawallet.app.repository.PreferenceRepositoryType; import com.google.firebase.analytics.FirebaseAnalytics; import com.google.firebase.crashlytics.FirebaseCrashlytics; -import com.google.firebase.iid.FirebaseInstanceId; +import com.google.firebase.messaging.FirebaseMessaging; import com.mixpanel.android.mpmetrics.MixpanelAPI; import org.json.JSONException; import org.json.JSONObject; import java.util.Iterator; -import java.util.Objects; import timber.log.Timber; @@ -31,7 +30,7 @@ public class AnalyticsService implements AnalyticsServiceType public AnalyticsService(Context context, PreferenceRepositoryType preferenceRepository) { this.preferenceRepository = preferenceRepository; - mixpanelAPI = MixpanelAPI.getInstance(context, KeyProviderFactory.get().getAnalyticsKey()); + mixpanelAPI = MixpanelAPI.getInstance(context, KeyProviderFactory.get().getAnalyticsKey(), false); firebaseAnalytics = FirebaseAnalytics.getInstance(context); } @@ -115,14 +114,14 @@ public void identify(String uuid) mixpanelAPI.getPeople().identify(uuid); mixpanelAPI.getPeople().set(Analytics.UserProperties.APPLICATION_ID.getValue(), BuildConfig.APPLICATION_ID); - FirebaseInstanceId.getInstance().getInstanceId() - .addOnCompleteListener(task -> { - if (task.isSuccessful()) - { - String token = Objects.requireNonNull(task.getResult()).getToken(); - mixpanelAPI.getPeople().setPushRegistrationId(token); - } - }); + FirebaseMessaging.getInstance().getToken() + .addOnCompleteListener(task -> { + if (task.isSuccessful()) + { + String token = task.getResult(); + mixpanelAPI.getPeople().setPushRegistrationId(token); + } + }); } } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8d9c1a8939..7802b1102f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,7 +1,6 @@ @@ -15,6 +14,8 @@ + + + android:enabled="true" + android:exported="false" + android:foregroundServiceType="connectedDevice"> + 0.0) - { - confirmPurchaseDialog(); - } - else - { - onProgress(true); - completeImport(); - } + public void onClick(View v) + { + if (v.getId() == R.id.import_ticket) + { + if (ticketRange != null) + { + if (viewModel.getSalesOrder().price > 0.0) + { + confirmPurchaseDialog(); } - else if (viewModel.getSalesOrder().contractType == currencyLink) + else { onProgress(true); - completeCurrencyImport(); + completeImport(); } - break; - case cancel_button: - //go to main screen - new HomeRouter().open(this, true); - finish(); - break; + } + else if (viewModel.getSalesOrder().contractType == currencyLink) + { + onProgress(true); + completeCurrencyImport(); + } + } + else if (v.getId() == R.id.cancel_button) + { + new HomeRouter().open(this, true); + finish(); } } diff --git a/app/src/main/java/com/alphawallet/app/ui/RedeemAssetSelectActivity.java b/app/src/main/java/com/alphawallet/app/ui/RedeemAssetSelectActivity.java index 8ef9200e07..b001379383 100644 --- a/app/src/main/java/com/alphawallet/app/ui/RedeemAssetSelectActivity.java +++ b/app/src/main/java/com/alphawallet/app/ui/RedeemAssetSelectActivity.java @@ -137,18 +137,17 @@ protected void onDestroy() public boolean onOptionsItemSelected(MenuItem item) { final int action_next = R.id.action_next; final int action_redeem = R.id.action_redeem; - switch (item.getItemId()) { - case action_next: { - onNext(); - } - break; - case action_redeem: { - onRedeem(); - } - break; - case android.R.id.home: { - finish(); - } + if (item.getItemId() == R.id.action_next) + { + onNext(); + } + else if (item.getItemId() == R.id.action_redeem) + { + onRedeem(); + } + else if (item.getItemId() == android.R.id.home) + { + finish(); } return super.onOptionsItemSelected(item); } diff --git a/app/src/main/java/com/alphawallet/app/ui/WalletsActivity.java b/app/src/main/java/com/alphawallet/app/ui/WalletsActivity.java index d3988f7e8e..100cd17668 100644 --- a/app/src/main/java/com/alphawallet/app/ui/WalletsActivity.java +++ b/app/src/main/java/com/alphawallet/app/ui/WalletsActivity.java @@ -278,19 +278,14 @@ public boolean onCreateOptionsMenu(Menu menu) @Override public boolean onOptionsItemSelected(MenuItem item) { - final int action_add = R.id.action_add; - switch (item.getItemId()) + if (item.getItemId() == R.id.action_add) { - case action_add: - { - onAddWallet(); - } - break; - case android.R.id.home: - { - onBackPressed(); - return true; - } + onAddWallet(); + } + else if (item.getItemId() == android.R.id.home) + { + onBackPressed(); + return true; } return super.onOptionsItemSelected(item); } diff --git a/app/src/main/java/com/alphawallet/app/ui/widget/holder/WalletHolder.java b/app/src/main/java/com/alphawallet/app/ui/widget/holder/WalletHolder.java index aab32f565d..1417c405d4 100644 --- a/app/src/main/java/com/alphawallet/app/ui/widget/holder/WalletHolder.java +++ b/app/src/main/java/com/alphawallet/app/ui/widget/holder/WalletHolder.java @@ -222,22 +222,19 @@ private void checkLastBackUpTime() { } @Override - public void onClick(View view) { - //if (wallet == null) { return; } //protect against click between constructor and bind - final int wallet_click_layer = R.id.wallet_click_layer; - final int layout_manage_wallet = R.id.layout_manage_wallet; - switch (view.getId()) { - case wallet_click_layer: - clickCallback.onWalletClicked(wallet); - break; - - case layout_manage_wallet: - Intent intent = new Intent(getContext(), WalletActionsActivity.class); - intent.putExtra("wallet", wallet); - intent.putExtra("currency", wallet.balanceSymbol); - intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); - getContext().startActivity(intent); - break; + public void onClick(View view) + { + if (view.getId() == R.id.wallet_click_layer) + { + clickCallback.onWalletClicked(wallet); + } + else if (view.getId() == R.id.wallet_click_layer) + { + Intent intent = new Intent(getContext(), WalletActionsActivity.class); + intent.putExtra("wallet", wallet); + intent.putExtra("currency", wallet.balanceSymbol); + intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); + getContext().startActivity(intent); } } diff --git a/app/src/main/java/com/alphawallet/app/ui/widget/holder/WalletSummaryHolder.java b/app/src/main/java/com/alphawallet/app/ui/widget/holder/WalletSummaryHolder.java index 663ddd5589..d0e9f175b4 100644 --- a/app/src/main/java/com/alphawallet/app/ui/widget/holder/WalletSummaryHolder.java +++ b/app/src/main/java/com/alphawallet/app/ui/widget/holder/WalletSummaryHolder.java @@ -263,20 +263,17 @@ private void checkLastBackUpTime() @Override public void onClick(View view) { - //if (wallet == null) { return; } //protect against click between constructor and bind - switch (view.getId()) + if (view.getId() == R.id.container) { - case R.id.container: - clickCallback.onWalletClicked(wallet); - break; - - case R.id.layout_manage_wallet: - Intent intent = new Intent(getContext(), WalletActionsActivity.class); - intent.putExtra("wallet", wallet); - intent.putExtra("currency", wallet.balanceSymbol); - intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); - getContext().startActivity(intent); - break; + clickCallback.onWalletClicked(wallet); + } + else if (view.getId() == R.id.layout_manage_wallet) + { + Intent intent = new Intent(getContext(), WalletActionsActivity.class); + intent.putExtra("wallet", wallet); + intent.putExtra("currency", wallet.balanceSymbol); + intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); + getContext().startActivity(intent); } } diff --git a/app/src/main/java/com/alphawallet/app/widget/AddWalletView.java b/app/src/main/java/com/alphawallet/app/widget/AddWalletView.java index 076f86bf0e..8a0f671dd4 100644 --- a/app/src/main/java/com/alphawallet/app/widget/AddWalletView.java +++ b/app/src/main/java/com/alphawallet/app/widget/AddWalletView.java @@ -39,56 +39,40 @@ private void init(@LayoutRes int layoutId) { @Override public void onClick(View view) { - - final int close_action = R.id.close_action; - final int new_account_action = R.id.new_account_action; - final int import_account_action = R.id.import_account_action; - final int watch_account_action = R.id.watch_account_action; - final int hardware_card = R.id.hardware_card; - - switch (view.getId()) + if (view.getId() == R.id.close_action) { - case close_action: + if (onCloseActionListener != null) { - if (onCloseActionListener != null) - { - onCloseActionListener.onClose(view); - } - break; + onCloseActionListener.onClose(view); } - case new_account_action: + } + else if (view.getId() == R.id.new_account_action) + { + if (onNewWalletClickListener != null) { - if (onNewWalletClickListener != null) - { - onNewWalletClickListener.onNewWallet(view); - } - break; + onNewWalletClickListener.onNewWallet(view); } - case import_account_action: + } + else if (view.getId() == R.id.import_account_action) + { + if (onImportWalletClickListener != null) { - if (onImportWalletClickListener != null) - { - onImportWalletClickListener.onImportWallet(view); - } - break; + onImportWalletClickListener.onImportWallet(view); } - case watch_account_action: + } + else if (view.getId() == R.id.watch_account_action) + { + if (onWatchWalletClickListener != null) { - if (onWatchWalletClickListener != null) - { - onWatchWalletClickListener.onWatchWallet(view); - } - break; + onWatchWalletClickListener.onWatchWallet(view); } - case hardware_card: + } + else if (view.getId() == R.id.hardware_card) + { + if (onHardwareCardClickListener != null) { - if (onHardwareCardClickListener != null) - { - onHardwareCardClickListener.detectCard(view); - } + onHardwareCardClickListener.detectCard(view); } - default: - break; } } diff --git a/app/src/main/java/com/alphawallet/app/widget/QRCodeActionsView.java b/app/src/main/java/com/alphawallet/app/widget/QRCodeActionsView.java index d9a72d9caf..f0932f6bff 100644 --- a/app/src/main/java/com/alphawallet/app/widget/QRCodeActionsView.java +++ b/app/src/main/java/com/alphawallet/app/widget/QRCodeActionsView.java @@ -36,52 +36,41 @@ private void init(@LayoutRes int layoutId) { findViewById(R.id.close_action).setOnClickListener(this); } - //TODO: Refactor with if/else @Override - public void onClick(View view) { - - final int send_to_this_address_action = R.id.send_to_this_address_action; - final int add_custom_token_action = R.id.add_custom_token_action; - final int watch_account_action = R.id.watch_account_action; - final int open_in_etherscan_action = R.id.open_in_etherscan_action; - final int close_action = R.id.close_action; - - switch (view.getId()) { - case send_to_this_address_action: { - if (onSendToAddressClickListener != null) { - onSendToAddressClickListener.onClick(view); - } - break; + public void onClick(View view) + { + if (view.getId() == R.id.send_to_this_address_action) + { + if (onSendToAddressClickListener != null) { + onSendToAddressClickListener.onClick(view); } - case add_custom_token_action: { - if (onAddCustonTokenClickListener != null) { - onAddCustonTokenClickListener.onClick(view); - } - break; + } + else if (view.getId() == R.id.add_custom_token_action) + { + if (onAddCustonTokenClickListener != null) { + onAddCustonTokenClickListener.onClick(view); } - case watch_account_action: { - if (onWatchWalletClickListener != null) { - onWatchWalletClickListener.onClick(view); - } - break; - + } + else if (view.getId() == R.id.watch_account_action) + { + if (onWatchWalletClickListener != null) { + onWatchWalletClickListener.onClick(view); } - case open_in_etherscan_action: { - if (onOpenInEtherscanClickListener != null) { - onOpenInEtherscanClickListener.onClick(view); - } - break; + } + else if (view.getId() == R.id.open_in_etherscan_action) + { + if (onOpenInEtherscanClickListener != null) { + onOpenInEtherscanClickListener.onClick(view); } - case close_action: { - if (onCloseActionListener != null) { - onCloseActionListener.onClick(view); - } - break; + } + else if (view.getId() == R.id.close_action) + { + if (onCloseActionListener != null) { + onCloseActionListener.onClick(view); } } } - public void setOnSendToAddressClickListener(OnClickListener onSendToAddressClickListener) { this.onSendToAddressClickListener = onSendToAddressClickListener; } diff --git a/app/src/main/java/com/alphawallet/app/widget/SignTransactionDialog.java b/app/src/main/java/com/alphawallet/app/widget/SignTransactionDialog.java index 3f60a5c579..9a7a6d99d3 100644 --- a/app/src/main/java/com/alphawallet/app/widget/SignTransactionDialog.java +++ b/app/src/main/java/com/alphawallet/app/widget/SignTransactionDialog.java @@ -83,7 +83,7 @@ public void onAuthenticationError(int errorCode, authCallback.authenticateFail(activity.getString(R.string.too_many_fails), AuthenticationFailType.FINGERPRINT_NOT_VALIDATED, callbackId); break; case BiometricPrompt.ERROR_USER_CANCELED: - authCallback.authenticateFail(activity.getString(R.string.fingerprint_error_user_canceled), AuthenticationFailType.AUTHENTICATION_DIALOG_CANCELLED, callbackId); + authCallback.authenticateFail(activity.getString(androidx.biometric.R.string.fingerprint_error_user_canceled), AuthenticationFailType.AUTHENTICATION_DIALOG_CANCELLED, callbackId); break; case BiometricPrompt.ERROR_HW_NOT_PRESENT: case BiometricPrompt.ERROR_HW_UNAVAILABLE: diff --git a/app/src/main/java/com/alphawallet/app/widget/WalletFragmentActionsView.java b/app/src/main/java/com/alphawallet/app/widget/WalletFragmentActionsView.java index d2db27dda4..adeaf37f83 100644 --- a/app/src/main/java/com/alphawallet/app/widget/WalletFragmentActionsView.java +++ b/app/src/main/java/com/alphawallet/app/widget/WalletFragmentActionsView.java @@ -35,31 +35,30 @@ private void init(@LayoutRes int layoutId) { } @Override - public void onClick(View view) { - switch (view.getId()) { - - case R.id.copy_wallet_address_action: { - if (onCopyWalletAddressClickListener != null) { - onCopyWalletAddressClickListener.onClick(view); - } - break; + public void onClick(View view) + { + if (view.getId() == R.id.copy_wallet_address_action) + { + if (onCopyWalletAddressClickListener != null) { + onCopyWalletAddressClickListener.onClick(view); } - case R.id.show_my_wallet_address_action: { - if (onShowMyWalletAddressClickListener != null) { - onShowMyWalletAddressClickListener.onClick(view); - } - break; + } + else if (view.getId() == R.id.show_my_wallet_address_action) + { + if (onShowMyWalletAddressClickListener != null) { + onShowMyWalletAddressClickListener.onClick(view); } - case R.id.add_hide_tokens_action: { - if (onAddHideTokensClickListener != null) { - onAddHideTokensClickListener.onClick(view); - } - break; + } + else if (view.getId() == R.id.add_hide_tokens_action) + { + if (onAddHideTokensClickListener != null) { + onAddHideTokensClickListener.onClick(view); } - case R.id.rename_this_wallet_action: { - if (onRenameThisWalletListener != null) { - onRenameThisWalletListener.onClick(view); - } + } + else if (view.getId() == R.id.rename_this_wallet_action) + { + if (onRenameThisWalletListener != null) { + onRenameThisWalletListener.onClick(view); } } } diff --git a/app/src/main/res/layout/activity_add_custom_rpc_network.xml b/app/src/main/res/layout/activity_add_custom_rpc_network.xml index b6c35d629a..78642d4255 100644 --- a/app/src/main/res/layout/activity_add_custom_rpc_network.xml +++ b/app/src/main/res/layout/activity_add_custom_rpc_network.xml @@ -1,108 +1,113 @@ + xmlns:custom="http://schemas.android.com/apk/res-auto" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> - - + android:layout_height="match_parent" + android:layout_above="@id/layoutButtons" + android:layout_below="@id/toolbar"> - + android:focusable="true" + android:focusableInTouchMode="true" + android:orientation="vertical"> + android:id="@+id/input_network_name" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="@dimen/standard_16" + custom:label="@string/hint_network_name" /> + android:id="@+id/input_network_rpc_url" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="@dimen/standard_16" + custom:label="@string/hint_network_rpc_url" /> + android:id="@+id/input_network_chain_id" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="@dimen/standard_16" + custom:inputType="number" + custom:label="@string/hint_network_chain_id" /> + android:id="@+id/input_network_symbol" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="@dimen/standard_16" + custom:label="@string/hint_network_symbol" /> + android:id="@+id/input_network_block_explorer_url" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="@dimen/standard_16" + custom:label="@string/hint_network_block_explorer_url" /> + + + android:id="@+id/testnet_frame" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + android:layout_width="match_parent" + android:layout_height="50dp" + android:gravity="center_vertical" + android:paddingStart="@dimen/standard_16" + android:paddingEnd="@dimen/standard_16" + android:text="@string/this_is_testnet" + android:textAllCaps="false"/> + android:id="@+id/checkbox_testnet" + android:layout_width="wrap_content" + android:layout_gravity="center_vertical|end" + android:layout_marginEnd="@dimen/small_12" + app:useMaterialThemeColors="false" + app:buttonTint="@color/selector_button_tint" + tools:checked="true" + android:layout_height="wrap_content" /> + android:id="@+id/testnet_switch" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical|end" + android:layout_marginEnd="@dimen/small_12" + android:clickable="true" + android:focusable="true" + android:visibility="gone" /> + android:id="@+id/layoutButtons" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_alignParentBottom="true" /> diff --git a/app/src/main/res/layout/item_erc1155_asset_select.xml b/app/src/main/res/layout/item_erc1155_asset_select.xml index a72d39ee35..97e891e0ed 100644 --- a/app/src/main/res/layout/item_erc1155_asset_select.xml +++ b/app/src/main/res/layout/item_erc1155_asset_select.xml @@ -128,13 +128,16 @@ tools:visibility="visible" /> + android:id="@+id/checkbox" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + app:useMaterialThemeColors="false" + app:buttonTint="@color/selector_button_tint" + tools:checked="true" + android:clickable="false" + android:visibility="gone" + tools:visibility="visible" /> diff --git a/app/src/main/res/layout/item_token.xml b/app/src/main/res/layout/item_token.xml index 0dab03d0fd..fde8cca578 100644 --- a/app/src/main/res/layout/item_token.xml +++ b/app/src/main/res/layout/item_token.xml @@ -1,173 +1,177 @@ + xmlns:tools="http://schemas.android.com/tools" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:id="@+id/token_layout" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:clickable="true" + android:focusable="true"> + android:id="@+id/select_token" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + app:useMaterialThemeColors="false" + app:buttonTint="@color/selector_button_tint" + tools:checked="true" + android:visibility="gone" + tools:visibility="visible" /> - - + android:layout_alignParentTop="true" + android:layout_toEndOf="@id/select_token" + android:orientation="vertical" + android:padding="@dimen/standard_16"> - - - - - - - - - + android:orientation="horizontal"> - + - + android:orientation="vertical"> - - - - + android:layout_marginEnd="@dimen/standard_16" + android:layout_weight="1" + android:ellipsize="end" + android:singleLine="true" + tools:text="Ethereum" /> - - + - + android:baselineAligned="false" + android:orientation="horizontal"> - + android:layout_weight="1" + android:gravity="center_vertical" + android:orientation="horizontal"> - + + + + + + + + + android:visibility="gone" + tools:visibility="visible"> - + android:layout_marginEnd="@dimen/tiny_8" + android:text="@string/unknown_balance_without_symbol" + tools:text="$3.47" /> - + android:orientation="horizontal" + android:padding="2dp" + tools:ignore="UseCompoundDrawables"> + + + + + android:id="@+id/ticker_progress" + android:layout_width="@dimen/token_icon_small" + android:layout_height="@dimen/token_icon_small" /> diff --git a/app/src/main/res/layout/item_wallet.xml b/app/src/main/res/layout/item_wallet.xml index 10a0c0d90a..90a82cf322 100644 --- a/app/src/main/res/layout/item_wallet.xml +++ b/app/src/main/res/layout/item_wallet.xml @@ -1,108 +1,113 @@ + xmlns:tools="http://schemas.android.com/tools" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:descendantFocusability="blocksDescendants" + android:focusable="false" + android:orientation="horizontal" + android:paddingHorizontal="@dimen/standard_16"> + android:id="@+id/layout_icon" + android:layout_width="@dimen/token_icon" + android:layout_height="@dimen/token_icon" + android:layout_alignParentStart="true" + android:layout_centerVertical="true" + android:gravity="center_vertical" + android:orientation="horizontal"> + android:id="@+id/wallet_icon" + android:layout_width="@dimen/token_icon" + android:layout_height="@dimen/token_icon" /> - - + android:layout_centerVertical="true" + android:layout_marginStart="@dimen/tiny_8" + android:layout_toEndOf="@id/layout_icon" + android:orientation="vertical" + tools:ignore="RelativeOverlap"> - + android:orientation="horizontal" + android:visibility="gone"> + android:id="@+id/wallet_balance" + style="@style/Aw.Typography.Caption" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:textIsSelectable="true" + tools:text="0" /> + + - - + android:orientation="horizontal"> + android:id="@+id/wallet_name" + style="@style/Aw.Typography.Caption" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + tools:text="user.eth" /> + android:id="@+id/wallet_address_separator" + style="@style/Aw.Typography.Caption" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:paddingLeft="@dimen/mini_4" + android:paddingRight="@dimen/mini_4" + android:text="|" + tools:ignore="HardcodedText" /> + + + android:id="@+id/checkbox" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentEnd="true" + android:layout_centerVertical="true" + android:contentDescription="Selection" + app:useMaterialThemeColors="false" + app:buttonTint="@color/selector_button_tint" + tools:checked="true" + tools:visibility="visible" + android:visibility="gone" + tools:ignore="HardcodedText" /> diff --git a/app/src/main/res/layout/layout_password_input.xml b/app/src/main/res/layout/layout_password_input.xml index 6490596870..e1bc43e150 100644 --- a/app/src/main/res/layout/layout_password_input.xml +++ b/app/src/main/res/layout/layout_password_input.xml @@ -1,97 +1,98 @@ + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + android:id="@+id/box_layout" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/tiny_8"> + android:id="@+id/edit_text" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_alignParentStart="true" + android:layout_alignParentTop="true" + android:layout_marginStart="@dimen/standard_16" + android:layout_marginEnd="@dimen/standard_16" + android:background="@drawable/background_password_entry" + android:imeOptions="actionDone" + android:inputType="textMultiLine|textNoSuggestions" + android:minHeight="56dp" + android:paddingStart="@dimen/standard_16" + android:paddingTop="@dimen/tiny_8" + android:paddingEnd="@dimen/standard_16" + android:paddingBottom="@dimen/tiny_8" + tools:hint="Password" /> + android:id="@+id/toggle_password" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:padding="8dp" + android:layout_alignParentEnd="true" + android:layout_centerVertical="true" + android:layout_marginEnd="@dimen/standard_16" + android:button="@drawable/selector_show_password" + android:visibility="gone" + tools:visibility="visible" /> + android:id="@+id/label" + style="@style/Aw.Typography.Label.Small" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/comfy_28" + android:layout_marginBottom="@dimen/mini_4" + android:background="?colorSurface" + android:paddingStart="@dimen/mini_4" + android:paddingEnd="@dimen/mini_4" + android:visibility="invisible" + tools:text="Label" + tools:visibility="visible" /> + android:id="@+id/error" + style="@style/Aw.Typography.Sub" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@id/box_layout" + android:layout_alignParentEnd="true" + android:layout_marginEnd="@dimen/standard_16" + android:textColor="?colorError" + android:visibility="gone" + tools:text="Error Text" + tools:visibility="visible" /> + android:id="@+id/text_word_count" + style="@style/Aw.Typography.Sub.Small" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignBottom="@id/box_layout" + android:layout_alignParentEnd="true" + android:layout_marginEnd="@dimen/cozy_20" + android:layout_marginBottom="1dp" + android:gravity="center_horizontal" + android:visibility="gone" + tools:text="0/12" + tools:visibility="visible" /> + android:id="@+id/instruction" + style="@style/Aw.Typography.Sub" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@id/box_layout" + android:layout_alignParentEnd="true" + android:layout_marginEnd="@dimen/standard_16" + android:visibility="gone" + tools:text="Instruction" + tools:visibility="visible" /> \ No newline at end of file diff --git a/build.gradle b/build.gradle index 2b5610a4ba..5a1fe3edc2 100644 --- a/build.gradle +++ b/build.gradle @@ -1,3 +1,6 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + + buildscript { repositories { // don't add anything here until you read to the bottom of this bracket @@ -9,72 +12,24 @@ buildscript { // don't do that. add that repository to app/build.gradle } dependencies { - classpath 'com.android.tools.build:gradle:7.4.2' - //NB - there is an issue with newer versions of gradle. The APK balloons out, so far haven't diagnosed why. - //If you want to try upgrading gradle plugin past 3.5.4 you will need to also diagnose the APK ballooning issue. - classpath "io.realm:realm-gradle-plugin:10.18.0" + classpath libs.gradle + classpath libs.realm.gradle.plugin // WARNING WARNING WARNING // you are about to add here a dependency to be used in the Android app // don't do that. add that dependency to app/build.gradle - classpath 'com.google.gms:google-services:4.4.0' - classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.0' - classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.9' - classpath 'com.google.dagger:hilt-android-gradle-plugin:2.48' + classpath libs.google.services + classpath libs.kotlin.gradle.plugin + classpath libs.firebase.crashlytics.gradle + classpath libs.hilt.android.gradle.plugin } } -allprojects { - repositories { - google() - mavenCentral() - maven { url = uri("https://jitpack.io") } - maven { url = uri("https://maven.pkg.github.com/trustwallet/wallet-core") - credentials { - username = getGitHubUsername() as String?: System.getenv("GITHUB_USER") - password = getPAT() as String?: System.getenv("GITHUB_TOKEN") - } - } - // WARNING WARNING WARNING - // you are about to add here a repository which provides some library for the Android app - // don't do that. add that repository to app/build.gradle - } - - configurations.configureEach { - resolutionStrategy { - force 'com.google.firebase:firebase-analytics:16.5.0' - } - } - - tasks.withType(Test).configureEach { - maxParallelForks = 2 - forkEvery = 80 - maxHeapSize = "2048m" - minHeapSize = "1024m" - } -} - -tasks.register("clean") { - delete rootProject.buildDir +plugins { + alias(libs.plugins.androidApplication) apply false } -gradle.projectsEvaluated({ - def username = getGitHubUsername() - def password = getPAT() - if (!username || !password) { - throw new GradleException('Please provide GitHub username and Personal Access Token. Find more here https://github.com/alphaWallet/alpha-wallet-android#getting-started') +configurations.configureEach { + resolutionStrategy { + force 'com.google.firebase:firebase-analytics:16.5.0' } -}) - -private String getGitHubUsername() { - project.findProperty("gpr.user") -} - -private String getPAT() { - def encodedToken = project.findProperty("gpr.key") - def firstEncode = new String(encodedToken.decodeBase64()) - new String(firstEncode.decodeBase64()) } - -//task clean(type: Delete) { -// delete rootProject.buildDir -//} diff --git a/dmz/build.gradle b/dmz/build.gradle index e68ece9eb7..c2820830f2 100644 --- a/dmz/build.gradle +++ b/dmz/build.gradle @@ -1,10 +1,7 @@ -buildscript { - repositories { - mavenCentral() - } - dependencies { - classpath("org.springframework.boot:spring-boot-gradle-plugin:2.2.2.RELEASE") - } +// TODO: DMZ not buildable until Gradle v8 is ready. Use previous snapshot for DMZ until fixed + +plugins { + id 'org.springframework.boot' version '2.2.2.RELEASE' } apply plugin: 'java' @@ -17,9 +14,11 @@ bootJar { launchScript() } -repositories { - mavenCentral() -} +/* +dependencies { + classpath("org.springframework.boot:spring-boot-gradle-plugin:2.2.2.RELEASE") + } + */ sourceCompatibility = 1.8 targetCompatibility = 1.8 diff --git a/gradle.properties b/gradle.properties index 0842382cd3..a337e45051 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,13 +1,28 @@ -## This file *is* checked into Version Control Systems, -# as it contains information specific to this project. - -# for most projects, the default setting for gradle, -# MaxMetaspaceSize=256m should be sufficent but this project has grown -# bigger than typical. +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. For more details, visit +# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true android.enableJetifier=true android.jetifier.ignorelist=bcprov-jdk15on,bcprov-jdk18on,com.squareup.moshi -android.useAndroidX=true -org.gradle.jvmargs=-Xms2048m -Xmx4096m +org.gradle.configuration-cache=true + +# Enables namespacing of each library's R class so that its R class includes only the +# resources declared in the library itself and none from the library's dependencies, +# thereby reducing the size of the R class for that library +android.nonTransitiveRClass=true # Base64 Encoded GitHub PAT, # Make sure only check read:packages and read:user permissions if you want to create your own PAT, diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000000..690960c8fd --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,116 @@ +[versions] +agp = "8.3.0" +android_lint_reporter = "2.1.0" +androidBom = "1.23.0" +bcprovJdk15onVersion = "1.70" +bcprovJdk18onVersion = "1.77" +biometric = "1.1.0" +compiler = "4.16.0" +core = "3.5.3" +coreSplashscreen = "1.0.1" +coreVersion = "1.10.3" +detektGradlePlugin = "1.23.5" +firebaseAnalytics = "21.5.1" +firebaseCrashlytics = "18.6.2" +firebaseCrashlyticsGradle = "2.9.9" +firebaseMessaging = "23.4.1" +flexboxLayout = "2.0.1" +glide = "4.16.0" +googleServices = "4.4.1" +gradle = "8.3.0" +gridlayout = "1.0.0" +gson = "2.10.1" +guava = "30.1.1-android" +hiltAndroidGradlePlugin = "2.48" +jacocoAndroid = "0.1.5" +jsonSimple = "4.0.1" +junit = "4.13.2" +junitVersion = "1.1.5" +espressoCore = "3.5.1" +appcompat = "1.6.1" +keyboardvisibilityevent = "3.0.0-RC3" +kotlinGradlePlugin = "1.8.0" +kotson = "2.5.0" +mailchimpSdkAndroid = "1.0.0" +material = "1.11.0" +constraintlayout = "2.1.4" +lifecycleLivedataKtx = "2.7.0" +lifecycleViewmodelKtx = "2.7.0" +mixpanelAndroid = "5.8.4" +navigationFragment = "2.7.7" +navigationUi = "2.7.7" +okhttp = "4.12.0" +preferenceKtx = "1.2.1" +realmGradlePlugin = "10.18.0" +recyclerview = "1.3.2" +rxjava = "2.2.21" +rxandroid = "2.1.1" +slf4jApi = "2.0.9" +springBootGradlePlugin = "2.2.2.RELEASE" +timber = "5.0.1" +vectordrawable = "1.1.0" +walletCore = "3.2.18" +webkit = "1.10.0" +workRuntimeKtx = "2.9.0" +zxingAndroidEmbedded = "4.3.0" + +[libraries] +android-bom = { module = "com.walletconnect:android-bom", version.ref = "androidBom" } +android_lint_reporter = { module = "gradle.plugin.com.worker8.android_lint_reporter:android_lint_reporter", version.ref = "android_lint_reporter" } +biometric = { module = "androidx.biometric:biometric", version.ref = "biometric" } +bouncycastle-bcprov-jdk15on = { module = "org.bouncycastle:bcprov-jdk15on", version.ref = "bcprovJdk15onVersion" } +bouncycastle-bcprov-jdk18on = { module = "org.bouncycastle:bcprov-jdk18on", version.ref = "bcprovJdk18onVersion" } +compiler = { module = "com.github.bumptech.glide:compiler", version.ref = "compiler" } +core = { module = "com.google.zxing:core", version.ref = "core" } +core-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "coreSplashscreen" } +detekt-gradle-plugin = { module = "io.gitlab.arturbosch.detekt:detekt-gradle-plugin", version.ref = "detektGradlePlugin" } +firebase-analytics = { module = "com.google.firebase:firebase-analytics", version.ref = "firebaseAnalytics" } +firebase-crashlytics = { module = "com.google.firebase:firebase-crashlytics", version.ref = "firebaseCrashlytics" } +firebase-crashlytics-gradle = { module = "com.google.firebase:firebase-crashlytics-gradle", version.ref = "firebaseCrashlyticsGradle" } +firebase-messaging = { module = "com.google.firebase:firebase-messaging", version.ref = "firebaseMessaging" } +flexbox-layout = { module = "com.github.google:flexbox-layout", version.ref = "flexboxLayout" } +glide = { module = "com.github.bumptech.glide:glide", version.ref = "glide" } +google-services = { module = "com.google.gms:google-services", version.ref = "googleServices" } +gradle = { module = "com.android.tools.build:gradle", version.ref = "gradle" } +gridlayout = { module = "androidx.gridlayout:gridlayout", version.ref = "gridlayout" } +gson = { module = "com.google.code.gson:gson", version.ref = "gson" } +guava = { module = "com.google.guava:guava", version.ref = "guava" } +hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hiltAndroidGradlePlugin" } +hilt-android-gradle-plugin = { module = "com.google.dagger:hilt-android-gradle-plugin", version.ref = "hiltAndroidGradlePlugin" } +hilt-compiler = { module = "com.google.dagger:hilt-compiler", version.ref = "hiltAndroidGradlePlugin" } +jacoco-android = { module = "com.dicedmelon.gradle:jacoco-android", version.ref = "jacocoAndroid" } +json-simple = { module = "com.github.cliftonlabs:json-simple", version.ref = "jsonSimple" } +junit = { group = "junit", name = "junit", version.ref = "junit" } +ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } +espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } +appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } +keyboardvisibilityevent = { module = "net.yslibrary.keyboardvisibilityevent:keyboardvisibilityevent", version.ref = "keyboardvisibilityevent" } +kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlinGradlePlugin" } +kotson = { module = "com.github.salomonbrys.kotson:kotson", version.ref = "kotson" } +mailchimp-sdk-android = { module = "com.github.mailchimp:mailchimp-sdk-android", version.ref = "mailchimpSdkAndroid" } +material = { group = "com.google.android.material", name = "material", version.ref = "material" } +constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" } +lifecycle-livedata-ktx = { group = "androidx.lifecycle", name = "lifecycle-livedata-ktx", version.ref = "lifecycleLivedataKtx" } +lifecycle-viewmodel-ktx = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-ktx", version.ref = "lifecycleViewmodelKtx" } +mixpanel-android = { module = "com.mixpanel.android:mixpanel-android", version.ref = "mixpanelAndroid" } +navigation-fragment = { group = "androidx.navigation", name = "navigation-fragment", version.ref = "navigationFragment" } +navigation-ui = { group = "androidx.navigation", name = "navigation-ui", version.ref = "navigationUi" } +okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" } +play-core = { module = "com.google.android.play:core", version.ref = "coreVersion" } +preference-ktx = { module = "androidx.preference:preference-ktx", version.ref = "preferenceKtx" } +realm-gradle-plugin = { module = "io.realm:realm-gradle-plugin", version.ref = "realmGradlePlugin" } +recyclerview = { module = "androidx.recyclerview:recyclerview", version.ref = "recyclerview" } +rxjava = { module = "io.reactivex.rxjava2:rxjava", version.ref = "rxjava" } +rxandroid = { module = "io.reactivex.rxjava2:rxandroid", version.ref = "rxandroid" } +slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4jApi" } +spring-boot-gradle-plugin = { module = "org.springframework.boot:spring-boot-gradle-plugin", version.ref = "springBootGradlePlugin" } +timber = { module = "com.jakewharton.timber:timber", version.ref = "timber" } +vectordrawable = { module = "androidx.vectordrawable:vectordrawable", version.ref = "vectordrawable" } +wallet-core = { module = "com.trustwallet:wallet-core", version.ref = "walletCore" } +webkit = { module = "androidx.webkit:webkit", version.ref = "webkit" } +work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "workRuntimeKtx" } +zxing-android-embedded = { module = "com.journeyapps:zxing-android-embedded", version.ref = "zxingAndroidEmbedded" } + +[plugins] +androidApplication = { id = "com.android.application", version.ref = "agp" } + diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 096a3b1018..cdf8663b75 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Fri Apr 22 09:46:44 IST 2022 +#Sat Mar 09 17:11:16 AEDT 2024 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip distributionPath=wrapper/dists -zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/hardware_stub/build.gradle b/hardware_stub/build.gradle index ded82c8ca0..d03ef5b8c3 100644 --- a/hardware_stub/build.gradle +++ b/hardware_stub/build.gradle @@ -4,11 +4,11 @@ plugins { android { namespace 'com.alphawallet.hardware' + compileSdk 34 defaultConfig { minSdk 24 - compileSdk 33 - targetSdkVersion 33 + targetSdk 34 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles "consumer-rules.pro" @@ -28,7 +28,7 @@ android { dependencies { - testImplementation 'junit:junit:4.13.2' - androidTestImplementation 'androidx.test.ext:junit:1.1.5' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' + testImplementation libs.junit + androidTestImplementation libs.ext.junit + androidTestImplementation libs.espresso.core } diff --git a/lib/build.gradle b/lib/build.gradle index 7971d6bdd8..e31be6432c 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -1,33 +1,45 @@ -buildscript { - repositories { - google() - jcenter() - mavenCentral() - maven { url 'https://jitpack.io' } - maven { url 'https://plugins.gradle.org/m2/' } +plugins { + id 'com.android.library' +} + +android { + namespace 'com.alphawallet' + compileSdk 34 + + defaultConfig { + minSdk 24 + targetSdk 34 + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles "consumer-rules.pro" } - dependencies { - classpath 'com.github.jengelman.gradle.plugins:shadow:6.1.0' + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 } } -apply plugin: 'com.github.johnrengelman.shadow' -apply plugin: 'java-library' - dependencies { - //implementation 'org.web3j:core:4.9.8' + implementation files('../app/libs/abi-4.9.8.jar') implementation files('../app/libs/core-4.9.8.jar') implementation files('../app/libs/crypto-4.9.8.jar') implementation files('../app/libs/utils-4.9.8.jar') - testImplementation 'junit:junit:4.13.2' + testImplementation libs.junit - implementation 'org.bouncycastle:bcprov-jdk15on:1.70' + implementation libs.bouncycastle.bcprov.jdk15on // https://mvnrepository.com/artifact/com.github.cliftonlabs/json-simple - implementation group: 'com.github.cliftonlabs', name: 'json-simple', version: '4.0.1' + implementation libs.json.simple // https://mvnrepository.com/artifact/com.google.code.gson/gson - implementation group: 'com.google.code.gson', name: 'gson', version: '2.10.1' + implementation libs.gson } sourceCompatibility = "17" diff --git a/lib/src/main/java/com/alphawallet/token/tools/VerifyXMLDSig.java b/lib/src/main/java/com/alphawallet/token/tools/VerifyXMLDSig.java deleted file mode 100644 index cadb79b0e9..0000000000 --- a/lib/src/main/java/com/alphawallet/token/tools/VerifyXMLDSig.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.alphawallet.token.tools; - -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.io.UnsupportedEncodingException; -import com.alphawallet.token.entity.XMLDsigVerificationResult; -import com.github.cliftonlabs.json_simple.JsonObject; - -public class VerifyXMLDSig { - - //Invoke with Lambda via VerifyXMLDSig interface - public Response VerifyTSMLFile(Request req) throws Exception { - JsonObject result = validateSSLCertificate(req.file); - return new Response(result); - } - - public JsonObject validateSSLCertificate(String file) throws UnsupportedEncodingException { - JsonObject result = new JsonObject(); - InputStream stream = new ByteArrayInputStream(file.getBytes("UTF-8")); - XMLDsigVerificationResult XMLDsigVerificationResult = new XMLDSigVerifier().VerifyXMLDSig(stream); - if (XMLDsigVerificationResult.isValid) - { - result.put("result", "pass"); - result.put("issuer", XMLDsigVerificationResult.issuerPrincipal); - result.put("subject", XMLDsigVerificationResult.subjectPrincipal); - result.put("keyName", XMLDsigVerificationResult.keyName); - result.put("keyType", XMLDsigVerificationResult.keyType); - } - else - { - result.put("result", "fail"); - result.put("failureReason", XMLDsigVerificationResult.failureReason); - } - return result; - } - - public static class Request { - String file; - - public String getFile() { - return file; - } - - public void setFile(String file) { - this.file = file; - } - - public Request(String file) { - this.file = file; - } - - public Request() { - } - } - - public static class Response { - JsonObject result; - - public JsonObject getResult() { return result; } - - public void setResult(JsonObject result) { this.result = result; } - - public Response(JsonObject result) { - this.result = result; - } - - public Response() { - } - } - -} - - diff --git a/lib/src/main/java/com/alphawallet/token/tools/XMLDSigVerifier.java b/lib/src/main/java/com/alphawallet/token/tools/XMLDSigVerifier.java deleted file mode 100644 index 2c7d3aa1d8..0000000000 --- a/lib/src/main/java/com/alphawallet/token/tools/XMLDSigVerifier.java +++ /dev/null @@ -1,401 +0,0 @@ -package com.alphawallet.token.tools; - -import org.w3c.dom.DOMException; -import org.w3c.dom.Document; -import org.w3c.dom.NodeList; -import org.xml.sax.SAXException; -import java.io.IOException; -import java.io.InputStream; -import java.security.InvalidAlgorithmParameterException; -import java.security.Key; -import java.security.KeyException; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.Principal; -import java.security.PublicKey; -import java.security.Security; -import java.security.cert.CertPathValidator; -import java.security.cert.CertPathValidatorException; -import java.security.cert.CertificateException; -import java.security.cert.CertificateExpiredException; -import java.security.cert.CertificateFactory; -import java.security.cert.CertificateNotYetValidException; -import java.security.cert.PKIXParameters; -import java.security.cert.TrustAnchor; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import javax.net.ssl.TrustManagerFactory; -import javax.net.ssl.X509TrustManager; -import javax.xml.crypto.AlgorithmMethod; -import javax.xml.crypto.KeySelector; -import javax.xml.crypto.KeySelectorException; -import javax.xml.crypto.KeySelectorResult; -import javax.xml.crypto.MarshalException; -import javax.xml.crypto.XMLCryptoContext; -import javax.xml.crypto.XMLStructure; -import javax.xml.crypto.dsig.XMLSignature; -import javax.xml.crypto.dsig.XMLSignatureException; -import javax.xml.crypto.dsig.XMLSignatureFactory; -import javax.xml.crypto.dsig.dom.DOMValidateContext; -import javax.xml.crypto.dsig.keyinfo.KeyInfo; -import javax.xml.crypto.dsig.keyinfo.KeyName; -import javax.xml.crypto.dsig.keyinfo.KeyValue; -import javax.xml.crypto.dsig.keyinfo.X509Data; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; - -import com.alphawallet.token.entity.XMLDsigVerificationResult; - -/** - * James Sangalli mans this project since July 2019. - * Stormbird Pte Ltd, in Sydney - */ - -/** - * This verifiers an XML Signature using the JSR 105 API. It assumes - * the key needed to verify the signature is certified by one of the - * X.509 certificates in the KeyInfo, and that X.509 certificate, - * together with any other found in KeyInfo, form a chain of - * certificate to a top level certified by one of the trusted - * authorities of the installed JRE - * - * Out of scope: - * - Multi-signature XML file - * - Ignores any public key provided in KeyInfo - * - * See the test case for usage examples. - */ -public class XMLDSigVerifier { - - public XMLDsigVerificationResult VerifyXMLDSig(InputStream fileStream) - { - XMLDsigVerificationResult result = new XMLDsigVerificationResult(); - try - { - //Signature will also be validated in this call, if it fails an exception is thrown - //No point to validate the certificate is this signature is invalid to begin with - //And TrustAddressGenerator needs to get an XMLSignature too. - XMLSignature signature = getValidXMLSignature(fileStream); - result.isValid = true; //would go to catch if this was not the case - //check that the tsml file is signed by a valid certificate - return validateCertificateIssuer(signature, result); - } - catch(Exception e) - { - result.isValid = false; - result.failureReason = e.getMessage(); - return result; - } - } - - XMLSignature getValidXMLSignature(InputStream fileStream) - throws ParserConfigurationException, - IOException, - SAXException, - MarshalException, - XMLSignatureException, - DOMException - { - DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); - dbFactory.setNamespaceAware(true); - DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); - Document xml = dBuilder.parse(fileStream); - xml.getDocumentElement().normalize(); - - // Find Signature element - NodeList nl = xml.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature"); - if (nl.getLength() == 0) - { - throw new DOMException(DOMException.INDEX_SIZE_ERR, "Missing elements"); - } - - // Create a DOM XMLSignatureFactory that will be used to unmarshal the - // document containing the XMLSignature - XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM"); - - // Create a DOMValidateContext and specify a KeyValue KeySelector - // and document context - DOMValidateContext valContext = new DOMValidateContext(new SigningCertSelector(), nl.item(0)); - - // unmarshal the XMLSignature - XMLSignature signature = fac.unmarshalXMLSignature(valContext); - - boolean validSig = signature.validate(valContext); - if(!validSig) - { - throw new XMLSignatureException("Invalid XML signature"); - } - return signature; - } - - private void validateCertificateChain(List certList) - throws NoSuchAlgorithmException, - KeyStoreException, - InvalidAlgorithmParameterException, - CertificateException, - CertPathValidatorException - { - // By default on Oracle JRE, algorithm is PKIX - TrustManagerFactory tmf = TrustManagerFactory - .getInstance(TrustManagerFactory.getDefaultAlgorithm()); - // 'null' will initialise the tmf with the default CA certs installed - // with the JRE. - tmf.init((KeyStore) null); - - X509TrustManager tm = (X509TrustManager) tmf.getTrustManagers()[0]; - CertPathValidator cpv = CertPathValidator.getInstance("PKIX"); - Set anch = new HashSet<>(); - for (X509Certificate cert : tm.getAcceptedIssuers()) - { - anch.add(new TrustAnchor(cert, null)); - } - PKIXParameters params = new PKIXParameters(anch); - Security.setProperty("ocsp.enable", "true"); - params.setRevocationEnabled(true); - CertificateFactory factory = CertificateFactory.getInstance("X.509"); - try - { - cpv.validate(factory.generateCertPath(certList), params); - } - catch (CertPathValidatorException e) - { - System.out.println(e.getIndex()); - //if the timestamp check fails because the cert is expired - //we allow this to continue (code 0) - if(e.getIndex() != 0) - { - throw e; - } - } - } - - private X509Certificate findRootCert(List certificates) { - X509Certificate rootCert = null; - for (X509Certificate cert : certificates) { - X509Certificate signer = this.findSignerCertificate(cert, certificates); - if (signer == null || signer.equals(cert)) { - rootCert = cert; - break; - } - } - return rootCert; - } - - private List reorderCertificateChain(List chain) - { - X509Certificate[] reorderedChain = new X509Certificate[chain.size()]; - int position = chain.size() - 1; - X509Certificate rootCert = this.findRootCert(chain); - reorderedChain[position] = rootCert; - for (X509Certificate cert = rootCert; - (cert = this.findSignedCert(cert, chain)) != null && position > 0; - reorderedChain[position] = cert - ) { - --position; - } - return Arrays.asList(reorderedChain); - } - - private X509Certificate findSignedCert(X509Certificate signingCert, List certificates) - { - X509Certificate signed = null; - for (X509Certificate cert : certificates) - { - Principal signingCertSubjectDN = signingCert.getSubjectDN(); - Principal certIssuerDN = cert.getIssuerDN(); - if (certIssuerDN.equals(signingCertSubjectDN) && !cert.equals(signingCert)) - { - signed = cert; - break; - } - } - return signed; - } - - - private X509Certificate findSignerCertificate(X509Certificate signedCert, List certificates) { - X509Certificate signer = null; - for (X509Certificate cert : certificates) { - Principal certSubjectDN = cert.getSubjectDN(); - Principal issuerDN = signedCert.getIssuerDN(); - if (certSubjectDN.equals(issuerDN)) { - signer = cert; - break; - } - } - return signer; - } - - private XMLDsigVerificationResult validateCertificateIssuer(XMLSignature signature, XMLDsigVerificationResult result) { - try - { - KeyInfo xmlKeyInfo = signature.getKeyInfo(); - List certList = getCertificateChainFromXML(xmlKeyInfo.getContent()); - List orderedCerts = reorderCertificateChain(certList); - X509Certificate signingCert = selectSigningKeyFromXML(xmlKeyInfo.getContent()); - //Throws if invalid - validateCertificateChain(orderedCerts); - result.issuerPrincipal = signingCert.getIssuerX500Principal().getName(); - result.subjectPrincipal = signingCert.getSubjectX500Principal().getName(); - result.keyType = signingCert.getSigAlgName(); - for (XMLStructure o : xmlKeyInfo.getContent()) - { - if (o instanceof KeyName) - { - result.keyName = ((KeyName) o).getName(); - } - } - } - catch(Exception e) - { - result.isValid = false; - result.failureReason = e.getMessage(); - } - return result; - } - - private List getCertificateChainFromXML(List xmlElements) throws KeyStoreException, ClassCastException { - boolean found = false; - List certs = new ArrayList<>(); - for (int i = 0; i < xmlElements.size(); i++) - { - XMLStructure xmlStructure = xmlElements.get(i); - if (xmlStructure instanceof X509Data) - { - if(found) throw new KeyStoreException("Duplicate X509Data element"); - found = true; - for (Object o : ((X509Data) xmlStructure).getContent()) - { - if (o instanceof X509Certificate) - { - certs.add((X509Certificate)o); - } - } - } - } - return certs; - } - - private PublicKey recoverPublicKeyFromXML(List xmlElements) throws KeyStoreException { - boolean found = false; - PublicKey keyVal = null; - for (int i = 0; i < xmlElements.size(); i++) - { - XMLStructure xmlStructure = xmlElements.get(i); - if (xmlStructure instanceof KeyValue kv) - { - //should only be one KeyValue - if(found) throw new KeyStoreException("Duplicate Key found"); - found = true; - try - { - keyVal = kv.getPublicKey(); - } - catch (KeyException e) - { - e.printStackTrace(); - } - } - } - return keyVal; - } - - private X509Certificate selectSigningKeyFromXML(List xmlElements) throws KeyStoreException, CertificateNotYetValidException { - PublicKey recovered = recoverPublicKeyFromXML(xmlElements); - //Certificates from the XML might be in the wrong order - List certList = reorderCertificateChain(getCertificateChainFromXML(xmlElements)); - for (X509Certificate crt : certList) - { - try - { - crt.checkValidity(); - } - catch (CertificateExpiredException e) - { - //allow this - System.out.println("Allowing expired cert: " + e.getMessage()); - continue; - } - if (recovered != null) - { - PublicKey certKey = crt.getPublicKey(); - if (Arrays.equals(recovered.getEncoded(), certKey.getEncoded())) - { - return crt; - } - } - else if (crt.getSigAlgName().equals("SHA256withECDSA")) - { - return crt; - } - } - //if non recovered, simply return the first certificate? - return certList.get(0); - - } - - private class SigningCertSelector extends KeySelector - { - public KeySelectorResult select( - KeyInfo keyInfo, - KeySelector.Purpose purpose, - AlgorithmMethod method, - XMLCryptoContext context - ) throws KeySelectorException - { - if (keyInfo == null) throw new KeySelectorException("Null KeyInfo object!"); - PublicKey signer = null; - List list = keyInfo.getContent(); - boolean found = false; - for (XMLStructure xmlStructure : list) - { - if (xmlStructure instanceof KeyValue kv) - { - if(found) throw new KeySelectorException("Duplicate KeyValue"); - found = true; - try - { - signer = kv.getPublicKey(); - } - catch (KeyException e) - { - e.printStackTrace(); - } - } - } - if(signer != null) return new SimpleKeySelectorResult(signer); - X509Certificate signingCert = null; - try - { - signingCert = selectSigningKeyFromXML(list); - } - catch (Exception e) - { - throw new KeySelectorException(e.getMessage()); - } - if (signingCert != null) - { - return new SimpleKeySelectorResult(signingCert.getPublicKey()); - } - else - { - throw new KeySelectorException("No KeyValue element found!"); - } - } - } - - private class SimpleKeySelectorResult implements KeySelectorResult - { - private final PublicKey pk; - SimpleKeySelectorResult(PublicKey pk) { - this.pk = pk; - } - public Key getKey() { return pk; } - } -} diff --git a/settings.gradle b/settings.gradle index 63ef013e5c..a9a1f85625 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,45 @@ -include ':app', ':lib', ':util', ':dmz', ':hardware_stub' +pluginManagement { + repositories { + google { + content { + includeGroupByRegex("com\\.android.*") + includeGroupByRegex("com\\.google.*") + includeGroupByRegex("androidx.*") + } + } + mavenCentral() + gradlePluginPortal() + } +} +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS) + repositories { + google() + mavenCentral() + maven { url 'https://jitpack.io' } + maven { + url = uri("https://maven.pkg.github.com/trustwallet/wallet-core") + credentials { + username = getGitHubUsername() as String ?: System.getenv("GITHUB_USER") + password = getPAT() as String ?: System.getenv("GITHUB_TOKEN") + } + } + maven { + url "https://plugins.gradle.org/m2/" + } + maven { url 'https://repo.spring.io/milestone' } + } +} + +private static String getGitHubUsername() { + "JamesSmartCell" +} + +private static String getPAT() { + def encodedToken = "WjJod1gyaFZWVFF4ZGtoVk1qTkdiVVJqTlRWUGVtSlFZVlI0UkRocldYQkRZak5FTlU5aFNnPT0=" + def firstEncode = new String(encodedToken.decodeBase64()) + new String(firstEncode.decodeBase64()) +} + +rootProject.name = "AlphaWallet" +include ':app', ':lib', ':hardware_stub' //, ':dmz' //TODO: Fix DMZ for Gradle 8 diff --git a/util/build.gradle b/util/build.gradle index 936509ac53..7c8a420404 100644 --- a/util/build.gradle +++ b/util/build.gradle @@ -24,7 +24,7 @@ dependencies { implementation files('../app/libs/crypto-4.9.8.jar') implementation files('../app/libs/utils-4.9.8.jar') - implementation 'com.squareup.okhttp3:okhttp:4.11.0' + implementation 'com.squareup.okhttp3:okhttp:4.12.0' implementation 'io.reactivex.rxjava2:rxjava:2.2.21' testImplementation 'junit:junit:4.12' implementation project(path: ':lib') From f7e6f4ba37bf170e87f3dbae42d7077c7546b9aa Mon Sep 17 00:00:00 2001 From: James Brown Date: Sun, 10 Mar 2024 11:11:04 +1100 Subject: [PATCH 02/11] Update build test script --- build.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build.sh b/build.sh index 2289678e41..c835403e58 100644 --- a/build.sh +++ b/build.sh @@ -1,6 +1,7 @@ #!/usr/bin/env bash -cd dmz && ../gradlew -i build && ../gradlew -i test && cd .. +#No DMZ until we fix the Gradle 8 build +#cd dmz && ../gradlew -i build && ../gradlew -i test && cd .. cd lib && ../gradlew -i build && ../gradlew -i test && cd .. cd util && ../gradlew -i build && ../gradlew -i test && cd .. ./gradlew clean jacocoTestNoAnalyticsDebugUnitTestReport -x lint -x detekt From 5a94f899b0755667782f26c0ca08d5434c93c30c Mon Sep 17 00:00:00 2001 From: James Brown Date: Sun, 10 Mar 2024 11:14:19 +1100 Subject: [PATCH 03/11] Update build test script --- build.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sh b/build.sh index c835403e58..33f57f37c6 100644 --- a/build.sh +++ b/build.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash -#No DMZ until we fix the Gradle 8 build +#No DMZ until we fix the Gradle 8 build. Deprecate util build #cd dmz && ../gradlew -i build && ../gradlew -i test && cd .. cd lib && ../gradlew -i build && ../gradlew -i test && cd .. -cd util && ../gradlew -i build && ../gradlew -i test && cd .. +#cd util && ../gradlew -i build && ../gradlew -i test && cd .. ./gradlew clean jacocoTestNoAnalyticsDebugUnitTestReport -x lint -x detekt From 809de30a27287c4c26bfb680f6290d09b06a13a8 Mon Sep 17 00:00:00 2001 From: James Brown Date: Sun, 10 Mar 2024 18:46:43 +1100 Subject: [PATCH 04/11] Improve consistency of TokenIcons and fix Opensea feed --- app/src/main/java/com/alphawallet/app/C.java | 19 +- .../app/entity/ContractInteract.java | 15 +- .../alphawallet/app/entity/ImageEntry.java | 15 ++ .../app/entity/nftassets/NFTAsset.java | 11 +- .../app/entity/tokens/ERC1155Token.java | 16 ++ .../app/entity/tokens/ERC721Token.java | 20 +- .../alphawallet/app/entity/tokens/Token.java | 10 + .../SharedPreferenceRepository.java | 2 +- .../app/repository/TokenLocalSource.java | 3 +- .../app/repository/TokenRepository.java | 7 +- .../app/repository/TokenRepositoryType.java | 3 +- .../app/repository/TokensRealmSource.java | 37 ++-- .../app/service/AssetDefinitionService.java | 50 ----- .../app/service/OpenSeaService.java | 208 +++++++----------- .../app/service/TokensService.java | 41 +++- .../alphawallet/app/ui/MyAddressActivity.java | 4 +- .../app/ui/NFTAssetDetailActivity.java | 6 +- .../com/alphawallet/app/ui/SendActivity.java | 14 +- .../app/ui/TransactionDetailActivity.java | 2 +- .../ui/widget/adapter/ActivityAdapter.java | 3 +- .../app/ui/widget/adapter/TraitsAdapter.java | 9 +- .../app/ui/widget/entity/IconItem.java | 34 --- .../app/ui/widget/holder/EventHolder.java | 2 +- .../app/ui/widget/holder/TokenGridHolder.java | 2 +- .../app/ui/widget/holder/TokenHolder.java | 2 +- .../app/ui/widget/holder/TokenListHolder.java | 4 +- .../ui/widget/holder/TransactionHolder.java | 6 +- .../app/ui/widget/holder/TransferHolder.java | 2 +- .../java/com/alphawallet/app/util/Utils.java | 2 +- .../app/util/ens/AWEnsResolver.java | 7 +- .../app/viewmodel/TokenIconViewModel.java | 171 ++++++++++++++ .../app/widget/ActionSheetDialog.java | 2 +- .../app/widget/BalanceDisplayWidget.java | 6 +- .../app/widget/EventDetailWidget.java | 4 +- .../alphawallet/app/widget/InputAmount.java | 8 +- .../com/alphawallet/app/widget/TokenIcon.java | 207 ++++++++--------- .../app/widget/TokenInfoHeaderView.java | 2 +- lib/build.gradle | 7 +- .../alphawallet/attestation/Attestation.java | 37 ++++ 39 files changed, 585 insertions(+), 415 deletions(-) create mode 100644 app/src/main/java/com/alphawallet/app/entity/ImageEntry.java create mode 100644 app/src/main/java/com/alphawallet/app/viewmodel/TokenIconViewModel.java diff --git a/app/src/main/java/com/alphawallet/app/C.java b/app/src/main/java/com/alphawallet/app/C.java index 712716bd06..505a088184 100644 --- a/app/src/main/java/com/alphawallet/app/C.java +++ b/app/src/main/java/com/alphawallet/app/C.java @@ -313,23 +313,8 @@ public enum TokenStatus { // OpenSea APIs public static final String OPENSEA_COLLECTION_API_MAINNET = "https://api.opensea.io/collection/"; - - public static final String OPENSEA_ASSETS_API_MAINNET = "https://api.opensea.io/api/v1/assets"; - public static final String OPENSEA_ASSETS_API_TESTNET = "https://testnets-api.opensea.io/api/v1/assets"; - public static final String OPENSEA_ASSETS_API_MATIC = "https://api.opensea.io/api/v2/assets/matic"; - public static final String OPENSEA_ASSETS_API_ARBITRUM = "https://api.opensea.io/api/v2/assets/arbitrum"; - public static final String OPENSEA_ASSETS_API_AVALANCHE = "https://api.opensea.io/api/v2/assets/avalanche"; - public static final String OPENSEA_ASSETS_API_KLAYTN = "https://api.opensea.io/api/v2/assets/klaytn"; - public static final String OPENSEA_ASSETS_API_OPTIMISM = "https://api.opensea.io/api/v2/assets/optimism"; - - public static final String OPENSEA_SINGLE_ASSET_API_MAINNET = "https://api.opensea.io/api/v1/asset/"; - public static final String OPENSEA_SINGLE_ASSET_API_TESTNET = "https://testnets-api.opensea.io/api/v1/asset/"; - public static final String OPENSEA_SINGLE_ASSET_API_MATIC = "https://api.opensea.io/api/v2/metadata/matic/"; - public static final String OPENSEA_SINGLE_ASSET_API_ARBITRUM = "https://api.opensea.io/api/v2/metadata/arbitrum/"; - public static final String OPENSEA_SINGLE_ASSET_API_AVALANCHE = "https://api.opensea.io/api/v2/metadata/avalanche/"; - public static final String OPENSEA_SINGLE_ASSET_API_KLAYTN = "https://api.opensea.io/api/v2/metadata/klaytn/"; - public static final String OPENSEA_SINGLE_ASSET_API_OPTIMISM = "https://api.opensea.io/api/v2/metadata/optimism/"; - + public static final String OPENSEA_ASSETS_API_V2 = "https://api.opensea.io/api/v2/chain/{CHAIN}/account/{ADDRESS}/nfts"; + public static final String OPENSEA_NFT_API_V2 = "https://api.opensea.io/api/v2/chain/{CHAIN}/contract/{ADDRESS}/nfts/{TOKEN_ID}"; //Timing public static long CONNECT_TIMEOUT = 10; //Seconds diff --git a/app/src/main/java/com/alphawallet/app/entity/ContractInteract.java b/app/src/main/java/com/alphawallet/app/entity/ContractInteract.java index 1c1bf8bb64..ffb42d7729 100644 --- a/app/src/main/java/com/alphawallet/app/entity/ContractInteract.java +++ b/app/src/main/java/com/alphawallet/app/entity/ContractInteract.java @@ -44,6 +44,13 @@ public Single> getScriptFileURI() return Single.fromCallable(() -> callSmartContractFuncAdaptiveArray(token.tokenInfo.chainId, getScriptURI(), token.getAddress(), token.getWallet())).observeOn(Schedulers.io()); } + public Single getContractURIResult() + { + return Single.fromCallable(() -> callSmartContractFunction(token.tokenInfo.chainId, getContractURI(), token.getAddress(), token.getWallet())) + .map(this::loadMetaData) + .observeOn(Schedulers.io()); + } + private String loadMetaData(String tokenURI) { if (TextUtils.isEmpty(tokenURI)) @@ -65,7 +72,7 @@ public NFTAsset fetchTokenMetadata(BigInteger tokenId) { //1. get TokenURI (check for non-standard URI - check "tokenURI" and "uri") String responseValue = callSmartContractFunction(token.tokenInfo.chainId, getTokenURI(tokenId), token.getAddress(), token.getWallet()); - if (responseValue == null) + if (TextUtils.isEmpty(responseValue)) { responseValue = callSmartContractFunction(token.tokenInfo.chainId, getTokenURI2(tokenId), token.getAddress(), token.getWallet()); } @@ -102,6 +109,12 @@ private static Function getScriptURI() { Collections.singletonList(new TypeReference() {})); } + private static Function getContractURI() { + return new Function("contractURI", + Collections.emptyList(), + Collections.singletonList(new TypeReference() {})); + } + private static void setupClient() { if (client == null) diff --git a/app/src/main/java/com/alphawallet/app/entity/ImageEntry.java b/app/src/main/java/com/alphawallet/app/entity/ImageEntry.java new file mode 100644 index 0000000000..4e82e039b5 --- /dev/null +++ b/app/src/main/java/com/alphawallet/app/entity/ImageEntry.java @@ -0,0 +1,15 @@ +package com.alphawallet.app.entity; + +public class ImageEntry +{ + final public long chainId; + final public String address; + final public String imageUrl; + + public ImageEntry(long networkId, String address, String imageUrl) + { + this.chainId = networkId; + this.address = address; + this.imageUrl = imageUrl; + } +} diff --git a/app/src/main/java/com/alphawallet/app/entity/nftassets/NFTAsset.java b/app/src/main/java/com/alphawallet/app/entity/nftassets/NFTAsset.java index 521743226f..c608bb7497 100644 --- a/app/src/main/java/com/alphawallet/app/entity/nftassets/NFTAsset.java +++ b/app/src/main/java/com/alphawallet/app/entity/nftassets/NFTAsset.java @@ -52,11 +52,13 @@ public NFTAsset[] newArray(int size) }; private static final String LOADING_TOKEN = "*Loading*"; private static final String ID = "id"; + private static final String OPENSEA_ID = "identifier"; private static final String ATTN_ID = "attn_id"; private static final String NAME = "name"; private static final String IMAGE = "image"; private static final String IMAGE_URL = "image_url"; private static final String IMAGE_PREVIEW = "image_preview_url"; + private static final String COLLECTION = "collection"; private static final String DESCRIPTION = "description"; private static final String IMAGE_ORIGINAL_URL = "image_original_url"; private static final String IMAGE_ANIMATION = "animation_url"; @@ -66,7 +68,7 @@ public NFTAsset[] newArray(int size) private static final String[] IMAGE_THUMBNAIL_DESIGNATORS = {IMAGE_PREVIEW, IMAGE, IMAGE_URL, IMAGE_ORIGINAL_URL, IMAGE_ANIMATION}; private static final String BACKGROUND_COLOUR = "background_color"; private static final String EXTERNAL_LINK = "external_link"; - private static final List DESIRED_PARAMS = Arrays.asList(NAME, BACKGROUND_COLOUR, IMAGE_URL, IMAGE, IMAGE_ORIGINAL_URL, IMAGE_PREVIEW, DESCRIPTION, EXTERNAL_LINK, IMAGE_ANIMATION); + private static final List DESIRED_PARAMS = Arrays.asList(NAME, BACKGROUND_COLOUR, IMAGE_URL, IMAGE, IMAGE_ORIGINAL_URL, IMAGE_PREVIEW, DESCRIPTION, EXTERNAL_LINK, IMAGE_ANIMATION, COLLECTION); private static final List ATTRIBUTE_DESCRIPTOR = Arrays.asList("attributes", "traits"); private final Map assetMap = new HashMap<>(); private final Map attributeMap = new HashMap<>(); @@ -265,6 +267,11 @@ private void loadFromMetaData(String metaData) try { JSONObject jsonData = new JSONObject(metaData); + if (jsonData.has("nft")) + { + //need to unwrap this return value + jsonData = jsonData.getJSONObject("nft"); + } Iterator keys = jsonData.keys(); String id = null; @@ -272,7 +279,7 @@ private void loadFromMetaData(String metaData) { String key = keys.next(); String value = jsonData.getString(key); - if (key.equals(ID)) + if (key.equals(ID) || key.equals(OPENSEA_ID)) { id = value; } diff --git a/app/src/main/java/com/alphawallet/app/entity/tokens/ERC1155Token.java b/app/src/main/java/com/alphawallet/app/entity/tokens/ERC1155Token.java index aafc723bed..eb6c6a18db 100644 --- a/app/src/main/java/com/alphawallet/app/entity/tokens/ERC1155Token.java +++ b/app/src/main/java/com/alphawallet/app/entity/tokens/ERC1155Token.java @@ -814,6 +814,22 @@ && getNFTTokenId(tokenId).compareTo(BigInteger.valueOf(0xFFFF)) < 0 && getNFTTokenId(tokenId).compareTo(BigInteger.ZERO) > 0; } + @Override + public String getFirstImageUrl() + { + if (assets != null && !assets.isEmpty() && assets.values().stream().findFirst().isPresent()) + { + //get first asset + NFTAsset firstAsset = assets.values().stream().findFirst().get(); + if (firstAsset.hasImageAsset()) + { + return firstAsset.getThumbnail(); + } + } + + return ""; + } + @Override public boolean isBatchTransferAvailable() { diff --git a/app/src/main/java/com/alphawallet/app/entity/tokens/ERC721Token.java b/app/src/main/java/com/alphawallet/app/entity/tokens/ERC721Token.java index 1276c54836..24055cbcf0 100644 --- a/app/src/main/java/com/alphawallet/app/entity/tokens/ERC721Token.java +++ b/app/src/main/java/com/alphawallet/app/entity/tokens/ERC721Token.java @@ -427,7 +427,7 @@ private void updateEnumerableBalance(Web3j web3j, Realm realm) throws IOExceptio { // find tokenId from index String tokenId = callSmartContractFunction(tokenInfo.chainId, tokenOfOwnerByIndex(BigInteger.valueOf(tokenIndex)), getAddress(), getWallet()); - if (tokenId == null) continue; + if (TextUtils.isEmpty(tokenId)) continue; tokenIdsHeld.add(new BigInteger(tokenId)); } } @@ -620,7 +620,7 @@ private HashSet checkBalances(Web3j web3j, HashSet event for (BigInteger tokenId : eventIds) { String owner = callSmartContractFunction(tokenInfo.chainId, ownerOf(tokenId), getAddress(), getWallet()); - if (owner == null || owner.equalsIgnoreCase(getWallet())) + if (TextUtils.isEmpty(owner) || owner.equalsIgnoreCase(getWallet())) { heldTokens.add(tokenId); } @@ -728,6 +728,22 @@ public EthFilter getSendBalanceFilter(Event event, DefaultBlockParameter startBl return filter; } + @Override + public String getFirstImageUrl() + { + if (tokenBalanceAssets != null && !tokenBalanceAssets.isEmpty() && tokenBalanceAssets.values().stream().findFirst().isPresent()) + { + //get first asset + NFTAsset firstAsset = tokenBalanceAssets.values().stream().findFirst().get(); + if (firstAsset.hasImageAsset()) + { + return firstAsset.getThumbnail(); + } + } + + return ""; + } + public String getTransferID(Transaction tx) { if (tx.transactionInput != null && tx.transactionInput.miscData.size() > 0) diff --git a/app/src/main/java/com/alphawallet/app/entity/tokens/Token.java b/app/src/main/java/com/alphawallet/app/entity/tokens/Token.java index 7033d5f4db..7b34cc55bd 100644 --- a/app/src/main/java/com/alphawallet/app/entity/tokens/Token.java +++ b/app/src/main/java/com/alphawallet/app/entity/tokens/Token.java @@ -1083,6 +1083,11 @@ public Single> getScriptURI() return contractInteract.getScriptFileURI(); } + public Single getContractURI() + { + return contractInteract.getContractURIResult(); + } + /** * Event filters for send and receive of the token, overriden by the token type */ @@ -1153,4 +1158,9 @@ public String getAttestationCollectionId(TokenDefinition td) { return getTSKey(); } + + public String getFirstImageUrl() + { + return ""; + } } diff --git a/app/src/main/java/com/alphawallet/app/repository/SharedPreferenceRepository.java b/app/src/main/java/com/alphawallet/app/repository/SharedPreferenceRepository.java index fc4e1d7dba..5ace4ee3f2 100644 --- a/app/src/main/java/com/alphawallet/app/repository/SharedPreferenceRepository.java +++ b/app/src/main/java/com/alphawallet/app/repository/SharedPreferenceRepository.java @@ -478,7 +478,7 @@ public void setPostNotificationsPermissionRequested(String address, boolean hasR @Override public boolean getUseTSViewer() { - return pref.getBoolean(USE_TOKENSCRIPT_VIEWER, true); + return pref.getBoolean(USE_TOKENSCRIPT_VIEWER, false); } @Override diff --git a/app/src/main/java/com/alphawallet/app/repository/TokenLocalSource.java b/app/src/main/java/com/alphawallet/app/repository/TokenLocalSource.java index cb0f8e3010..8f8abe78b8 100644 --- a/app/src/main/java/com/alphawallet/app/repository/TokenLocalSource.java +++ b/app/src/main/java/com/alphawallet/app/repository/TokenLocalSource.java @@ -3,6 +3,7 @@ import android.util.Pair; import com.alphawallet.app.entity.ContractType; +import com.alphawallet.app.entity.ImageEntry; import com.alphawallet.app.entity.Wallet; import com.alphawallet.app.entity.nftassets.NFTAsset; import com.alphawallet.app.entity.tokendata.TokenGroup; @@ -38,7 +39,7 @@ public interface TokenLocalSource void deleteRealmTokens(Wallet wallet, List tcmList); - void storeTokenUrl(long chainId, String address, String imageUrl); + void storeTokenUrl(List entries); Token initNFTAssets(Wallet wallet, Token tokens); diff --git a/app/src/main/java/com/alphawallet/app/repository/TokenRepository.java b/app/src/main/java/com/alphawallet/app/repository/TokenRepository.java index 01c79b1372..af4c1a1041 100644 --- a/app/src/main/java/com/alphawallet/app/repository/TokenRepository.java +++ b/app/src/main/java/com/alphawallet/app/repository/TokenRepository.java @@ -16,6 +16,7 @@ import com.alphawallet.app.C; import com.alphawallet.app.entity.ContractLocator; import com.alphawallet.app.entity.ContractType; +import com.alphawallet.app.entity.ImageEntry; import com.alphawallet.app.entity.NetworkInfo; import com.alphawallet.app.entity.TransferFromEventResponse; import com.alphawallet.app.entity.Wallet; @@ -1295,9 +1296,9 @@ public Single fetchIsRedeemed(Token token, BigInteger tokenId) } @Override - public void addImageUrl(long networkId, String address, String imageUrl) + public void addImageUrl(List entries) { - localSource.storeTokenUrl(networkId, address, imageUrl); + localSource.storeTokenUrl(entries); } public static Web3j getWeb3jServiceForEvents(long chainId) @@ -1358,7 +1359,7 @@ public static String callSmartContractFunction(long chainId, // } - return null; + return ""; } public static List callSmartContractFuncAdaptiveArray(long chainId, diff --git a/app/src/main/java/com/alphawallet/app/repository/TokenRepositoryType.java b/app/src/main/java/com/alphawallet/app/repository/TokenRepositoryType.java index fd232f4f87..5556f7451e 100644 --- a/app/src/main/java/com/alphawallet/app/repository/TokenRepositoryType.java +++ b/app/src/main/java/com/alphawallet/app/repository/TokenRepositoryType.java @@ -4,6 +4,7 @@ import com.alphawallet.app.entity.ContractLocator; import com.alphawallet.app.entity.ContractType; +import com.alphawallet.app.entity.ImageEntry; import com.alphawallet.app.entity.TransferFromEventResponse; import com.alphawallet.app.entity.Wallet; import com.alphawallet.app.entity.nftassets.NFTAsset; @@ -66,7 +67,7 @@ public interface TokenRepositoryType Single fetchIsRedeemed(Token token, BigInteger tokenId); - void addImageUrl(long chainId, String address, String imageUrl); + void addImageUrl(List entries); void updateLocalAddress(String walletAddress); diff --git a/app/src/main/java/com/alphawallet/app/repository/TokensRealmSource.java b/app/src/main/java/com/alphawallet/app/repository/TokensRealmSource.java index 45d29a0aaf..ea6142ee42 100644 --- a/app/src/main/java/com/alphawallet/app/repository/TokensRealmSource.java +++ b/app/src/main/java/com/alphawallet/app/repository/TokensRealmSource.java @@ -9,6 +9,7 @@ import com.alphawallet.app.entity.ContractType; import com.alphawallet.app.entity.CustomViewSettings; +import com.alphawallet.app.entity.ImageEntry; import com.alphawallet.app.entity.NetworkInfo; import com.alphawallet.app.entity.Wallet; import com.alphawallet.app.entity.nftassets.NFTAsset; @@ -717,31 +718,29 @@ public Single storeTokenInfo(Wallet wallet, TokenInfo tInfo, Contract } @Override - public void storeTokenUrl(long networkId, String address, String imageUrl) + public void storeTokenUrl(List entries) { try (Realm realm = realmManager.getRealmInstance(IMAGES_DB)) { - final String instanceKey = address.toLowerCase() + "-" + networkId; - final RealmAuxData instance = realm.where(RealmAuxData.class).equalTo("instanceKey", instanceKey).findFirst(); + realm.executeTransaction(r -> { + for (ImageEntry thisEntry : entries) + { + final String instanceKey = thisEntry.address.toLowerCase() + "-" + thisEntry.chainId; + RealmAuxData instance = r.where(RealmAuxData.class).equalTo("instanceKey", instanceKey).findFirst(); - if (instance == null || !instance.getResult().equals(imageUrl)) - { - realm.executeTransactionAsync(r -> { - RealmAuxData aux; - if (instance == null) + if (instance == null || !instance.getResult().equals(thisEntry.imageUrl)) { - aux = r.createObject(RealmAuxData.class, instanceKey); - } - else - { - aux = instance; - } + if (instance == null) + { + instance = r.createObject(RealmAuxData.class, instanceKey); + } - aux.setResult(imageUrl); - aux.setResultTime(System.currentTimeMillis()); - r.insertOrUpdate(aux); - }); - } + instance.setResult(thisEntry.imageUrl); + instance.setResultTime(System.currentTimeMillis()); + r.insertOrUpdate(instance); + } + } + }); } } diff --git a/app/src/main/java/com/alphawallet/app/service/AssetDefinitionService.java b/app/src/main/java/com/alphawallet/app/service/AssetDefinitionService.java index 2739528a7d..56e19b4aa2 100644 --- a/app/src/main/java/com/alphawallet/app/service/AssetDefinitionService.java +++ b/app/src/main/java/com/alphawallet/app/service/AssetDefinitionService.java @@ -3148,56 +3148,6 @@ public void storeTokenViewHeight(long chainId, String address, int listViewHeigh } } - public String getTokenImageUrl(long networkId, String address) - { - String url = ""; - String instanceKey = address.toLowerCase() + "-" + networkId; - try (Realm realm = realmManager.getRealmInstance(IMAGES_DB)) - { - RealmAuxData instance = realm.where(RealmAuxData.class) - .equalTo("instanceKey", instanceKey) - .findFirst(); - - if (instance != null) - { - url = instance.getResult(); - } - } - catch (Exception ex) - { - Timber.e(ex); - } - - return url; - } - - public Pair getFallbackUrlForToken(Token token) - { - boolean storedOverride = false; - String correctedAddr = Keys.toChecksumAddress(token.getAddress()); - - String tURL = getTokenImageUrl(token.tokenInfo.chainId, token.getAddress()); - if (TextUtils.isEmpty(tURL)) - { - tURL = Utils.getTWTokenImageUrl(token.tokenInfo.chainId, correctedAddr); - } - else - { - storedOverride = true; - } - - return new Pair<>(tURL, storedOverride); - } - - public void storeImageUrl(long chainId, String imageUrl) - { - String tokenAddress = Utils.getTokenAddrFromAWUrl(imageUrl); - if (!TextUtils.isEmpty(tokenAddress)) - { - tokensService.addTokenImageUrl(chainId, tokenAddress, imageUrl); - } - } - public Single fetchViewHeight(long chainId, String address) { return Single.fromCallable(() -> { diff --git a/app/src/main/java/com/alphawallet/app/service/OpenSeaService.java b/app/src/main/java/com/alphawallet/app/service/OpenSeaService.java index 906b42287f..c69bef76aa 100644 --- a/app/src/main/java/com/alphawallet/app/service/OpenSeaService.java +++ b/app/src/main/java/com/alphawallet/app/service/OpenSeaService.java @@ -1,5 +1,15 @@ package com.alphawallet.app.service; +import static com.alphawallet.ethereum.EthereumNetworkBase.ARBITRUM_MAIN_ID; +import static com.alphawallet.ethereum.EthereumNetworkBase.AVALANCHE_ID; +import static com.alphawallet.ethereum.EthereumNetworkBase.BINANCE_MAIN_ID; +import static com.alphawallet.ethereum.EthereumNetworkBase.KLAYTN_ID; +import static com.alphawallet.ethereum.EthereumNetworkBase.MAINNET_ID; +import static com.alphawallet.ethereum.EthereumNetworkBase.OPTIMISTIC_MAIN_ID; +import static com.alphawallet.ethereum.EthereumNetworkBase.POLYGON_ID; +import static com.alphawallet.ethereum.EthereumNetworkBase.POLYGON_TEST_ID; +import static com.alphawallet.ethereum.EthereumNetworkBase.SEPOLIA_TESTNET_ID; + import android.net.Uri; import android.text.TextUtils; import android.text.format.DateUtils; @@ -24,6 +34,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import io.reactivex.Single; @@ -40,11 +51,23 @@ public class OpenSeaService { private final OkHttpClient httpClient; - private static final int PAGE_SIZE = 50; + private static final int PAGE_SIZE = 200; private final Map imageUrls = new HashMap<>(); private static final TokenFactory tf = new TokenFactory(); private final LongSparseArray networkCheckTimes = new LongSparseArray<>(); - private final LongSparseArray pageOffsets = new LongSparseArray<>(); + private final Map pageOffsets = new ConcurrentHashMap<>(); + + private final Map API_CHAIN_MAP = Map.of( + MAINNET_ID, "ethereum", + KLAYTN_ID, "klaytn", + POLYGON_TEST_ID, "mumbai", + POLYGON_ID, "matic", + OPTIMISTIC_MAIN_ID, "optimism", + ARBITRUM_MAIN_ID, "arbitrum", + SEPOLIA_TESTNET_ID, "sepolia", + AVALANCHE_ID, "avalanche", + BINANCE_MAIN_ID, "bsc" + ); public OpenSeaService() { @@ -115,46 +138,42 @@ public Single getTokens(String address, if (!canCheckChain(networkId)) return new Token[0]; networkCheckTimes.put(networkId, currentTime); - int pageOffset = pageOffsets.get(networkId, 0); + String pageCursor = pageOffsets.getOrDefault(networkId, ""); Timber.d("Fetch from opensea : %s", networkName); do { - String jsonData = fetchAssets(networkId, address, pageOffset); + String jsonData = fetchAssets(networkId, address, pageCursor); if (!JsonUtils.hasAssets(jsonData)) { return foundTokens.values().toArray(new Token[0]); //on error return results found so far } JSONObject result = new JSONObject(jsonData); - JSONArray assets; - if (result.has("assets")) - { - assets = result.getJSONArray("assets"); - } - else - { - assets = result.getJSONArray("results"); - } + JSONArray assets = result.getJSONArray("nfts"); receivedTokens = assets.length(); - pageOffset += assets.length(); //process this page of results processOpenseaTokens(foundTokens, assets, address, networkId, networkName, tokensService); currentPage++; + pageCursor = result.getString("next"); + if (TextUtils.isEmpty(pageCursor)) + { + break; + } } - while (receivedTokens == PAGE_SIZE && currentPage <= 3); //fetch 4 pages for each loop + while (currentPage <= 3); //fetch 4 pages for each loop + + pageOffsets.put(networkId, pageCursor); if (receivedTokens < PAGE_SIZE) { - Timber.d("Reset OpenSeaAPI reads at: %s", pageOffset); - pageOffsets.put(networkId, 0); + Timber.d("Reset OpenSeaAPI reads at: %s", pageCursor); } else { - pageOffsets.put(networkId, pageOffset); networkCheckTimes.put(networkId, currentTime - 55 * DateUtils.SECOND_IN_MILLIS); //do another read within 5 seconds } @@ -181,19 +200,18 @@ private void processOpenseaTokens(Map foundTokens, for (int i = 0; i < assets.length(); i++) { JSONObject assetJSON = assets.getJSONObject(i); - AssetContract assetContract = - new Gson().fromJson(assetJSON.getString("asset_contract"), AssetContract.class); + String tokenStandard = assetJSON.getString("token_standard").toLowerCase(); - if (assetContract != null && !TextUtils.isEmpty(assetContract.getSchemaName())) + if (!TextUtils.isEmpty(tokenStandard)) { - switch (assetContract.getSchemaName()) + switch (tokenStandard) { - case "ERC721": - handleERC721(assetContract, assetList, assetJSON, networkId, foundTokens, tokensService, + case "erc721": + handleERC721(assetList, assetJSON, networkId, foundTokens, tokensService, networkName, address); break; - case "ERC1155": - handleERC1155(assetContract, assetList, assetJSON, networkId, foundTokens, tokensService, + case "erc1155": + handleERC1155(assetList, assetJSON, networkId, foundTokens, tokensService, networkName, address); break; } @@ -201,8 +219,7 @@ private void processOpenseaTokens(Map foundTokens, } } - private void handleERC721(AssetContract assetContract, - Map> assetList, + private void handleERC721(Map> assetList, JSONObject assetJSON, long networkId, Map foundTokens, @@ -212,52 +229,50 @@ private void handleERC721(AssetContract assetContract, { NFTAsset asset = new NFTAsset(assetJSON.toString()); - BigInteger tokenId = assetJSON.has("token_id") ? - new BigInteger(assetJSON.getString("token_id")) + BigInteger tokenId = assetJSON.has("identifier") ? + new BigInteger(assetJSON.getString("identifier")) : null; if (tokenId == null) return; - addAssetImageToHashMap(assetContract.getAddress(), assetContract.getImageUrl()); + String contractAddress = assetJSON.getString("contract"); + String collectionName = assetJSON.getString("collection"); - Token token = foundTokens.get(assetContract.getAddress()); + Token token = foundTokens.get(contractAddress); if (token == null) { TokenInfo tInfo; ContractType type; long lastCheckTime = 0; - Token checkToken = svs.getToken(networkId, assetContract.getAddress()); + Token checkToken = svs.getToken(networkId, contractAddress); if (checkToken != null && (checkToken.isERC721() || checkToken.isERC721Ticket())) { - assetList.put(assetContract.getAddress(), checkToken.getTokenAssets()); + assetList.put(contractAddress, checkToken.getTokenAssets()); tInfo = checkToken.tokenInfo; type = checkToken.getInterfaceSpec(); lastCheckTime = checkToken.lastTxTime; - JSONObject collectionJSON = assetJSON.getJSONObject("collection"); - String collectionName = collectionJSON.getString("name"); - if (!TextUtils.isEmpty(collectionName) && (TextUtils.isEmpty(checkToken.tokenInfo.name) || !collectionName.equals(checkToken.tokenInfo.name))) + if (!TextUtils.isEmpty(collectionName) && (TextUtils.isEmpty(checkToken.tokenInfo.name))) { //Update to collection name if the token name is blank, or if the collection name is not blank and current token name is different - tInfo = new TokenInfo(assetContract.getAddress(), collectionName, assetContract.getSymbol(), 0, tInfo.isEnabled, networkId); + tInfo = new TokenInfo(contractAddress, collectionName, "", 0, tInfo.isEnabled, networkId); } } else //if we haven't seen the contract before, or it was previously logged as something other than a ERC721 variant then specify undetermined flag { - tInfo = new TokenInfo(assetContract.getAddress(), assetContract.getName(), assetContract.getSymbol(), 0, true, networkId); - type = ContractType.ERC721_UNDETERMINED; + tInfo = new TokenInfo(contractAddress, asset.getName(), "", 0, true, networkId); + type = ContractType.ERC721; } token = tf.createToken(tInfo, type, networkName); token.setTokenWallet(address); token.lastTxTime = lastCheckTime; - foundTokens.put(assetContract.getAddress(), token); + foundTokens.put(contractAddress, token); } asset.updateAsset(tokenId, assetList.get(token.getAddress())); token.addAssetToTokenBalanceAssets(tokenId, asset); } - private void handleERC1155(AssetContract assetContract, - Map> assetList, + private void handleERC1155(Map> assetList, JSONObject assetJSON, long networkId, Map foundTokens, @@ -267,39 +282,39 @@ private void handleERC1155(AssetContract assetContract, { NFTAsset asset = new NFTAsset(assetJSON.toString()); - BigInteger tokenId = assetJSON.has("token_id") ? - new BigInteger(assetJSON.getString("token_id")) + BigInteger tokenId = assetJSON.has("identifier") ? + new BigInteger(assetJSON.getString("identifier")) : null; if (tokenId == null) return; - addAssetImageToHashMap(assetContract.getAddress(), assetContract.getImageUrl()); + String contractAddress = assetJSON.getString("contract"); + String collectionName = assetJSON.getString("collection"); - Token token = foundTokens.get(assetContract.getAddress()); + Token token = foundTokens.get(contractAddress); if (token == null) { TokenInfo tInfo; ContractType type; long lastCheckTime = 0; - Token checkToken = svs.getToken(networkId, assetContract.getAddress()); + Token checkToken = svs.getToken(networkId, contractAddress); if (checkToken != null && checkToken.getInterfaceSpec() == ContractType.ERC1155) { - assetList.put(assetContract.getAddress(), checkToken.getTokenAssets()); + assetList.put(contractAddress, checkToken.getTokenAssets()); tInfo = checkToken.tokenInfo; type = checkToken.getInterfaceSpec(); lastCheckTime = checkToken.lastTxTime; } else { - tInfo = new TokenInfo(assetContract.getAddress(), assetContract.getName(), assetContract.getSymbol(), 0, true, networkId); + tInfo = new TokenInfo(contractAddress, collectionName, "", 0, true, networkId); type = ContractType.ERC1155; } token = tf.createToken(tInfo, type, networkName); token.setTokenWallet(address); token.lastTxTime = lastCheckTime; - token.setAssetContract(assetContract); - foundTokens.put(assetContract.getAddress(), token); + foundTokens.put(contractAddress, token); } asset.updateAsset(tokenId, assetList.get(token.getAddress())); token.addAssetToTokenBalanceAssets(tokenId, asset); @@ -352,92 +367,37 @@ public Single getCollection(Token token, String slug) fetchCollection(token.tokenInfo.chainId, slug)); } - public String fetchAssets(long networkId, String address, int offset) + public String fetchAssets(long networkId, String address, String pageCursor) { - String api = ""; - String ownerOption = "owner"; - - //TODO: Put these into a mapping - if (networkId == EthereumNetworkBase.MAINNET_ID) - { - api = C.OPENSEA_ASSETS_API_MAINNET; - } - else if (networkId == EthereumNetworkBase.GOERLI_ID) - { - api = C.OPENSEA_ASSETS_API_TESTNET; - } - else if (networkId == EthereumNetworkBase.POLYGON_ID) - { - api = C.OPENSEA_ASSETS_API_MATIC; - ownerOption = "owner_address"; - } - else if (networkId == EthereumNetworkBase.ARBITRUM_MAIN_ID) - { - api = C.OPENSEA_ASSETS_API_ARBITRUM; - ownerOption = "owner_address"; - } - else if (networkId == EthereumNetworkBase.AVALANCHE_ID) - { - api = C.OPENSEA_ASSETS_API_AVALANCHE; - ownerOption = "owner_address"; - } - else if (networkId == EthereumNetworkBase.KLAYTN_ID) + String mappingName = API_CHAIN_MAP.get(networkId); + if (TextUtils.isEmpty(mappingName)) { - api = C.OPENSEA_ASSETS_API_KLAYTN; - ownerOption = "owner_address"; - } - else if (networkId == EthereumNetworkBase.OPTIMISTIC_MAIN_ID) - { - api = C.OPENSEA_ASSETS_API_OPTIMISM; - ownerOption = "owner_address"; + return JsonUtils.EMPTY_RESULT; } - if (!TextUtils.isEmpty(api)) - { - Uri.Builder builder = new Uri.Builder(); - builder.encodedPath(api) - .appendQueryParameter(ownerOption, address) - .appendQueryParameter("limit", String.valueOf(PAGE_SIZE)) - .appendQueryParameter("offset", String.valueOf(offset)); + String api = C.OPENSEA_ASSETS_API_V2.replace("{CHAIN}", mappingName).replace("{ADDRESS}", address); - return executeRequest(networkId, builder.build().toString()); + Uri.Builder builder = new Uri.Builder(); + builder.encodedPath(api) + .appendQueryParameter("limit", String.valueOf(PAGE_SIZE)); + + if (!TextUtils.isEmpty(pageCursor)) + { + builder.appendQueryParameter("next", pageCursor); } - return JsonUtils.EMPTY_RESULT; + return executeRequest(networkId, builder.build().toString()); } public String fetchAsset(long networkId, String contractAddress, String tokenId) { - String api = ""; - if (networkId == EthereumNetworkBase.MAINNET_ID) - { - api = C.OPENSEA_SINGLE_ASSET_API_MAINNET + contractAddress + "/" + tokenId; - } - else if (networkId == EthereumNetworkBase.GOERLI_ID) - { - api = C.OPENSEA_SINGLE_ASSET_API_TESTNET + contractAddress + "/" + tokenId; - } - else if (networkId == EthereumNetworkBase.POLYGON_ID) - { - api = C.OPENSEA_SINGLE_ASSET_API_MATIC + contractAddress + "/" + tokenId; - } - else if (networkId == EthereumNetworkBase.ARBITRUM_MAIN_ID) - { - api = C.OPENSEA_SINGLE_ASSET_API_ARBITRUM + contractAddress + "/" + tokenId; - } - else if (networkId == EthereumNetworkBase.AVALANCHE_ID) - { - api = C.OPENSEA_SINGLE_ASSET_API_AVALANCHE + contractAddress + "/" + tokenId; - } - else if (networkId == EthereumNetworkBase.KLAYTN_ID) - { - api = C.OPENSEA_SINGLE_ASSET_API_KLAYTN + contractAddress + "/" + tokenId; - } - else if (networkId == EthereumNetworkBase.OPTIMISTIC_MAIN_ID) + String mappingName = API_CHAIN_MAP.get(networkId); + if (TextUtils.isEmpty(mappingName)) { - api = C.OPENSEA_SINGLE_ASSET_API_OPTIMISM + contractAddress + "/" + tokenId; + return JsonUtils.EMPTY_RESULT; } + String api = C.OPENSEA_NFT_API_V2.replace("{CHAIN}", mappingName).replace("{ADDRESS}", contractAddress).replace("{TOKEN_ID}", tokenId); return executeRequest(networkId, api); } diff --git a/app/src/main/java/com/alphawallet/app/service/TokensService.java b/app/src/main/java/com/alphawallet/app/service/TokensService.java index d85c9a370f..c4e9ac95d5 100644 --- a/app/src/main/java/com/alphawallet/app/service/TokensService.java +++ b/app/src/main/java/com/alphawallet/app/service/TokensService.java @@ -3,6 +3,7 @@ import static com.alphawallet.app.repository.TokensRealmSource.databaseKey; import static com.alphawallet.ethereum.EthereumNetworkBase.MAINNET_ID; +import android.media.Image; import android.text.TextUtils; import android.text.format.DateUtils; import android.util.Pair; @@ -15,6 +16,7 @@ import com.alphawallet.app.entity.ContractLocator; import com.alphawallet.app.entity.ContractType; import com.alphawallet.app.entity.CustomViewSettings; +import com.alphawallet.app.entity.ImageEntry; import com.alphawallet.app.entity.NetworkInfo; import com.alphawallet.app.entity.ServiceSyncCallback; import com.alphawallet.app.entity.Wallet; @@ -78,6 +80,7 @@ public class TokensService private ContractLocator focusToken; private final ConcurrentLinkedDeque unknownTokens; private final ConcurrentLinkedQueue baseTokenCheck; + private final ConcurrentLinkedQueue imagesForWrite; private long openSeaCheckId; private boolean appHasFocus; private static boolean walletStartup = false; @@ -102,6 +105,8 @@ public class TokensService private Disposable tokenStoreDisposable; @Nullable private Disposable openSeaQueryDisposable; + @Nullable + private Disposable imageWriter; private static boolean done = false; @@ -120,6 +125,7 @@ public TokensService(EthereumNetworkRepositoryType ethereumNetworkRepository, focusToken = null; this.unknownTokens = new ConcurrentLinkedDeque<>(); this.baseTokenCheck = new ConcurrentLinkedQueue<>(); + this.imagesForWrite = new ConcurrentLinkedQueue<>(); setCurrentAddress(ethereumNetworkRepository.getCurrentWalletAddress()); //set current wallet address at service startup appHasFocus = true; transferCheckChain = 0; @@ -403,7 +409,6 @@ public void stopUpdateCycle() if (queryUnknownTokensDisposable != null && !queryUnknownTokensDisposable.isDisposed()) { queryUnknownTokensDisposable.dispose(); } if (openSeaQueryDisposable != null && !openSeaQueryDisposable.isDisposed()) { openSeaQueryDisposable.dispose(); } - IconItem.resetCheck(); pendingChainMap.clear(); tokenStoreList.clear(); baseTokenCheck.clear(); @@ -502,6 +507,35 @@ private void startUnknownCheck() } } + private void startImageWrite() + { + if (imageWriter == null || imageWriter.isDisposed()) + { + imageWriter = Observable.interval(500, 500, TimeUnit.MILLISECONDS) + .doOnNext(l -> writeImages()).subscribe(); + } + } + + private void writeImages() + { + if (imagesForWrite.isEmpty()) + { + imageWriter.dispose(); + imageWriter = null; + } + else + { + Single.fromCallable(() -> { + List entries = new ArrayList<>(imagesForWrite); + imagesForWrite.clear(); + tokenRepository.addImageUrl(entries); + return ""; + }) + .subscribeOn(Schedulers.io()) + .observeOn(Schedulers.io()).subscribe().isDisposed(); + } + } + private void startupPass() { if (!walletStartup) return; @@ -540,9 +574,12 @@ public String getNetworkSymbol(long chainId) return info.symbol; } + //Add to write queue public void addTokenImageUrl(long networkId, String address, String imageUrl) { - tokenRepository.addImageUrl(networkId, address, imageUrl); + ImageEntry entry = new ImageEntry(networkId, address, imageUrl); + imagesForWrite.add(entry); + startImageWrite(); } public Single update(String address, long chainId, ContractType type) diff --git a/app/src/main/java/com/alphawallet/app/ui/MyAddressActivity.java b/app/src/main/java/com/alphawallet/app/ui/MyAddressActivity.java index b8a4845189..8b04ef4c22 100644 --- a/app/src/main/java/com/alphawallet/app/ui/MyAddressActivity.java +++ b/app/src/main/java/com/alphawallet/app/ui/MyAddressActivity.java @@ -212,7 +212,7 @@ private void showPointOfSaleMode() private void setupPOSMode(NetworkInfo info) { if (token == null) token = viewModel.getTokenService().getToken(info.chainId, wallet.address); - amountInput.setupToken(token, viewModel.getAssetDefinitionService(), viewModel.getTokenService(), this); + amountInput.setupToken(token, viewModel.getTokenService(), this); amountInput.setAmount(""); updateCryptoAmount(BigDecimal.ZERO); } @@ -340,7 +340,7 @@ public void onWindowFocusChanged(boolean hasFocus) } else { - amountInput.setupToken(token, viewModel.getAssetDefinitionService(), viewModel.getTokenService(), this); + amountInput.setupToken(token, viewModel.getTokenService(), this); } } } diff --git a/app/src/main/java/com/alphawallet/app/ui/NFTAssetDetailActivity.java b/app/src/main/java/com/alphawallet/app/ui/NFTAssetDetailActivity.java index 56f2ad7474..58ff870fc2 100644 --- a/app/src/main/java/com/alphawallet/app/ui/NFTAssetDetailActivity.java +++ b/app/src/main/java/com/alphawallet/app/ui/NFTAssetDetailActivity.java @@ -5,6 +5,7 @@ import static com.alphawallet.app.widget.AWalletAlertDialog.WARNING; import static java.util.Collections.singletonList; +import android.annotation.SuppressLint; import android.content.Intent; import android.os.Bundle; import android.text.Html; @@ -90,6 +91,7 @@ public class NFTAssetDetailActivity extends BaseActivity implements StandardFunc private NFTAttributeLayout nftAttributeLayout; private NFTAttributeLayout tsAttributeLayout; private TextView tokenDescription; + @SuppressLint("RestrictedApi") private ActionMenuItemView refreshMenu; private ProgressBar progressBar; private TokenInfoCategoryView descriptionLabel; @@ -114,7 +116,7 @@ public class NFTAssetDetailActivity extends BaseActivity implements StandardFunc private boolean triggeredReload; private long chainId; private Web3TokenView tokenScriptView; - private boolean usingNativeTokenScript = false; + private boolean usingNativeTokenScript = true; @Override protected void onCreate(@Nullable Bundle savedInstanceState) @@ -309,7 +311,7 @@ private void setup() if (!viewModel.getUseTSViewer()) { TokenDefinition td = viewModel.getAssetDefinitionService().getAssetDefinition(this.token); - this.usingNativeTokenScript = td.nameSpace != null; + this.usingNativeTokenScript = (td != null && td.nameSpace != null); } if (asset != null && asset.isAttestation()) diff --git a/app/src/main/java/com/alphawallet/app/ui/SendActivity.java b/app/src/main/java/com/alphawallet/app/ui/SendActivity.java index d26e0b051d..6441f2da80 100644 --- a/app/src/main/java/com/alphawallet/app/ui/SendActivity.java +++ b/app/src/main/java/com/alphawallet/app/ui/SendActivity.java @@ -193,12 +193,6 @@ else if (item.getItemId() == R.id.action_show_contract) return false; } - @Override - public void onBackPressed() - { - onBack(); - } - @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { @@ -380,7 +374,7 @@ else if (result.type != EIP681Type.ADDRESS && result.chainId != token.tokenInfo. sendText.setText(R.string.transfer_request); token = viewModel.getToken(result.chainId, wallet.address); addressInput.setAddress(result.getAddress()); - amountInput.setupToken(token, viewModel.getAssetDefinitionService(), viewModel.getTokenService(), this); + amountInput.setupToken(token, viewModel.getTokenService(), this); amountInput.setAmount(ethAmount); setupTokenContent(); break; @@ -398,7 +392,7 @@ else if (resultToken.isERC20()) //ERC20 send request token = resultToken; setupTokenContent(); - amountInput.setupToken(token, viewModel.getAssetDefinitionService(), viewModel.getTokenService(), this); + amountInput.setupToken(token, viewModel.getTokenService(), this); //convert token amount into scaled value String convertedAmount = Convert.getConvertedValue(result.tokenAmount, token.tokenInfo.decimals); amountInput.setAmount(convertedAmount); @@ -432,7 +426,7 @@ private void showChainChangeDialog(long chainId) dialog.setButtonListener(v -> { //we should change the chain. token = viewModel.getToken(chainId, token.getAddress()); - amountInput.setupToken(token, viewModel.getAssetDefinitionService(), viewModel.getTokenService(), this); + amountInput.setupToken(token, viewModel.getTokenService(), this); dialog.dismiss(); validateEIP681Request(currentResult, false); }); @@ -488,7 +482,7 @@ protected void onDestroy() private void setupTokenContent() { amountInput = findViewById(R.id.input_amount); - amountInput.setupToken(token, viewModel.getAssetDefinitionService(), viewModel.getTokenService(), this); + amountInput.setupToken(token, viewModel.getTokenService(), this); addressInput = findViewById(R.id.input_address); addressInput.setAddressCallback(this); addressInput.setChainOverrideForWalletConnect(token.tokenInfo.chainId); diff --git a/app/src/main/java/com/alphawallet/app/ui/TransactionDetailActivity.java b/app/src/main/java/com/alphawallet/app/ui/TransactionDetailActivity.java index d1c10aba20..6e07dd5adc 100644 --- a/app/src/main/java/com/alphawallet/app/ui/TransactionDetailActivity.java +++ b/app/src/main/java/com/alphawallet/app/ui/TransactionDetailActivity.java @@ -244,7 +244,7 @@ private void setupTokenDetails() Token targetToken = viewModel.getToken(transaction.chainId, TextUtils.isEmpty(tokenAddress) ? transaction.to : tokenAddress); if (targetToken.isEthereum()) return; tokenDetailsLayout.setVisibility(View.VISIBLE); - icon.bindData(targetToken, viewModel.getTokenService()); + icon.bindData(targetToken); address.setText(Keys.toChecksumAddress(targetToken.getAddress())); tokenName.setText(targetToken.getFullName()); } diff --git a/app/src/main/java/com/alphawallet/app/ui/widget/adapter/ActivityAdapter.java b/app/src/main/java/com/alphawallet/app/ui/widget/adapter/ActivityAdapter.java index 919e7da648..bb44f6fcf3 100644 --- a/app/src/main/java/com/alphawallet/app/ui/widget/adapter/ActivityAdapter.java +++ b/app/src/main/java/com/alphawallet/app/ui/widget/adapter/ActivityAdapter.java @@ -116,8 +116,7 @@ public ActivityAdapter(TokensService service, FetchTransactionsInteract fetchTra public BinderViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { switch (viewType) { case TransactionHolder.VIEW_TYPE: - return new TransactionHolder(parent, tokensService, fetchTransactionsInteract, - assetService); + return new TransactionHolder(parent, tokensService, fetchTransactionsInteract); case EventHolder.VIEW_TYPE: return new EventHolder(parent, tokensService, fetchTransactionsInteract, assetService, this); diff --git a/app/src/main/java/com/alphawallet/app/ui/widget/adapter/TraitsAdapter.java b/app/src/main/java/com/alphawallet/app/ui/widget/adapter/TraitsAdapter.java index f720cd0ebe..1fbd98e3c9 100644 --- a/app/src/main/java/com/alphawallet/app/ui/widget/adapter/TraitsAdapter.java +++ b/app/src/main/java/com/alphawallet/app/ui/widget/adapter/TraitsAdapter.java @@ -70,7 +70,14 @@ public void onBindViewHolder(@NonNull TraitsAdapter.ViewHolder viewHolder, int i @Override public int getItemCount() { - return traitList.size(); + if (traitList != null) + { + return traitList.size(); + } + else + { + return 0; + } } static class ViewHolder extends RecyclerView.ViewHolder { diff --git a/app/src/main/java/com/alphawallet/app/ui/widget/entity/IconItem.java b/app/src/main/java/com/alphawallet/app/ui/widget/entity/IconItem.java index dbff547845..09f64baaed 100644 --- a/app/src/main/java/com/alphawallet/app/ui/widget/entity/IconItem.java +++ b/app/src/main/java/com/alphawallet/app/ui/widget/entity/IconItem.java @@ -7,46 +7,12 @@ public class IconItem { private final String url; - private final UseIcon useText; - - private final static Map iconLoadType = new ConcurrentHashMap<>(); public IconItem(String url, long chainId, String correctedAddress) { this.url = url; - this.useText = getLoadType(chainId, correctedAddress); - } - - private UseIcon getLoadType(long chainId, String correctedAddress) - { - String key = databaseKey(chainId, correctedAddress); - return iconLoadType.containsKey(key) - ? (iconLoadType.get(key) ? UseIcon.SECONDARY : UseIcon.NO_ICON) - : UseIcon.PRIMARY; } public String getUrl() { return url; } - - public boolean useTextSymbol() { - return useText == UseIcon.NO_ICON; - } - - public boolean usePrimary() { - return useText == UseIcon.PRIMARY; - } - - //Use Secondary icon - public static void secondaryFound(long chainId, String address) - { - iconLoadType.put(databaseKey(chainId, address.toLowerCase()), true); - } - - /** - * Resets the failed icon fetch checking - try again to load failed icons - */ - public static void resetCheck() - { - iconLoadType.clear(); - } } diff --git a/app/src/main/java/com/alphawallet/app/ui/widget/holder/EventHolder.java b/app/src/main/java/com/alphawallet/app/ui/widget/holder/EventHolder.java index c020ecc27c..440f05f398 100644 --- a/app/src/main/java/com/alphawallet/app/ui/widget/holder/EventHolder.java +++ b/app/src/main/java/com/alphawallet/app/ui/widget/holder/EventHolder.java @@ -93,7 +93,7 @@ public void bind(@Nullable EventMeta data, @NonNull Bundle addition) if (token == null) token = tokensService.getToken(data.chainId, walletAddress); String sym = token.getShortSymbol(); - tokenIcon.bindData(token, assetDefinition); + tokenIcon.bindData(token); String itemView = null; TokenDefinition td = assetDefinition.getAssetDefinition(token); diff --git a/app/src/main/java/com/alphawallet/app/ui/widget/holder/TokenGridHolder.java b/app/src/main/java/com/alphawallet/app/ui/widget/holder/TokenGridHolder.java index f636feffea..4cbd1626b0 100644 --- a/app/src/main/java/com/alphawallet/app/ui/widget/holder/TokenGridHolder.java +++ b/app/src/main/java/com/alphawallet/app/ui/widget/holder/TokenGridHolder.java @@ -52,7 +52,7 @@ public void bind(@Nullable TokenCardMeta tcm, @NonNull Bundle addition) { Token token = tokensService.getToken(tcm.getChain(), tcm.getAddress()); if (token == null) return; //TODO: Generate placeholder - imageIcon.bindData(token, assetDefinition); + imageIcon.bindData(token); name.setText(token.getName(assetDefinition, token.balance.intValue())); count.setText(getString(R.string.token_count, token.balance.intValue())); diff --git a/app/src/main/java/com/alphawallet/app/ui/widget/holder/TokenHolder.java b/app/src/main/java/com/alphawallet/app/ui/widget/holder/TokenHolder.java index 124ecc212d..a5de438022 100644 --- a/app/src/main/java/com/alphawallet/app/ui/widget/holder/TokenHolder.java +++ b/app/src/main/java/com/alphawallet/app/ui/widget/holder/TokenHolder.java @@ -155,7 +155,7 @@ else if (data.getNameWeight() < 1000L && !token.isEthereum()) balanceCoin.setText(getString(R.string.valueSymbol, coinBalance, symbol)); } - tokenIcon.bindData(token, assetDefinition); + tokenIcon.bindData(token); if (!token.isEthereum()) { tokenIcon.setChainIcon(token.tokenInfo.chainId); //Add in when we upgrade the design diff --git a/app/src/main/java/com/alphawallet/app/ui/widget/holder/TokenListHolder.java b/app/src/main/java/com/alphawallet/app/ui/widget/holder/TokenListHolder.java index 720b064dde..fe90e42a3c 100644 --- a/app/src/main/java/com/alphawallet/app/ui/widget/holder/TokenListHolder.java +++ b/app/src/main/java/com/alphawallet/app/ui/widget/holder/TokenListHolder.java @@ -69,7 +69,7 @@ public void bind(@Nullable TokenCardMeta data, @NonNull Bundle addition) switchEnabled.setOnCheckedChangeListener(null); switchEnabled.setChecked(data.isEnabled); switchEnabled.setOnCheckedChangeListener(this); - tokenIcon.bindData(token, assetDefinition); + tokenIcon.bindData(token); if (data.isEnabled) { @@ -109,4 +109,4 @@ public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) onTokenClickListener.onTokenClick(token, position, isChecked); } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/alphawallet/app/ui/widget/holder/TransactionHolder.java b/app/src/main/java/com/alphawallet/app/ui/widget/holder/TransactionHolder.java index 579f0fb451..21562a2536 100644 --- a/app/src/main/java/com/alphawallet/app/ui/widget/holder/TransactionHolder.java +++ b/app/src/main/java/com/alphawallet/app/ui/widget/holder/TransactionHolder.java @@ -44,12 +44,11 @@ public class TransactionHolder extends BinderViewHolder impleme private final TextView supplemental; private final TokensService tokensService; private final FetchTransactionsInteract transactionsInteract; - private final AssetDefinitionService assetService; private Transaction transaction; private String defaultAddress; - public TransactionHolder(ViewGroup parent, TokensService service, FetchTransactionsInteract interact, AssetDefinitionService svs) + public TransactionHolder(ViewGroup parent, TokensService service, FetchTransactionsInteract interact) { super(R.layout.item_transaction, parent); date = findViewById(R.id.text_tx_time); @@ -60,7 +59,6 @@ public TransactionHolder(ViewGroup parent, TokensService service, FetchTransacti supplemental = findViewById(R.id.supplimental); tokensService = service; transactionsInteract = interact; - assetService = svs; itemView.setOnClickListener(this); } @@ -97,7 +95,7 @@ public void bind(@Nullable TransactionMeta data, @NonNull Bundle addition) setTokenDetailName(token); //set colours and up/down arrow - tokenIcon.bindData(token, assetService); + tokenIcon.bindData(token); tokenIcon.setStatusIcon(token.getTxStatus(transaction)); tokenIcon.setChainIcon(token.tokenInfo.chainId); diff --git a/app/src/main/java/com/alphawallet/app/ui/widget/holder/TransferHolder.java b/app/src/main/java/com/alphawallet/app/ui/widget/holder/TransferHolder.java index 40b99b0fbf..07aa2d8916 100644 --- a/app/src/main/java/com/alphawallet/app/ui/widget/holder/TransferHolder.java +++ b/app/src/main/java/com/alphawallet/app/ui/widget/holder/TransferHolder.java @@ -98,7 +98,7 @@ public void bind(@Nullable TokenTransferData data, @NonNull Bundle addition) disposable.dispose(); } - tokenIcon.bindData(token, assetDefinition); + tokenIcon.bindData(token); //We haven't yet fetched the underlying transaction. Fetch and display if (tx == null) diff --git a/app/src/main/java/com/alphawallet/app/util/Utils.java b/app/src/main/java/com/alphawallet/app/util/Utils.java index 186ac955d9..1a40ca3448 100644 --- a/app/src/main/java/com/alphawallet/app/util/Utils.java +++ b/app/src/main/java/com/alphawallet/app/util/Utils.java @@ -96,7 +96,7 @@ public class Utils private static final String CHAIN_REPO_ADDRESS_TOKEN = "[CHAIN]"; private static final String TOKEN_LOGO = "/logo.png"; public static final String ALPHAWALLET_REPO_NAME = "https://raw.githubusercontent.com/alphawallet/iconassets/master/"; - private static final String TRUST_ICON_REPO_BASE = "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/"; + public static final String TRUST_ICON_REPO_BASE = "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/"; private static final String TRUST_ICON_REPO = TRUST_ICON_REPO_BASE + CHAIN_REPO_ADDRESS_TOKEN + "/assets/" + ICON_REPO_ADDRESS_TOKEN + TOKEN_LOGO; private static final String ALPHAWALLET_ICON_REPO = ALPHAWALLET_REPO_NAME + ICON_REPO_ADDRESS_TOKEN + TOKEN_LOGO; private static final String ATTESTATION_PREFIX = "#attestation="; diff --git a/app/src/main/java/com/alphawallet/app/util/ens/AWEnsResolver.java b/app/src/main/java/com/alphawallet/app/util/ens/AWEnsResolver.java index 26965a7da3..612e6354bc 100644 --- a/app/src/main/java/com/alphawallet/app/util/ens/AWEnsResolver.java +++ b/app/src/main/java/com/alphawallet/app/util/ens/AWEnsResolver.java @@ -7,6 +7,7 @@ import com.alphawallet.app.C; import com.alphawallet.app.entity.UnableToResolveENS; +import com.alphawallet.app.entity.nftassets.NFTAsset; import com.alphawallet.app.service.OpenSeaService; import com.alphawallet.app.util.Utils; import com.alphawallet.app.web3j.ens.EnsResolutionException; @@ -179,11 +180,11 @@ private String getEip155Url(String locator) String tokenId = matcher.group(8); String asset = new OpenSeaService().fetchAsset(chainId, tokenAddress, tokenId); - JSONObject assetObj = new JSONObject(asset); - String url = assetObj.getString(OPENSEA_IMAGE_PREVIEW); + NFTAsset nftAsset = new NFTAsset(asset); + String url = nftAsset.getThumbnail(); if (!TextUtils.isEmpty(url) && url.endsWith(".svg")) { - String original = assetObj.getString(OPENSEA_IMAGE_ORIGINAL); + String original = nftAsset.getImage(); if (!TextUtils.isEmpty(original)) url = original; } return url; diff --git a/app/src/main/java/com/alphawallet/app/viewmodel/TokenIconViewModel.java b/app/src/main/java/com/alphawallet/app/viewmodel/TokenIconViewModel.java new file mode 100644 index 0000000000..b4379063e5 --- /dev/null +++ b/app/src/main/java/com/alphawallet/app/viewmodel/TokenIconViewModel.java @@ -0,0 +1,171 @@ +package com.alphawallet.app.viewmodel; + +import static com.alphawallet.app.repository.TokensRealmSource.IMAGES_DB; +import static com.alphawallet.app.util.Utils.ALPHAWALLET_REPO_NAME; +import static com.alphawallet.app.util.Utils.isValidUrl; + +import android.text.TextUtils; + +import androidx.annotation.NonNull; + +import com.alphawallet.app.entity.tokens.Token; +import com.alphawallet.app.repository.entity.RealmAuxData; +import com.alphawallet.app.service.AssetDefinitionService; +import com.alphawallet.app.service.RealmManager; +import com.alphawallet.app.service.TokensService; +import com.alphawallet.app.util.Utils; + +import org.json.JSONObject; +import org.web3j.crypto.Keys; + +import java.util.Iterator; + +import javax.inject.Inject; + +import dagger.hilt.android.lifecycle.HiltViewModel; +import io.reactivex.Single; +import io.realm.Realm; +import timber.log.Timber; + +@HiltViewModel +public class TokenIconViewModel extends BaseViewModel +{ + private final RealmManager realmManager; + private final AssetDefinitionService assetDefinitionService; + private final TokensService tokensService; + + @Inject + public TokenIconViewModel( + RealmManager realmManager, + AssetDefinitionService assetDefinitionService, + TokensService tokensService) + { + this.realmManager = realmManager; + this.assetDefinitionService = assetDefinitionService; + this.tokensService = tokensService; + } + + public String getTokenName(Token token) + { + return token.getName(assetDefinitionService, token.getTokenCount()); + } + + public Single getIconFallback(final Token token, boolean useContractURI) + { + if (useContractURI) + { + //attempt to pull contract URI + return token.getContractURI() + .map(uri -> handleURIResult(token, uri)); + } + else //use TW as last resort + { + return Single.fromCallable(() -> getFallbackUrlForToken(token)); + } + } + + private String handleURIResult(Token token, String uriResult) + { + String imageUrl = imageFromMetadata(uriResult); + + if (TextUtils.isEmpty(imageUrl)) + { + imageUrl = token.getFirstImageUrl(); //check for first NFT image + + if (TextUtils.isEmpty(imageUrl)) + { + imageUrl = getFallbackUrlForToken(token); + } + } + + return Utils.parseIPFS(imageUrl); + } + + @NonNull + private String getFallbackUrlForToken(@NonNull Token token) + { + String correctedAddr = Keys.toChecksumAddress(token.getAddress()); + return Utils.getTWTokenImageUrl(token.tokenInfo.chainId, correctedAddr); + } + + //Only store if it's not the AW iconassets url + public void storeImageUrl(long chainId, String address, String imageUrl) + { + if (!TextUtils.isEmpty(imageUrl) && isValidUrl(imageUrl) && !imageUrl.startsWith(ALPHAWALLET_REPO_NAME) && TextUtils.isEmpty(getTokenImageUrl(chainId, address))) + { + tokensService.addTokenImageUrl(chainId, address, imageUrl); + } + } + + private String getTokenImageUrl(long networkId, String address) + { + String url = ""; + String instanceKey = address.toLowerCase() + "-" + networkId; + try (Realm realm = realmManager.getRealmInstance(IMAGES_DB)) + { + RealmAuxData instance = realm.where(RealmAuxData.class) + .equalTo("instanceKey", instanceKey) + .findFirst(); + + if (instance != null) + { + url = instance.getResult(); + } + } + catch (Exception ex) + { + Timber.e(ex); + } + + return url; + } + + public String getTokenIcon(Token token) + { + //return getPrimaryIconURL(token); + //see if there's a stored icon + String icon = getTokenImageUrl(token.tokenInfo.chainId, token.tokenInfo.address); + if (TextUtils.isEmpty(icon)) + { + //attempt usual fetch + icon = getPrimaryIconURL(token); + } + else + { + System.out.println("FROM DB: " + icon); + } + + return icon; + } + + private String getPrimaryIconURL(Token token) + { + String correctedAddr = Keys.toChecksumAddress(token.getAddress()); + return Utils.getTokenImageUrl(correctedAddr); + } + + private String imageFromMetadata(String metaData) + { + try + { + JSONObject jsonData = new JSONObject(metaData); + Iterator keys = jsonData.keys(); + while (keys.hasNext()) + { + String key = keys.next(); + String value = jsonData.getString(key); + + if (key.startsWith("image")) + { + return value; + } + } + } + catch (Exception e) + { + // + } + + return ""; + } +} diff --git a/app/src/main/java/com/alphawallet/app/widget/ActionSheetDialog.java b/app/src/main/java/com/alphawallet/app/widget/ActionSheetDialog.java index 0572b884f2..68f971897b 100644 --- a/app/src/main/java/com/alphawallet/app/widget/ActionSheetDialog.java +++ b/app/src/main/java/com/alphawallet/app/widget/ActionSheetDialog.java @@ -151,7 +151,7 @@ else if (activity instanceof WalletConnectActivity) transaction = new Transaction(tx, token.tokenInfo.chainId, ts.getCurrentAddress()); transaction.transactionInput = Transaction.decoder.decodeInput(candidateTransaction, token.tokenInfo.chainId, token.getWallet()); - balanceDisplay.setupBalance(token, tokensService, transaction); + balanceDisplay.setupBalance(token, transaction); networkDisplay.setNetwork(token.tokenInfo.chainId); gasWidgetInterface = setupGasWidget(); diff --git a/app/src/main/java/com/alphawallet/app/widget/BalanceDisplayWidget.java b/app/src/main/java/com/alphawallet/app/widget/BalanceDisplayWidget.java index a0720842db..bef9f9d083 100644 --- a/app/src/main/java/com/alphawallet/app/widget/BalanceDisplayWidget.java +++ b/app/src/main/java/com/alphawallet/app/widget/BalanceDisplayWidget.java @@ -37,12 +37,12 @@ public BalanceDisplayWidget(Context context, @Nullable AttributeSet attrs) tokenIcon = findViewById(R.id.token_icon); } - public void setupBalance(Token token, TokensService tokenService, Transaction tx) + public void setupBalance(Token token, Transaction tx) { if (token.isNonFungible()) { tokenIcon.setVisibility(View.VISIBLE); - tokenIcon.bindData(token, tokenService); + tokenIcon.bindData(token); } else { @@ -72,4 +72,4 @@ else if (transaction == null || transaction.transactionInput == null || transact String newBalanceVal = BalanceUtils.getScaledValueScientific(new BigDecimal(balanceAfterTransaction), token.tokenInfo.decimals); newBalance.setText(getContext().getString(R.string.new_balance, newBalanceVal, token.getSymbol())); } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/alphawallet/app/widget/EventDetailWidget.java b/app/src/main/java/com/alphawallet/app/widget/EventDetailWidget.java index ef50d2d7f3..72579e6ee1 100644 --- a/app/src/main/java/com/alphawallet/app/widget/EventDetailWidget.java +++ b/app/src/main/java/com/alphawallet/app/widget/EventDetailWidget.java @@ -47,7 +47,7 @@ public EventDetailWidget(Context context, AttributeSet attrs) public void setupView(RealmAuxData data, Token token, AssetDefinitionService svs, String eventAmount) { holdingView.setVisibility(View.VISIBLE); - icon.bindData(token, svs); + icon.bindData(token); title.setText(data.getTitle(getContext())); symbol.setText(token.getSymbol()); int resourceId; @@ -76,7 +76,7 @@ public void setupView(RealmAuxData data, Token token, AssetDefinitionService svs public void setupTransactionView(Transaction tx, Token token, AssetDefinitionService svs, String supplimentalInfo) { holdingView.setVisibility(View.VISIBLE); - icon.bindData(token, svs); + icon.bindData(token); symbol.setText(token.getSymbol()); title.setVisibility(View.GONE); if (supplimentalInfo.charAt(0) == '-') diff --git a/app/src/main/java/com/alphawallet/app/widget/InputAmount.java b/app/src/main/java/com/alphawallet/app/widget/InputAmount.java index 4d49ba8deb..cd04f42178 100644 --- a/app/src/main/java/com/alphawallet/app/widget/InputAmount.java +++ b/app/src/main/java/com/alphawallet/app/widget/InputAmount.java @@ -62,7 +62,6 @@ public class InputAmount extends LinearLayout private Realm realm; private Realm tickerRealm; private TokensService tokensService; - private AssetDefinitionService assetService; private BigInteger gasPriceEstimate = BigInteger.ZERO; private BigDecimal exactAmount = BigDecimal.ZERO; private final Handler handler = new Handler(Looper.getMainLooper()); @@ -105,14 +104,13 @@ public InputAmount(Context context, AttributeSet attrs) * @param assetDefinitionService * @param svs */ - public void setupToken(@NotNull Token token, @Nullable AssetDefinitionService assetDefinitionService, + public void setupToken(@NotNull Token token, @NotNull TokensService svs, @NotNull AmountReadyCallback amountCallback) { this.token = token; this.tokensService = svs; - this.assetService = assetDefinitionService; this.amountReadyCallback = amountCallback; - icon.bindData(token, assetService); + icon.bindData(token); header.getChainName().setChainID(token.tokenInfo.chainId); updateAvailableBalance(); @@ -315,7 +313,7 @@ private void startTickerListener() private void showCrypto() { - icon.bindData(token, assetService); + icon.bindData(token); symbolText.setText(token.getSymbol()); availableSymbol.setText(token.getSymbol()); availableAmount.setText(token.getStringBalanceForUI(5)); diff --git a/app/src/main/java/com/alphawallet/app/widget/TokenIcon.java b/app/src/main/java/com/alphawallet/app/widget/TokenIcon.java index 248c62fdc3..837cd8e926 100644 --- a/app/src/main/java/com/alphawallet/app/widget/TokenIcon.java +++ b/app/src/main/java/com/alphawallet/app/widget/TokenIcon.java @@ -1,9 +1,13 @@ package com.alphawallet.app.widget; import static androidx.core.content.ContextCompat.getColorStateList; +import static com.alphawallet.app.util.Utils.ALPHAWALLET_REPO_NAME; +import static com.alphawallet.app.util.Utils.TRUST_ICON_REPO_BASE; import static com.alphawallet.ethereum.EthereumNetworkBase.MAINNET_ID; +import android.app.Activity; import android.content.Context; +import android.content.ContextWrapper; import android.content.res.TypedArray; import android.graphics.ColorMatrix; import android.graphics.ColorMatrixColorFilter; @@ -12,7 +16,6 @@ import android.os.Looper; import android.text.TextUtils; import android.util.AttributeSet; -import android.util.Pair; import android.view.View; import android.widget.ImageView; import android.widget.ProgressBar; @@ -20,6 +23,8 @@ import androidx.annotation.Nullable; import androidx.constraintlayout.widget.ConstraintLayout; +import androidx.lifecycle.ViewModelProvider; +import androidx.lifecycle.ViewModelStoreOwner; import com.alphawallet.app.R; import com.alphawallet.app.entity.tokendata.TokenGroup; @@ -27,13 +32,11 @@ import com.alphawallet.app.repository.CurrencyRepository; import com.alphawallet.app.repository.EthereumNetworkBase; import com.alphawallet.app.repository.EthereumNetworkRepository; -import com.alphawallet.app.service.AssetDefinitionService; import com.alphawallet.app.service.TickerService; -import com.alphawallet.app.service.TokensService; import com.alphawallet.app.ui.widget.TokensAdapterCallback; -import com.alphawallet.app.ui.widget.entity.IconItem; import com.alphawallet.app.ui.widget.entity.StatusType; import com.alphawallet.app.util.Utils; +import com.alphawallet.app.viewmodel.TokenIconViewModel; import com.bumptech.glide.Glide; import com.bumptech.glide.load.DataSource; import com.bumptech.glide.load.engine.GlideException; @@ -44,7 +47,10 @@ import com.bumptech.glide.request.target.Target; import org.jetbrains.annotations.NotNull; -import org.web3j.crypto.Keys; + +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.Disposable; +import io.reactivex.schedulers.Schedulers; public class TokenIcon extends ConstraintLayout { @@ -61,12 +67,21 @@ public class TokenIcon extends ConstraintLayout private final boolean squareToken; private TokensAdapterCallback tokensAdapterCallback; private volatile Token token; - - private final RequestListener requestListenerTW = new RequestListener() + private final TokenIconViewModel viewModel; + @Nullable + private Disposable disposable; + private String tokenName; + private StatusType currentStatus; + private Request currentRq; + /** + * Prevent glide dumping log errors - it is expected that load will fail + */ + private final RequestListener requestListener = new RequestListener<>() { @Override public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) { + loadFromAltRepo(model); return false; } @@ -76,11 +91,7 @@ public boolean onResourceReady(Drawable resource, Object model, Target if (model == null) return false; if (token != null) { - IconItem.secondaryFound(token.tokenInfo.chainId, token.getAddress()); - } - if (token == null || !model.toString().toLowerCase().contains(token.getAddress())) - { - return false; + viewModel.storeImageUrl(token.tokenInfo.chainId, token.tokenInfo.address, model.toString()); } handler.post(() -> { @@ -92,37 +103,6 @@ public boolean onResourceReady(Drawable resource, Object model, Target return false; } }; - private String tokenName; - private StatusType currentStatus; - private String fallbackIconUrl; - private Request currentRq; - /** - * Prevent glide dumping log errors - it is expected that load will fail - */ - private final RequestListener requestListener = new RequestListener<>() - { - @Override - public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) - { - if (model != null && token != null && model.toString().toLowerCase().contains(token.getAddress())) - { - handler.post(() -> loadFromAltRepo()); - } - - return false; - } - - @Override - public boolean onResourceReady(Drawable resource, Object model, Target target, DataSource dataSource, boolean isFirstResource) - { - handler.post(() -> { - textIcon.setVisibility(View.GONE); - icon.setVisibility(View.VISIBLE); - icon.setImageDrawable(resource); - }); - return false; - } - }; public TokenIcon(Context context, AttributeSet attrs) { @@ -143,6 +123,7 @@ public TokenIcon(Context context, AttributeSet attrs) currentStatus = StatusType.NONE; chainIcon = findViewById(R.id.status_chain_icon); chainIconBackground = findViewById(R.id.chain_icon_background); + viewModel = new ViewModelProvider((ViewModelStoreOwner) getActivity()).get(TokenIconViewModel.class); bindViews(); } @@ -183,20 +164,26 @@ public void clearLoad() { currentRq.pause(); currentRq.clear(); + currentRq = null; handler.removeCallbacksAndMessages(null); token = null; } + + if (disposable != null && !disposable.isDisposed()) + { + disposable.dispose(); + } } /** * This method is necessary to call from the binder to show information correctly. * * @param token Token object - * @param assetDefinition Asset Definition Service for Icons */ - public void bindData(Token token, @NotNull AssetDefinitionService assetDefinition) + public void bindData(Token token) { this.token = token; + clearLoad(); if (token.isEthereum()) { @@ -210,48 +197,22 @@ public void bindData(Token token, @NotNull AssetDefinitionService assetDefinitio } else { - this.tokenName = token.getName(assetDefinition, token.getTokenCount()); - Pair iconFallback = assetDefinition.getFallbackUrlForToken(token); - String mainIcon = iconFallback.second ? iconFallback.first : getPrimaryIconURL(token); - this.fallbackIconUrl = iconFallback.second ? getPrimaryIconURL(token) : iconFallback.first; - bind(token, new IconItem(mainIcon, token.tokenInfo.chainId, token.getAddress())); + this.tokenName = viewModel.getTokenName(token); + String mainIcon = viewModel.getTokenIcon(token); + bind(token, mainIcon); } } - public void bindData(Token token) - { - if (token == null) return; - this.tokenName = token.getName(); - this.fallbackIconUrl = Utils.getTWTokenImageUrl(token.tokenInfo.chainId, token.getAddress()); - bind(token, getIconUrl(token)); - } - public void bindData(long chainId) { + clearLoad(); loadImageFromResource(EthereumNetworkRepository.getChainLogo(chainId)); } - public void bindData(Token token, @NotNull TokensService svs) - { - this.handler.removeCallbacks(null); - if (token == null) return; - this.tokenName = token.getName(); - this.fallbackIconUrl = svs.getFallbackUrlForToken(token); - - if (token.group == TokenGroup.SPAM) - { - bindSpam(token); - } - else - { - bind(token, getIconUrl(token)); - } - } - - private void bind(Token token, IconItem iconItem) + private void bind(Token token, String iconUrl) { bindCommon(token); - displayTokenIcon(iconItem); + displayTokenIcon(iconUrl); } private void bindSpam(Token token) @@ -300,42 +261,18 @@ private void setupDefaultIcon() /** * Try to fetch Token Icon from the Token URL. */ - private void displayTokenIcon(IconItem iconItem) + private void displayTokenIcon(String iconUrl) { setupDefaultIcon(); - if (token.isEthereum() - || token.getWallet().equalsIgnoreCase(token.getAddress()) - || iconItem.useTextSymbol()) return; - - if (iconItem.usePrimary()) - { - final RequestOptions optionalCircleCrop = squareToken || iconItem.getUrl().startsWith(Utils.ALPHAWALLET_REPO_NAME) ? new RequestOptions() : new RequestOptions().circleCrop(); + final RequestOptions optionalCircleCrop = squareToken || iconUrl.startsWith(ALPHAWALLET_REPO_NAME) ? new RequestOptions() : new RequestOptions().circleCrop(); - currentRq = Glide.with(this) - .load(iconItem.getUrl()) + currentRq = Glide.with(this) + .load(iconUrl) .placeholder(R.drawable.ic_token_eth) .apply(optionalCircleCrop) .listener(requestListener) .into(new DrawableImageViewTarget(icon)).getRequest(); - } - else - { - loadFromAltRepo(); - } - } - - private String getPrimaryIconURL(Token token) - { - String correctedAddr = Keys.toChecksumAddress(token.getAddress()); - return Utils.getTokenImageUrl(correctedAddr); - } - - private IconItem getIconUrl(Token token) - { - String correctedAddr = Keys.toChecksumAddress(token.getAddress()); - String tURL = Utils.getTokenImageUrl(correctedAddr); - return new IconItem(tURL, token.tokenInfo.chainId, token.getAddress()); } public void setStatusIcon(StatusType type) @@ -375,19 +312,53 @@ public void setStatusIcon(StatusType type) /** * Attempt to load the icon from the Database icon or TW icon repo */ - private void loadFromAltRepo() + private void loadFromAltRepo(Object model) { - if (!Utils.stillAvailable(getContext()) || this.fallbackIconUrl == null) return; + //check how to load from alt repo or ignore + String checkUrl = model != null ? model.toString() : null; + if (!Utils.stillAvailable(getContext()) || token == null || TextUtils.isEmpty(checkUrl)) + { + return; + } + + //check heuristic: + //1. If it's the AW iconassets repo then check using contract URI + //2. If it's not Trust repo URL then check using Trust repo + //3. If it is the Trust repo then stop + + boolean useContractURI; + + if (checkUrl.startsWith(ALPHAWALLET_REPO_NAME)) + { + useContractURI = true; + } + else if (!checkUrl.startsWith(TRUST_ICON_REPO_BASE)) + { + useContractURI = false; + } + else + { + return; //we checked Trust repo, this is the final check + } + + disposable = viewModel.getIconFallback(token, useContractURI) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(this::loadImageAlt); + } + private void loadImageAlt(String fileUri) + { final RequestOptions optionalCircleCrop = squareToken ? new RequestOptions() : new RequestOptions().circleCrop(); currentRq = Glide.with(this) - .load(this.fallbackIconUrl) - .apply(optionalCircleCrop) - .listener(requestListenerTW) - .into(new DrawableImageViewTarget(iconSecondary)).getRequest(); + .load(fileUri) + .apply(optionalCircleCrop) + .listener(requestListener) + .into(new DrawableImageViewTarget(iconSecondary)).getRequest(); } + /** * This method is used to set TextIcon and make Icon hidden as there is no icon available for the token. * @@ -500,4 +471,18 @@ public void setAttestationIcon(String image, String symbol, long chain) setIsAttestation(symbol, chain); } } + + private Activity getActivity() + { + Context context = getContext(); + while (context instanceof ContextWrapper) + { + if (context instanceof Activity) + { + return (Activity) context; + } + context = ((ContextWrapper) context).getBaseContext(); + } + return null; + } } diff --git a/app/src/main/java/com/alphawallet/app/widget/TokenInfoHeaderView.java b/app/src/main/java/com/alphawallet/app/widget/TokenInfoHeaderView.java index 27bd504211..d6b0d7a300 100644 --- a/app/src/main/java/com/alphawallet/app/widget/TokenInfoHeaderView.java +++ b/app/src/main/java/com/alphawallet/app/widget/TokenInfoHeaderView.java @@ -37,7 +37,7 @@ public TokenInfoHeaderView(Context context) public TokenInfoHeaderView(Context context, Token token, TokensService svs) { this(context); - icon.bindData(token, svs); + icon.bindData(token); if (!token.isEthereum()) icon.setChainIcon(token.tokenInfo.chainId); setAmount(token.getFixedFormattedBalance()); setSymbol(token.tokenInfo.symbol); diff --git a/lib/build.gradle b/lib/build.gradle index e31be6432c..3552d17468 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -1,8 +1,9 @@ plugins { - id 'com.android.library' + //id 'com.android.library' //apply plugin: 'com.github.johnrengelman.shadow' + id 'java-library' } -android { +/*android { namespace 'com.alphawallet' compileSdk 34 @@ -24,7 +25,7 @@ android { sourceCompatibility JavaVersion.VERSION_17 targetCompatibility JavaVersion.VERSION_17 } -} +}*/ dependencies { diff --git a/lib/src/main/java/com/alphawallet/attestation/Attestation.java b/lib/src/main/java/com/alphawallet/attestation/Attestation.java index d481da4103..242348dfd4 100644 --- a/lib/src/main/java/com/alphawallet/attestation/Attestation.java +++ b/lib/src/main/java/com/alphawallet/attestation/Attestation.java @@ -46,6 +46,14 @@ public Attestation() { public int getVersion() { return version.getValue().intValueExact(); + /*if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) + { + return version.getValue().intValueExact(); + } + else + { + return version.getValue().intValue(); + }*/ } public void setVersion(int version) { @@ -54,6 +62,14 @@ public void setVersion(int version) { public int getSerialNumber() { return serialNumber.getValue().intValueExact(); + /*if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) + { + return serialNumber.getValue().intValueExact(); + } + else + { + return serialNumber.getValue().intValue(); + }*/ } public void setSerialNumber(long serialNumber) { @@ -140,6 +156,14 @@ public List getSmartcontracts() { Iterator it = smartcontracts.iterator(); while(it.hasNext()) { res.add(((ASN1Integer) it.next()).getValue().longValueExact()); + /*if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) + { + res.add(((ASN1Integer) it.next()).getValue().longValueExact()); + } + else + { + res.add(((ASN1Integer) it.next()).getValue().longValue()); + }*/ } return res; } @@ -182,6 +206,19 @@ public boolean isValidX509() { if (version.getValue().intValueExact() != 0 && version.getValue().intValueExact() != 1 && version.getValue().intValueExact() != 2) { return false; } + /*if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) + { + if (version.getValue().intValueExact() != 0 && version.getValue().intValueExact() != 1 && version.getValue().intValueExact() != 2) { + return false; + } + } + else + { + if (version.getValue().intValue() != 0 && version.getValue().intValue() != 1 && version.getValue().intValue() != 2) { + return false; + } + }*/ + if (issuer == null || issuer.getRDNs().length == 0) { return false; } From c9c906a7629c8ff6b4e87839ca2fdb9cdf5df288 Mon Sep 17 00:00:00 2001 From: James Brown Date: Sun, 10 Mar 2024 18:51:00 +1100 Subject: [PATCH 05/11] Remove trust address test --- .../tools/TrustAddressGeneratorTest.java | 84 ------------------- 1 file changed, 84 deletions(-) delete mode 100644 lib/src/test/java/com/alphawallet/token/tools/TrustAddressGeneratorTest.java diff --git a/lib/src/test/java/com/alphawallet/token/tools/TrustAddressGeneratorTest.java b/lib/src/test/java/com/alphawallet/token/tools/TrustAddressGeneratorTest.java deleted file mode 100644 index ec33c178f9..0000000000 --- a/lib/src/test/java/com/alphawallet/token/tools/TrustAddressGeneratorTest.java +++ /dev/null @@ -1,84 +0,0 @@ -package com.alphawallet.token.tools; - -import org.junit.Test; -import org.web3j.utils.Numeric; -import org.xml.sax.SAXException; - -import javax.xml.crypto.MarshalException; -import javax.xml.crypto.dsig.XMLSignature; -import javax.xml.crypto.dsig.XMLSignatureException; -import javax.xml.parsers.ParserConfigurationException; -import java.io.*; -import org.bouncycastle.util.encoders.Base64; - -public class TrustAddressGeneratorTest { - String digest; - - public TrustAddressGeneratorTest() throws IOException, MarshalException, ParserConfigurationException, SAXException, XMLSignatureException { - InputStream input = new FileInputStream("src/test/ts/EntryToken.tsml"); - XMLDSigVerifier sigVerifier = new XMLDSigVerifier(); - XMLSignature signature = sigVerifier.getValidXMLSignature(input); - InputStream digest = signature.getSignedInfo().getCanonicalizedData(); - this.digest = convertHexToBase64String(Numeric.toHexString(getBytesFromInputStream(digest))); - } - - @Test - public void generateTrustAddress() throws Exception { - System.out.println("digest:" + digest); - String trustAddress = TrustAddressGenerator.getTrustAddress("0x63cCEF733a093E5Bd773b41C96D3eCE361464942", digest); - assert(trustAddress.equals("0x2e02934b4ed1bee0defa7a58061dd8ee9440094c")); - } - - @Test - public void generateRevokeAddress() throws Exception { - String revokeAddress = TrustAddressGenerator.getRevokeAddress("0x63cCEF733a093E5Bd773b41C96D3eCE361464942", digest); - assert(revokeAddress.equals("0x6b4c50938caef365fa3e04bfe5a25da518dba447")); - } - - /* - * the following utility functions are moved from - * TrustAddressGenerator because it doesn't belong - * there. TrustAddressGenerator generates addresses from a - * contract address and a digest. Itself shouldn't do the work of - * parsing XML and calculating the digest. - */ - - byte[] getBytesFromInputStream(InputStream is) throws IOException { - ByteArrayOutputStream os = new ByteArrayOutputStream(); - byte[] buffer = new byte[0xFFFF]; - for (int len = is.read(buffer); len != -1; len = is.read(buffer)) { - os.write(buffer, 0, len); - } - return os.toByteArray(); - } - - String convertHexToBase64String(String input) throws IOException { - byte barr[] = new byte[16]; - int bcnt = 0; - for (int i = 0; i < 32; i += 2) { - char c1 = input.charAt(i); - char c2 = input.charAt(i + 1); - int i1 = convertCharToInt(c1); - int i2 = convertCharToInt(c2); - barr[bcnt] = 0; - barr[bcnt] |= (byte) ((i1 & 0x0F) << 4); - barr[bcnt] |= (byte) (i2 & 0x0F); - bcnt++; - } - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - Base64.encode(barr, outputStream); - return outputStream.toString(); - } - - int convertCharToInt(char c) { - char[] carr = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; - char clower = Character.toLowerCase(c); - for (int i = 0; i < carr.length; i++) { - if (clower == carr[i]) { - return i; - } - } - return 0; - } - -} From 42bbcd3fbfb9ef8928e951ebb572ab7bccf591d1 Mon Sep 17 00:00:00 2001 From: James Brown Date: Sun, 10 Mar 2024 18:54:23 +1100 Subject: [PATCH 06/11] Remove file erase --- build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sh b/build.sh index 33f57f37c6..b6888a2bef 100644 --- a/build.sh +++ b/build.sh @@ -4,4 +4,4 @@ #cd dmz && ../gradlew -i build && ../gradlew -i test && cd .. cd lib && ../gradlew -i build && ../gradlew -i test && cd .. #cd util && ../gradlew -i build && ../gradlew -i test && cd .. -./gradlew clean jacocoTestNoAnalyticsDebugUnitTestReport -x lint -x detekt +#./gradlew clean jacocoTestNoAnalyticsDebugUnitTestReport -x lint -x detekt From b35c9a27fdc849f6f1f98c724d6171ef86ccf671 Mon Sep 17 00:00:00 2001 From: James Brown Date: Sun, 10 Mar 2024 22:09:36 +1100 Subject: [PATCH 07/11] Fix tests --- app/build.gradle | 2 +- app/src/main/AndroidManifest.xml | 4 - .../app/entity/HomeCommsInterface.java | 1 - .../alphawallet/app/entity/HomeReceiver.java | 3 - .../alphawallet/app/entity/SuggestEIP1559.kt | 14 +- .../app/interact/WalletConnectInteract.java | 4 - .../app/service/WalletConnectService.java | 514 ------ .../app/ui/DappBrowserFragment.java | 1 - .../com/alphawallet/app/ui/HomeActivity.java | 11 +- .../app/ui/QRScanning/QRScannerActivity.java | 16 +- .../com/alphawallet/app/ui/SendActivity.java | 10 - .../app/ui/WalletConnectActivity.java | 1565 ----------------- .../app/ui/WalletConnectSessionActivity.java | 14 +- .../alphawallet/app/ui/WalletsActivity.java | 1 - .../app/viewmodel/DappBrowserViewModel.java | 23 +- .../app/viewmodel/HomeViewModel.java | 78 +- .../app/viewmodel/WalletConnectViewModel.java | 1 - .../alphawallet/app/walletconnect/WCClient.kt | 274 +-- .../app/walletconnect/entity/WCUtils.java | 2 - .../app/widget/ActionSheetDialog.java | 12 - 20 files changed, 48 insertions(+), 2502 deletions(-) delete mode 100644 app/src/main/java/com/alphawallet/app/service/WalletConnectService.java delete mode 100644 app/src/main/java/com/alphawallet/app/ui/WalletConnectActivity.java diff --git a/app/build.gradle b/app/build.gradle index 2be7b1a8f2..a0118326c0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -24,7 +24,7 @@ tasks.withType(Test).configureEach { } detekt { - toolVersion = "1.20.0-RC1" + toolVersion = "1.23.5" buildUponDefaultConfig = true // preconfigure defaults allRules = false // activate all available (even unstable) rules. baseline = file("$projectDir/check/detekt-baseline.xml") diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 7802b1102f..63bd3fef89 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -123,10 +123,6 @@ android:name=".ui.SplashActivity" android:theme="@style/AppTheme.NoActionBar.Splash" /> - - + for (i in (feeHistory.gasUsedRatio.size - 1) downTo 0) { if (feeHistory.gasUsedRatio[i] > 0.9) { baseFee[i] = baseFee[i + 1] } } + /*((feeHistory.gasUsedRatio.size - 1) downTo 0).forEach { i -> + if (feeHistory.gasUsedRatio[i] > 0.9) { + baseFee[i] = baseFee[i + 1] + } + }*/ + val order = (0..feeHistory.gasUsedRatio.size).map { it }.sortedBy { baseFee[it] } var maxBaseFee = ZERO val result = mutableMapOf() - (maxTimeFactor downTo 0).forEach { timeFactor -> + for (timeFactor in maxTimeFactor downTo 0) { var bf: BigInteger if (timeFactor < 1e-6) { bf = baseFee.last() @@ -145,8 +151,8 @@ internal fun suggestPriorityFee(firstBlock: Long, feeHistory: FeeHistory, gasSer rewardPercentile.toString()).blockingGet(); val rewardSize = feeHistoryFetch?.reward?.size ?: 0 - (0 until rewardSize).forEach { - rewards.add(BigInteger(Numeric.cleanHexPrefix(feeHistoryFetch.reward[it][0].removePrefix("0x")), + for (index in 0 until rewardSize) { + rewards.add(BigInteger(Numeric.cleanHexPrefix(feeHistoryFetch.reward[index][0].removePrefix("0x")), 16)) } if (rewardSize < blockCount) break diff --git a/app/src/main/java/com/alphawallet/app/interact/WalletConnectInteract.java b/app/src/main/java/com/alphawallet/app/interact/WalletConnectInteract.java index 81a88f256c..21863c5fea 100644 --- a/app/src/main/java/com/alphawallet/app/interact/WalletConnectInteract.java +++ b/app/src/main/java/com/alphawallet/app/interact/WalletConnectInteract.java @@ -6,21 +6,17 @@ import android.os.IBinder; import com.alphawallet.app.entity.WalletConnectActions; -import com.alphawallet.app.entity.lifi.Token; import com.alphawallet.app.entity.walletconnect.WalletConnectSessionItem; import com.alphawallet.app.entity.walletconnect.WalletConnectV2SessionItem; import com.alphawallet.app.repository.entity.RealmWCSession; import com.alphawallet.app.service.RealmManager; -import com.alphawallet.app.service.WalletConnectService; import com.alphawallet.app.viewmodel.WalletConnectViewModel; import com.alphawallet.app.walletconnect.WCClient; import com.alphawallet.app.walletconnect.entity.WCUtils; import com.walletconnect.web3.wallet.client.Wallet; import com.walletconnect.web3.wallet.client.Web3Wallet; -import java.math.BigDecimal; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import javax.inject.Inject; diff --git a/app/src/main/java/com/alphawallet/app/service/WalletConnectService.java b/app/src/main/java/com/alphawallet/app/service/WalletConnectService.java deleted file mode 100644 index 910dc5f936..0000000000 --- a/app/src/main/java/com/alphawallet/app/service/WalletConnectService.java +++ /dev/null @@ -1,514 +0,0 @@ -package com.alphawallet.app.service; - -import static com.alphawallet.app.C.WALLET_CONNECT_ADD_CHAIN; -import static com.alphawallet.app.C.WALLET_CONNECT_CLIENT_TERMINATE; -import static com.alphawallet.app.C.WALLET_CONNECT_COUNT_CHANGE; -import static com.alphawallet.app.C.WALLET_CONNECT_FAIL; -import static com.alphawallet.app.C.WALLET_CONNECT_NEW_SESSION; -import static com.alphawallet.app.C.WALLET_CONNECT_REQUEST; -import static com.alphawallet.app.C.WALLET_CONNECT_SWITCH_CHAIN; - -import android.app.Service; -import android.content.Intent; -import android.os.Binder; -import android.os.IBinder; -import android.text.TextUtils; - -import androidx.annotation.Nullable; -import androidx.localbroadcastmanager.content.LocalBroadcastManager; - -import com.alphawallet.app.C; -import com.alphawallet.app.entity.WalletConnectActions; -import com.alphawallet.app.entity.walletconnect.SignType; -import com.alphawallet.app.entity.walletconnect.WCRequest; -import com.alphawallet.app.walletconnect.WCClient; -import com.alphawallet.app.web3.entity.WalletAddEthereumChainObject; - -import java.util.List; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.TimeUnit; - -import io.reactivex.Observable; -import io.reactivex.disposables.Disposable; -import io.reactivex.schedulers.Schedulers; -import kotlin.Unit; -import timber.log.Timber; - -/** - * The purpose of this service is to manage the currently active WalletConnect sessions. Keep the connections alive and terminate where required. - * Doing this in an activity means the connection objects are subject to activity lifecycle events (Destroy etc). - */ -public class WalletConnectService extends Service -{ - private final static ConcurrentHashMap clientMap = new ConcurrentHashMap<>(); - private final static ConcurrentLinkedQueue signRequests = new ConcurrentLinkedQueue<>(); - private final static ConcurrentHashMap clientTimes = new ConcurrentHashMap<>(); - private WCRequest currentRequest = null; - - private static final String TAG = "WCClientSvs"; - - @Nullable - private Disposable messagePump; - - @Override - public int onStartCommand(Intent intent, int flags, int startId) - { - Timber.tag(TAG).d("SERVICE STARTING"); - - if (intent == null) - { - return Service.START_STICKY; - } - - try - { - int actionVal = Integer.parseInt(intent.getAction()); - WalletConnectActions action = WalletConnectActions.values()[actionVal]; - - switch (action) - { - case CONNECT: - Timber.tag(TAG).d("SERVICE CONNECT"); - break; - case APPROVE: - approveRequest(intent); - break; - case REJECT: - rejectRequest(intent); - break; - case DISCONNECT: - Timber.tag(TAG).d("SERVICE DISCONNECT"); - //kill any active connection - disconnectCurrentSessions(); - break; - case CLOSE: - Timber.tag(TAG).d("SERVICE CLOSE"); - //result.getData().getIntExtra(C.EXTRA_CHAIN_ID, -1); - String sessionId = intent.getStringExtra("session"); - disconnectSession(sessionId); - break; - case MSG_PUMP: - Timber.tag(TAG).d("SERVICE MSG PUMP"); - checkMessages(); - break; - case SWITCH_CHAIN: - Timber.tag(TAG).d("SERVICE SWITCH CHAIN"); - switchChain(intent); - break; - case ADD_CHAIN: - Timber.tag(TAG).d("SERVICE ADD CHAIN"); - addChain(intent); - break; - } - } - catch (Exception e) - { - Timber.e(e); - } - return START_STICKY; - } - - private final IBinder mBinder = new LocalBinder(); - - public WCRequest getPendingRequest(String sessionId) - { - WCRequest request = signRequests.poll(); - if (request != null && !request.sessionId.equals(sessionId)) - { - signRequests.add(request); // not for this client, put it back on the stack, at the back - request = null; - } - else if (request != null) - { - currentRequest = request; - } - - return request; - } - - public int getConnectionCount() - { - return clientMap.size(); - } - - public WCRequest getCurrentRequest() - { - return currentRequest; - } - - private void disconnectCurrentSessions() - { - for (WCClient client : clientMap.values()) - { - if (client.isConnected()) - { - client.killSession(); - } - } - } - - private void disconnectSession(String sessionId) - { - if (TextUtils.isEmpty(sessionId)) - { - disconnectCurrentSessions(); - } - else - { - WCClient client = clientMap.get(sessionId); - if (client != null) client.killSession(); - } - } - - //executed a pending request, remove from the queue - public void removePendingRequest(long id) - { - for (WCRequest rq : signRequests) - { - if (rq.id == id) - { - signRequests.remove(rq); - break; - } - } - } - - public class LocalBinder extends Binder - { - public WalletConnectService getService() - { - return WalletConnectService.this; - } - } - - @Nullable - private Disposable pingTimer; - - @Nullable - @Override - public IBinder onBind(Intent intent) - { - return mBinder; - } - - public WCClient getClient(String sessionId) - { - if (sessionId == null) - { - return null; - } - else - { - return clientMap.get(sessionId); - } - } - - public void putClient(String sessionId, WCClient client) - { - Timber.tag(TAG).d("Add session: %s", sessionId); - clientMap.put(sessionId, client); - broadcastConnectionCount(clientMap.size()); - clientTimes.put(sessionId, System.currentTimeMillis()); - setupClient(client); - startSessionPinger(); - } - - public void addClients(List clientList) - { - for (WCClient client : clientList) - { - String sessionId = client.sessionId(); - if (sessionId != null && clientMap.get(sessionId) == null) - { - Timber.d("WC: Add client: %s", sessionId); - putClient(sessionId, client); - } - } - } - - private void rejectRequest(Intent intent) - { - String sessionId = intent.getStringExtra("sessionId"); - long id = intent.getLongExtra("id", 0L); - String message = intent.getStringExtra("message"); - - WCClient c = clientMap.get(sessionId); - if (c != null && c.isConnected()) - { - c.rejectRequest(id, message); - } - } - - private void approveRequest(Intent intent) - { - String sessionId = intent.getStringExtra("sessionId"); - long id = intent.getLongExtra("id", 0L); - String message = intent.getStringExtra("message"); - - WCClient c = clientMap.get(sessionId); - - if (c != null && c.isConnected()) - { - c.approveRequest(id, message); - } - } - - private void setupClient(WCClient client) - { - client.setOnSessionRequest((id, peer) -> { - if (client.sessionId() == null) return Unit.INSTANCE; - setLastUsed(client); - signRequests.add(new WCRequest(client.sessionId(), id, peer, client.chainIdVal())); - broadcastSessionEvent(WALLET_CONNECT_NEW_SESSION, client.sessionId()); - Timber.tag(TAG).d("On Request: %s", peer.getName()); - return Unit.INSTANCE; - }); - - client.setOnFailure(throwable -> { - //alert UI - if (client.sessionId() == null) return Unit.INSTANCE; - Timber.tag(TAG).d("On Fail: %s", throwable.getMessage()); - //only add if no errors already in queue - if (queueHasNoErrors()) - { - signRequests.add(new WCRequest(client.sessionId(), throwable, client.chainIdVal())); - broadcastSessionEvent(WALLET_CONNECT_FAIL, client.sessionId()); - } - startMessagePump(); - return Unit.INSTANCE; - }); - - client.setOnDisconnect((code, reason) -> { - Timber.tag(TAG).d("Terminate session?"); - terminateClient(client.sessionId()); - client.resetState(); - return Unit.INSTANCE; - }); - - client.setOnEthSign((id, message) -> { - if (client.sessionId() == null) return Unit.INSTANCE; - setLastUsed(client); - WCRequest rq = new WCRequest(client.sessionId(), id, message); - Timber.tag(TAG).d("Sign Request: %s", message.toString()); - sendRequest(client, rq); - return Unit.INSTANCE; - }); - - client.setOnEthSignTransaction((id, transaction) -> { - if (client.sessionId() == null) return Unit.INSTANCE; - setLastUsed(client); - WCRequest rq = new WCRequest(client.sessionId(), id, transaction, true, client.chainIdVal()); - sendRequest(client, rq); - return Unit.INSTANCE; - }); - - client.setOnEthSendTransaction((id, transaction) -> { - if (client.sessionId() == null) return Unit.INSTANCE; - setLastUsed(client); - WCRequest rq = new WCRequest(client.sessionId(), id, transaction, false, client.chainIdVal()); - sendRequest(client, rq); - return Unit.INSTANCE; - }); - - client.setOnSwitchEthereumChain((requestId, chainId) -> { - Timber.tag(TAG).d("onSwitchEthereumChain: request.id: %s, sessionId: %s, chainId: %s", requestId, client.sessionId(), chainId); - // send broadcast to show dialog for switching chain - Intent i = new Intent(WALLET_CONNECT_SWITCH_CHAIN); - i.putExtra(C.EXTRA_WC_REQUEST_ID, requestId); - i.putExtra(C.EXTRA_SESSION_ID, client.sessionId()); - i.putExtra(C.EXTRA_CHAIN_ID, chainId); - i.putExtra(C.EXTRA_NAME, client.getPeerMeta().getName()); - sendWalletConnectBroadcast(i); - return Unit.INSTANCE; - }); - - client.setOnAddEthereumChain((requestId, chainObj) -> { - Timber.tag(TAG).d("onAddEthereumChain: requestId: %s, chainObj: %s", requestId, chainObj); - Intent i = new Intent(WALLET_CONNECT_ADD_CHAIN); - i.putExtra(C.EXTRA_WC_REQUEST_ID, requestId); - i.putExtra(C.EXTRA_SESSION_ID, client.sessionId()); - i.putExtra(C.EXTRA_CHAIN_OBJ, chainObj); - sendWalletConnectBroadcast(i); - return Unit.INSTANCE; - }); - } - - private void sendWalletConnectBroadcast(Intent i) - { - LocalBroadcastManager.getInstance(this).sendBroadcast(i); - } - - //TODO: Can we determine if AlphaWallet is running? If it is, no need to add this to the queue, - //TODO: as user will get the intent in walletConnectActionReceiver (repeat for below) - private void sendRequest(WCClient client, WCRequest rq) - { - Timber.d("sendRequest: sessionId: %s", client.sessionId()); - signRequests.add(rq); - //see if this connection is live, if so then bring WC request to foreground - switchToWalletConnectApprove(client.sessionId(), rq); - startMessagePump(); - } - - private void broadcastSessionEvent(String command, String sessionId) - { - Timber.d("broadcastSessionEvent: sessionId: %s, command: %s", sessionId, command); - Intent intent = new Intent(command); - intent.putExtra("sessionid", sessionId); - intent.putExtra("wcrequest", getPendingRequest(sessionId)); // pass WCRequest as parcelable in the intent - sendWalletConnectBroadcast(intent); - } - - private void broadcastConnectionCount(int count) - { - Intent intent = new Intent(WALLET_CONNECT_COUNT_CHANGE); - intent.putExtra("count", count); - sendWalletConnectBroadcast(intent); - } - - private void switchToWalletConnectApprove(String sessionId, WCRequest rq) - { - WCClient cc = clientMap.get(sessionId); - - if (cc != null) - { - Intent intent = new Intent(WALLET_CONNECT_REQUEST); - intent.putExtra("sessionid", sessionId); - intent.putExtra("wcrequest", rq); - sendWalletConnectBroadcast(intent); - - Timber.tag(TAG).d("Connected clients: %s", clientMap.size()); - } - } - - private void startSessionPinger() - { - if (pingTimer == null || pingTimer.isDisposed()) - { - pingTimer = Observable.interval(0, 30, TimeUnit.SECONDS) - .doOnNext(l -> ping()).subscribe(); - } - } - - private void ping() - { - for (String sessionKey : clientMap.keySet()) - { - WCClient c = clientMap.get(sessionKey); - if (c == null) return; - if (c.isConnected() && c.chainIdVal() != 0 && c.getAccounts() != null) - { - Timber.tag(TAG).d("Ping Key: %s", sessionKey); - c.updateSession(c.getAccounts(), c.chainIdVal(), true); - } - } - } - - public void terminateClient(String sessionKey) - { - broadcastSessionEvent(WALLET_CONNECT_CLIENT_TERMINATE, sessionKey); - clientMap.remove(sessionKey); - broadcastConnectionCount(clientMap.size()); - if (clientMap.size() == 0 && pingTimer != null && !pingTimer.isDisposed()) - { - Timber.tag(TAG).d("Stop timer & service"); - pingTimer.dispose(); - pingTimer = null; - stopSelf(); - } - } - - private void setLastUsed(WCClient c) - { - String sessionId = c.sessionId(); - if (sessionId != null) clientTimes.put(sessionId, System.currentTimeMillis()); - } - - private void startMessagePump() - { - if (messagePump != null && !messagePump.isDisposed()) messagePump.dispose(); - - messagePump = Observable.interval(2000, 2000, TimeUnit.MILLISECONDS) - .doOnNext(l -> checkMessages()) - .observeOn(Schedulers.newThread()).subscribe(); - } - - private void checkMessages() - { - WCRequest rq = signRequests.peek(); - if (rq != null) - { - WCClient cc = clientMap.get(rq.sessionId); - if (cc != null) - { - switchToWalletConnectApprove(rq.sessionId, rq); - } - } - else if (messagePump != null && !messagePump.isDisposed()) - { - messagePump.dispose(); - } - } - - private boolean queueHasNoErrors() - { - for (WCRequest rq : signRequests.toArray(new WCRequest[0])) - { - if (rq.type == SignType.FAILURE) return false; - } - - return true; - } - - private void switchChain(Intent intent) - { - long requestId = intent.getLongExtra(C.EXTRA_WC_REQUEST_ID, -1); - String id = intent.getStringExtra(C.EXTRA_SESSION_ID); - long chainId = intent.getLongExtra(C.EXTRA_CHAIN_ID, -1); - boolean approved = intent.getBooleanExtra(C.EXTRA_APPROVED, false); - boolean chainAvailable = intent.getBooleanExtra(C.EXTRA_CHAIN_AVAILABLE, true); - Timber.tag(TAG).d("sessionId: %s, chainId: %s, approved: %s", id, chainId, approved); - if (requestId != -1 && id != null && chainId != -1) - { - WCClient c = clientMap.get(id); - if (c != null) - { - c.switchChain(requestId, chainId, approved, chainAvailable); - } - else - { - Timber.tag(TAG).d("WCClient not found"); - } - } - } - - public void addChain(Intent intent) - { - long requestId = intent.getLongExtra(C.EXTRA_WC_REQUEST_ID, -1); - String id = intent.getStringExtra(C.EXTRA_SESSION_ID); - WalletAddEthereumChainObject chainObject = intent.getParcelableExtra(C.EXTRA_CHAIN_OBJ); - boolean chainAdded = intent.getBooleanExtra(C.EXTRA_APPROVED, false); - Timber.tag(TAG).d("sessionId: %s, chainObj: %s, chainAdded: %s", id, chainObject, chainAdded); - - if (requestId != -1) - { - WCClient c = clientMap.get(id); - if (c != null) - { - if (chainObject != null) - { - c.addChain(requestId, chainObject, chainAdded); - } - else - { - // reject with serverError - c.addChain(requestId, chainObject, false); - } - } - else - { - Timber.tag(TAG).d("WCClient not found"); - } - } - } -} diff --git a/app/src/main/java/com/alphawallet/app/ui/DappBrowserFragment.java b/app/src/main/java/com/alphawallet/app/ui/DappBrowserFragment.java index 080b4eb2eb..212b8f7f26 100644 --- a/app/src/main/java/com/alphawallet/app/ui/DappBrowserFragment.java +++ b/app/src/main/java/com/alphawallet/app/ui/DappBrowserFragment.java @@ -83,7 +83,6 @@ import com.alphawallet.app.repository.TokensRealmSource; import com.alphawallet.app.repository.entity.RealmToken; import com.alphawallet.app.service.GasService; -import com.alphawallet.app.service.WalletConnectService; import com.alphawallet.app.ui.QRScanning.QRScannerActivity; import com.alphawallet.app.ui.widget.OnDappHomeNavClickListener; import com.alphawallet.app.ui.widget.entity.ActionSheetCallback; diff --git a/app/src/main/java/com/alphawallet/app/ui/HomeActivity.java b/app/src/main/java/com/alphawallet/app/ui/HomeActivity.java index 6fa50fd33d..d4d65d1916 100644 --- a/app/src/main/java/com/alphawallet/app/ui/HomeActivity.java +++ b/app/src/main/java/com/alphawallet/app/ui/HomeActivity.java @@ -570,7 +570,7 @@ protected void onResume() { super.onResume(); setWCConnect(); - viewModel.prepare(this); + viewModel.prepare(); viewModel.getWalletName(this); viewModel.setErrorCallback(this); if (homeReceiver == null) @@ -913,15 +913,6 @@ public void resetTransactions() getFragment(ACTIVITY).resetTransactions(); } - @Override - public void openWalletConnect(String sessionId) - { - Intent intent = new Intent(getApplication(), WalletConnectActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); - intent.putExtra("session", sessionId); - startActivity(intent); - } - private void hideDialog() { if (dialog != null && dialog.isShowing()) diff --git a/app/src/main/java/com/alphawallet/app/ui/QRScanning/QRScannerActivity.java b/app/src/main/java/com/alphawallet/app/ui/QRScanning/QRScannerActivity.java index 3945e9f5bc..ad1ef3d414 100644 --- a/app/src/main/java/com/alphawallet/app/ui/QRScanning/QRScannerActivity.java +++ b/app/src/main/java/com/alphawallet/app/ui/QRScanning/QRScannerActivity.java @@ -26,7 +26,6 @@ import com.alphawallet.app.entity.analytics.QrScanResultType; import com.alphawallet.app.entity.analytics.QrScanSource; import com.alphawallet.app.ui.BaseActivity; -import com.alphawallet.app.ui.WalletConnectActivity; import com.alphawallet.app.ui.WalletConnectV2Activity; import com.alphawallet.app.viewmodel.QrScannerViewModel; import com.alphawallet.app.walletconnect.util.WalletConnectHelper; @@ -189,18 +188,8 @@ private void displayErrorDialog(String title, String errorMessage) private void startWalletConnect(String qrCode) { - Intent intent; - if (WalletConnectHelper.isWalletConnectV1(qrCode)) - { - intent = new Intent(this, WalletConnectActivity.class); - intent.putExtra("qrCode", qrCode); - intent.putExtra(C.EXTRA_CHAIN_ID, chainIdOverride); - } - else - { - intent = new Intent(this, WalletConnectV2Activity.class); - intent.putExtra("url", qrCode); - } + Intent intent = new Intent(this, WalletConnectV2Activity.class); + intent.putExtra("url", qrCode); startActivity(intent); setResult(WALLET_CONNECT); finish(); @@ -209,6 +198,7 @@ private void startWalletConnect(String qrCode) @Override public void onBackPressed() { + super.onBackPressed(); viewModel.track(Analytics.Action.SCAN_QR_CODE_CANCELLED); Intent intent = new Intent(); setResult(Activity.RESULT_CANCELED, intent); diff --git a/app/src/main/java/com/alphawallet/app/ui/SendActivity.java b/app/src/main/java/com/alphawallet/app/ui/SendActivity.java index 6441f2da80..181b1d7c98 100644 --- a/app/src/main/java/com/alphawallet/app/ui/SendActivity.java +++ b/app/src/main/java/com/alphawallet/app/ui/SendActivity.java @@ -289,16 +289,6 @@ else if (requestCode == C.BARCODE_READER_REQUEST_CODE) } } - private void startWalletConnect(String qrCode) - { - Intent intent = new Intent(this, WalletConnectActivity.class); - intent.putExtra("qrCode", qrCode); - intent.putExtra(C.EXTRA_CHAIN_ID, token.tokenInfo.chainId); - startActivity(intent); - setResult(RESULT_OK); - finish(); - } - private void showCameraDenied() { if (dialog != null && dialog.isShowing()) dialog.dismiss(); diff --git a/app/src/main/java/com/alphawallet/app/ui/WalletConnectActivity.java b/app/src/main/java/com/alphawallet/app/ui/WalletConnectActivity.java deleted file mode 100644 index 288e257240..0000000000 --- a/app/src/main/java/com/alphawallet/app/ui/WalletConnectActivity.java +++ /dev/null @@ -1,1565 +0,0 @@ -package com.alphawallet.app.ui; - -import static com.alphawallet.ethereum.EthereumNetworkBase.MAINNET_ID; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.net.Uri; -import android.os.Bundle; -import android.os.Handler; -import android.os.Looper; -import android.text.TextUtils; -import android.text.format.DateUtils; -import android.view.MenuItem; -import android.view.View; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.ProgressBar; -import android.widget.TextView; -import android.widget.Toast; - -import androidx.activity.result.ActivityResultLauncher; -import androidx.activity.result.contract.ActivityResultContracts; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.lifecycle.Lifecycle; -import androidx.lifecycle.ViewModelProvider; -import androidx.localbroadcastmanager.content.LocalBroadcastManager; - -import com.alphawallet.app.C; -import com.alphawallet.app.R; -import com.alphawallet.app.analytics.Analytics; -import com.alphawallet.app.entity.ActionSheetStatus; -import com.alphawallet.app.entity.AnalyticsProperties; -import com.alphawallet.app.entity.CryptoFunctions; -import com.alphawallet.app.entity.NetworkInfo; -import com.alphawallet.app.entity.SignAuthenticationCallback; -import com.alphawallet.app.entity.StandardFunctionInterface; -import com.alphawallet.app.entity.TransactionReturn; -import com.alphawallet.app.entity.Wallet; -import com.alphawallet.app.entity.WalletType; -import com.alphawallet.app.entity.analytics.ActionSheetSource; -import com.alphawallet.app.entity.tokens.Token; -import com.alphawallet.app.entity.walletconnect.SignType; -import com.alphawallet.app.entity.walletconnect.WCRequest; -import com.alphawallet.app.repository.EthereumNetworkBase; -import com.alphawallet.app.repository.SignRecord; -import com.alphawallet.app.service.GasService; -import com.alphawallet.app.ui.widget.entity.ActionSheetCallback; -import com.alphawallet.app.viewmodel.WalletConnectViewModel; -import com.alphawallet.app.walletconnect.AWWalletConnectClient; -import com.alphawallet.app.walletconnect.WCClient; -import com.alphawallet.app.walletconnect.WCSession; -import com.alphawallet.app.walletconnect.entity.WCEthereumSignMessage; -import com.alphawallet.app.walletconnect.entity.WCEthereumTransaction; -import com.alphawallet.app.walletconnect.entity.WCPeerMeta; -import com.alphawallet.app.walletconnect.entity.WCUtils; -import com.alphawallet.app.walletconnect.entity.WalletConnectCallback; -import com.alphawallet.app.web3.entity.Address; -import com.alphawallet.app.web3.entity.WalletAddEthereumChainObject; -import com.alphawallet.app.web3.entity.Web3Transaction; -import com.alphawallet.app.widget.AWalletAlertDialog; -import com.alphawallet.app.widget.ActionSheet; -import com.alphawallet.app.widget.ActionSheetDialog; -import com.alphawallet.app.widget.ActionSheetSignDialog; -import com.alphawallet.app.widget.ChainName; -import com.alphawallet.app.widget.FunctionButtonBar; -import com.alphawallet.app.widget.SignTransactionDialog; -import com.alphawallet.app.widget.TokenIcon; -import com.alphawallet.hardware.SignatureFromKey; -import com.alphawallet.token.entity.EthereumMessage; -import com.alphawallet.token.entity.EthereumTypedMessage; -import com.alphawallet.token.entity.SignMessageType; -import com.alphawallet.token.entity.Signable; -import com.bumptech.glide.Glide; -import com.google.gson.Gson; - -import org.jetbrains.annotations.NotNull; -import org.web3j.utils.Numeric; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.UUID; - -import javax.inject.Inject; - -import dagger.hilt.android.AndroidEntryPoint; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.schedulers.Schedulers; -import kotlin.Unit; -import timber.log.Timber; - -@AndroidEntryPoint -public class WalletConnectActivity extends BaseActivity implements ActionSheetCallback, StandardFunctionInterface, WalletConnectCallback -{ - public static final String WC_LOCAL_PREFIX = "wclocal:"; - public static final String WC_INTENT = "wcintent:"; - private static final String TAG = "WCClient"; - private static final String DEFAULT_ICON = "https://example.walletconnect.org/favicon.ico"; - private static final long CONNECT_TIMEOUT = 10 * DateUtils.SECOND_IN_MILLIS; // 10 Seconds timeout - private final Handler handler = new Handler(Looper.getMainLooper()); - private final long switchChainDialogCallbackId = 1; - private WalletConnectViewModel viewModel; - private LocalBroadcastManager broadcastManager; - private WCClient client; - private WCSession session; - private WCPeerMeta peerMeta; - private WCPeerMeta remotePeerMeta; - private ActionSheet confirmationDialog; - ActivityResultLauncher getGasSettings = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), - result -> confirmationDialog.setCurrentGasIndex(result)); - private AddEthereumChainPrompt addEthereumChainPrompt; - // data for switch chain request - private long switchChainRequestId; // rpc request id - private long switchChainId; // new chain to switch to - private String name; // remote peer name - private String currentSessionId; // sessionId for which chain is switched - private boolean chainAvailable; // flag denoting chain available in AW or not - private ImageView icon; - private TextView peerName; - private TextView peerUrl; - private TextView statusText; - private TextView textName; - private TextView txCount; - private ChainName chainName; - private TokenIcon chainIcon; - private ProgressBar progressBar; - private LinearLayout infoLayout; - private LinearLayout txCountLayout; - private FunctionButtonBar functionBar; - private boolean fromDappBrowser = false; //if using this from dappBrowser (which is a bit strange but could happen) then return back to browser once signed - private boolean fromPhoneBrowser = false; //if from phone browser, clicking 'back' should take user back to dapp running on the phone's browser, - // -- according to WalletConnect's expected UX docs. - private boolean fromSessionActivity = false; - private String qrCode; - private SignAuthenticationCallback signCallback; - private long lastId; - private String signData; - private WCEthereumSignMessage.WCSignType signType; - private long chainIdOverride; - - @Inject - AWWalletConnectClient awWalletConnectClient; - - ActivityResultLauncher getNetwork = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), - result -> { - if (result.getData() == null) return; - chainIdOverride = result.getData().getLongExtra(C.EXTRA_CHAIN_ID, MAINNET_ID); - confirmationDialog.updateChain(chainIdOverride); - }); - private boolean waitForWalletConnectSession = false; - private long requestId = 0; - private AWalletAlertDialog dialog = null; - private final BroadcastReceiver walletConnectActionReceiver = new BroadcastReceiver() - { - @Override - public void onReceive(Context context, Intent intent) - { - Timber.tag(TAG).d("Received message"); - String action = intent.getAction(); - switch (action) - { - case C.WALLET_CONNECT_REQUEST: - case C.WALLET_CONNECT_NEW_SESSION: - case C.WALLET_CONNECT_FAIL: - Timber.tag(TAG).d("MSG: %s", action); -// getPendingRequest(); - WCRequest wcRequest = (WCRequest) intent.getParcelableExtra("wcrequest"); - if (wcRequest != null) - { - executedPendingRequest(wcRequest.id); - receiveRequest(wcRequest); - } - else - { - // something went wrong - } - break; - case C.WALLET_CONNECT_CLIENT_TERMINATE: - String sessionId = intent.getStringExtra("sessionid"); - Timber.tag(TAG).d("MSG: TERMINATE: %s", sessionId); - if (viewModel != null) - { - viewModel.endSession(sessionId); - } - if (getSessionId() != null && getSessionId().equals(sessionId)) - { - setupClient(sessionId); - finish(); - } - break; - case C.WALLET_CONNECT_SWITCH_CHAIN: - Timber.tag(TAG).d("MSG: SWITCH CHAIN: "); - onSwitchChainRequest(intent); - break; - case C.WALLET_CONNECT_ADD_CHAIN: - Timber.tag(TAG).d("MSG: ADD CHAIN"); - onAddChainRequest(intent); - break; - } - } - }; - - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) - { - super.onCreate(savedInstanceState); - - setContentView(R.layout.activity_wallet_connect); - - toolbar(); - - setTitle(getString(R.string.title_wallet_connect)); - - initViews(); - - initViewModel(); - - Timber.tag(TAG).d("Starting Activity: %s", getSessionId()); - - retrieveQrCode(); - viewModel.prepare(); - - if (savedInstanceState != null) restoreState(savedInstanceState); - } - - private void restoreState(Bundle savedInstance) - { - //Orientation change? - - if (savedInstance.containsKey("ORIENTATION") && savedInstance.containsKey("SESSIONID")) - { - int oldOrientation = savedInstance.getInt("ORIENTATION"); - int newOrientation = getResources().getConfiguration().orientation; - - if (oldOrientation != newOrientation) - { - requestId = savedInstance.getLong("SESSIONID"); - String sessionId = savedInstance.getString("SESSIONIDSTR"); - session = viewModel.getSession(sessionId); - - if (savedInstance.containsKey("TRANSACTION")) - { - Web3Transaction w3Tx = savedInstance.getParcelable("TRANSACTION"); - chainIdOverride = savedInstance.getLong("CHAINID"); - - //kick off transaction - final ActionSheetDialog confDialog = generateTransactionRequest(w3Tx, chainIdOverride); - if (confDialog != null) - { - confirmationDialog = confDialog; - confirmationDialog.show(); - } - } - else if (savedInstance.containsKey("SIGNDATA")) - { - signData = savedInstance.getString("SIGNDATA"); - signType = WCEthereumSignMessage.WCSignType.values()[savedInstance.getInt("SIGNTYPE")]; - lastId = savedInstance.getLong("LASTID"); - String peerUrl = savedInstance.getString("PEERURL"); - Signable signable = null; - - //kick off sign - switch (signType) - { - case MESSAGE: - case PERSONAL_MESSAGE: - signable = new EthereumMessage(signData, peerUrl, lastId, SignMessageType.SIGN_PERSONAL_MESSAGE); - break; - case TYPED_MESSAGE: - signable = new EthereumTypedMessage(signData, peerUrl, lastId, new CryptoFunctions()); - break; - } - - doSignMessage(signable); - } - } - } - } - - @Override - protected void onNewIntent(Intent intent) - { - super.onNewIntent(intent); - Bundle data = intent.getExtras(); - chainIdOverride = 0; - if (data != null) - { - //detect new intent from service - String sessionIdFromService = data.getString("session"); - if (sessionIdFromService != null) - { - handleStartFromWCMessage(sessionIdFromService); - } - else - { - handleStartDirectFromQRScan(); - } - } - } - - private void onServiceReady(Boolean b) - { - if (waitForWalletConnectSession) - { - waitForWalletConnectSession = false; - handleStartDirectFromQRScan(); - } - } - - private void handleStartDirectFromQRScan() - { - String sessionId = getSessionId(); - retrieveQrCode(); - String newSessionId = getSessionId(); - Timber.tag(TAG).d("Received New Intent: %s (%s)", newSessionId, sessionId); - viewModel.getClient(this, newSessionId, client -> { - if (client == null || !client.isConnected()) - { - //TODO: ERROR! - showErrorDialogTerminate(getString(R.string.session_terminated)); - infoLayout.setVisibility(View.GONE); - } - else - { - //setup the screen - Timber.tag(TAG).d("Resume Connection session: %s", newSessionId); - setClient(client); - } - }); - } - - private void handleStartFromWCMessage(String sessionIdFromService) - { - //is different from current sessionId? - if (!sessionIdFromService.equals(getSessionId())) - { - //restore different session - session = viewModel.getSession(sessionIdFromService); - viewModel.getClient(this, sessionIdFromService, this::setClient); - qrCode = null; - fromDappBrowser = false; - } - else - { - //init UI - displaySessionStatus(sessionIdFromService); - } - } - - private void setClient(WCClient receivedClient) - { - client = receivedClient; - displaySessionStatus(client.sessionId()); - } - - private void retrieveQrCode() - { - Bundle data = getIntent().getExtras(); - if (data != null) - { - String qrCode = data.getString("qrCode"); - String sessionId = data.getString("session"); - chainIdOverride = data.getLong(C.EXTRA_CHAIN_ID, 0); - if (sessionId != null) fromSessionActivity = true; - if (!TextUtils.isEmpty(qrCode)) - { - parseSessionCode(data.getString("qrCode")); - } - else if (!TextUtils.isEmpty(sessionId)) - { - session = viewModel.getSession(sessionId); - } - } - else - { - Toast.makeText(this, "Error retrieving QR code", Toast.LENGTH_SHORT).show(); - finish(); - } - } - - private void parseSessionCode(String wcCode) - { - if (wcCode != null && wcCode.startsWith(WC_LOCAL_PREFIX)) - { - wcCode = wcCode.replace(WC_LOCAL_PREFIX, ""); - fromDappBrowser = true; - } - else if (wcCode != null && wcCode.startsWith(WC_INTENT)) - { - wcCode = wcCode.replace(WC_INTENT, ""); - fromPhoneBrowser = true; //don't use this yet, but could use it for switching between apps - } - this.qrCode = wcCode; - session = WCSession.Companion.from(qrCode); - Timber.d("WCClient: %s", qrCode); - } - - private void initViewModel() - { - viewModel = new ViewModelProvider(this) - .get(WalletConnectViewModel.class); - - viewModel.defaultWallet().observe(this, this::onDefaultWallet); - viewModel.serviceReady().observe(this, this::onServiceReady); - viewModel.transactionFinalised().observe(this, this::txWritten); - viewModel.transactionError().observe(this, this::txError); - viewModel.transactionSigned().observe(this, this::txSigned); - - viewModel.startService(this); - } - - private void initViews() - { - progressBar = findViewById(R.id.progress); - infoLayout = findViewById(R.id.layout_info); - icon = findViewById(R.id.icon); - peerName = findViewById(R.id.peer_name); - peerUrl = findViewById(R.id.peer_url); - statusText = findViewById(R.id.connection_status); - textName = findViewById(R.id.text_name); - txCountLayout = findViewById(R.id.layout_tx_count); - txCount = findViewById(R.id.tx_count); - chainName = findViewById(R.id.chain_name); - chainIcon = findViewById(R.id.chain_icon); - - progressBar.setVisibility(View.VISIBLE); - infoLayout.setVisibility(View.GONE); - - functionBar = findViewById(R.id.layoutButtons); - functionBar.setupFunctions(this, new ArrayList<>(Collections.singletonList(R.string.action_end_session))); - functionBar.setVisibility(View.GONE); - } - - @Override - public void handleClick(String action, int id) - { - if (id == R.string.action_end_session) - { - endSessionDialog(); - } - } - - //TODO: Refactor this into elements - this function is unmaintainable - private void onDefaultWallet(Wallet wallet) - { - Timber.tag(TAG).d("Open Connection: %s", getSessionId()); - - String peerId; - String sessionId = getSessionId(); - String connectionId = viewModel.getRemotePeerId(sessionId); - if (!TextUtils.isEmpty(viewModel.getWallet().address)) - { - if (connectionId == null && session != null) //new session request - { - Timber.tag(TAG).d("New Session: %s", getSessionId()); - //new connection, create a random ID to identify us to the remotePeer. - peerId = UUID.randomUUID().toString(); //Create a new ID for our side of this session. The remote peer uses this ID to identify us - connectionId = null; //connectionId is only relevant for resuming a session - } - else //existing session, rebuild the session data - { - //try to retrieve the session from database - session = viewModel.getSession(sessionId); - displaySessionStatus(sessionId); - - viewModel.getClient(this, sessionId, client -> { - Timber.tag(TAG).d("Resume Session: %s", getSessionId()); - - if (client == null && fromSessionActivity) - { - functionBar.setVisibility(View.GONE); - return; - } - else if (client == null || !client.isConnected()) - { - if (client == null || (!fromSessionActivity && session == null)) - { - showErrorDialogTerminate(getString(R.string.session_terminated)); - infoLayout.setVisibility(View.GONE); - } - else - { - //attempt to restart the session; this will allow session 'force' close - restartSession(session); - } - } - else - { -// getPendingRequest(); - setClient(client); - } - }); - - return; - } - - Timber.tag(TAG).d("connect: peerID %s", peerId); - Timber.tag(TAG).d("connect: remotePeerID %s", connectionId); - - initWalletConnectPeerMeta(); - initWalletConnectClient(); - initWalletConnectSession(peerId, connectionId); - } - } - - private void restartSession(WCSession session) - { - String sessionId = session.getTopic(); - client = WCUtils.createWalletConnectSession(this, viewModel.getWallet(), - session, viewModel.getPeerId(sessionId), viewModel.getRemotePeerId(sessionId)); - viewModel.putClient(this, sessionId, client); - } - - @SuppressWarnings("MethodOnlyUsedFromInnerClass") - private void executedPendingRequest(long id) - { - viewModel.removePendingRequest(this, id); - } - - @Override - public boolean receiveRequest(WCRequest rq) - { - if (rq != null) - { - requestId = rq.id; - long useChainId = viewModel.getChainId(getSessionId()); - switch (rq.type) - { - case MESSAGE: - if (watchOnly(rq.id)) return false; - runOnUiThread(() -> { - onEthSign(rq.id, rq.sign, useChainId); - }); - break; - case SIGN_TX: - if (watchOnly(rq.id)) return false; - runOnUiThread(() -> { - onEthSignTransaction(rq.id, rq.tx, useChainId); - }); - break; - case SEND_TX: - if (watchOnly(rq.id)) return false; - runOnUiThread(() -> { - onEthSendTransaction(rq.id, rq.tx, useChainId); - }); - break; - case FAILURE: - runOnUiThread(() -> { - onFailure(rq.throwable); - }); - break; - case SESSION_REQUEST: - Timber.tag(TAG).d("On Request: %s", rq.peer.getName()); - runOnUiThread(() -> { - onSessionRequest(rq.id, rq.peer, rq.chainId); - }); - break; - } - } - - return true; - } - - private boolean watchOnly(long id) - { - if (!viewModel.getWallet().canSign()) - { - closeErrorDialog(); - dialog = new AWalletAlertDialog(this, AWalletAlertDialog.ERROR); - dialog.setTitle(R.string.watch_wallet); - dialog.setButton(R.string.action_close, v -> { - viewModel.rejectRequest(getApplication(), getSessionId(), - id, getString(R.string.message_authentication_failed)); - dialog.dismiss(); - }); - dialog.setCancelable(false); - dialog.show(); - return true; - } - else - { - return false; - } - } - - private void closeErrorDialog() - { - if (dialog != null && dialog.isShowing()) - { - dialog.dismiss(); - } - } - - private void startMessageCheck() - { - IntentFilter filter = new IntentFilter(C.WALLET_CONNECT_REQUEST); - filter.addAction(C.WALLET_CONNECT_NEW_SESSION); - filter.addAction(C.WALLET_CONNECT_FAIL); - filter.addAction(C.WALLET_CONNECT_CLIENT_TERMINATE); - filter.addAction(C.WALLET_CONNECT_SWITCH_CHAIN); - filter.addAction(C.WALLET_CONNECT_ADD_CHAIN); - if (broadcastManager == null) broadcastManager = LocalBroadcastManager.getInstance(this); - broadcastManager.registerReceiver(walletConnectActionReceiver, filter); - } - - private String getSessionId() - { - if (qrCode != null) - { - String uriString = qrCode.replace("wc:", "wc://"); - return Uri.parse(uriString).getUserInfo(); - } - else if (session != null) - { - return session.getTopic(); - } - else - { - return null; - } - } - - private void initWalletConnectSession(String peerId, String connectionId) - { - if (session == null) - { - //error situation! - invalidSession(); - return; - } - - Timber.tag(TAG).d("Connect: %s (%s)", getSessionId(), connectionId); - client.connect(session, peerMeta, peerId, connectionId); - - client.setOnFailure(throwable -> { - Timber.tag(TAG).d("On Fail: %s", throwable.getMessage()); - showErrorDialog("Error: " + throwable.getMessage()); - return Unit.INSTANCE; - }); - - handler.postDelayed(() -> { - //Timeout check - if (client != null && client.chainIdVal() == 0 && (dialog == null || !dialog.isShowing())) - { - //show timeout - showTimeoutDialog(); - } - }, CONNECT_TIMEOUT); - } - - private void invalidSession() - { - closeErrorDialog(); - dialog = new AWalletAlertDialog(this, AWalletAlertDialog.ERROR); - dialog.setTitle(R.string.invalid_walletconnect_session); - dialog.setMessage(R.string.restart_walletconnect_session); - dialog.setButton(R.string.dialog_ok, v -> { - dialog.dismiss(); - finish(); - }); - dialog.setCancelable(false); - dialog.show(); - - viewModel.trackError(Analytics.Error.WALLET_CONNECT, getString(R.string.invalid_walletconnect_session)); - } - - private void initWalletConnectPeerMeta() - { - peerMeta = new WCPeerMeta( - getString(R.string.app_name), - C.ALPHAWALLET_WEB, - viewModel.getWallet().address, - new ArrayList<>(Collections.singleton(C.ALPHAWALLET_LOGO_URI)) - ); - } - - private void initWalletConnectClient() - { - client = new WCClient(); - - client.setOnWCOpen(peerId -> { - viewModel.putClient(this, getSessionId(), client); - Timber.tag(TAG).d("On Open: %s", peerId); - return Unit.INSTANCE; - }); - } - - @Override - protected void onSaveInstanceState(@NotNull Bundle state) - { - super.onSaveInstanceState(state); - //need to preserve the orientation and current signing request - state.putInt("ORIENTATION", getResources().getConfiguration().orientation); - state.putLong("SESSIONID", requestId); - state.putString("SESSIONIDSTR", getSessionId()); - if (confirmationDialog != null && confirmationDialog.isShowing() && confirmationDialog.getTransaction() != null) - { - state.putParcelable("TRANSACTION", confirmationDialog.getTransaction()); - state.putLong("CHAINID", viewModel.getChainId(getSessionId())); - } - if (confirmationDialog != null && confirmationDialog.isShowing() && signData != null) - { - state.putString("SIGNDATA", signData); - state.putInt("SIGNTYPE", signType.ordinal()); - state.putLong("LASTID", lastId); - state.putString("PEERURL", peerUrl.getText().toString()); - } - } - - private void setupClient(final String sessionId) - { - viewModel.getClient(this, sessionId, client -> - runOnUiThread(() -> { - if (client == null || !client.isConnected()) - { - statusText.setText(R.string.not_connected); - statusText.setTextColor(getColor(R.color.error)); - } - else - { - statusText.setText(R.string.online); - statusText.setTextColor(getColor(R.color.positive)); - } - })); - } - - private void displaySessionStatus(String sessionId) - { - progressBar.setVisibility(View.GONE); - functionBar.setVisibility(View.VISIBLE); - infoLayout.setVisibility(View.VISIBLE); - WCPeerMeta remotePeerData = viewModel.getRemotePeer(sessionId); - this.remotePeerMeta = remotePeerData; // init meta to access in other places - if (remotePeerData != null) - { - if (remotePeerData.getIcons().isEmpty()) - { - icon.setImageResource(R.drawable.ic_coin_eth_small); - } - else - { - Glide.with(this) - .load(remotePeerData.getIcons().get(0)) - .circleCrop() - .into(icon); - } - peerName.setText(remotePeerData.getName()); - textName.setText(remotePeerData.getName()); - peerUrl.setText(remotePeerData.getUrl()); - chainName.setChainID(viewModel.getChainId(sessionId)); - chainIcon.setVisibility(View.VISIBLE); - chainIcon.bindData(viewModel.getChainId(sessionId)); - viewModel.startGasCycle(viewModel.getChainId(sessionId)); - updateSignCount(); - } - } - - private void onSessionRequest(Long id, WCPeerMeta peer, long chainId) - { - if (peer == null) - { - finish(); - } - - closeErrorDialog(); - - closeConfirmationDialog(); - - String displayIcon = (peer.getIcons().size() > 0) ? peer.getIcons().get(0) : DEFAULT_ICON; - - chainIdOverride = chainIdOverride > 0 ? chainIdOverride : (chainId > 0 ? chainId : MAINNET_ID); - - displaySessionRequestDetails(peer, chainIdOverride, displayIcon); - - confirmationDialog = new ActionSheetDialog(this, peer, chainIdOverride, displayIcon, this); - if (confirmationDialog.getActionSheetStatus() == ActionSheetStatus.ERROR_INVALID_CHAIN) - { - showErrorDialogUnsupportedNetwork(id, chainIdOverride); - return; - } - else - { - confirmationDialog.show(); - confirmationDialog.fullExpand(); - viewModel.track(Analytics.Action.WALLET_CONNECT_SESSION_REQUEST); - } - - if (confirmationDialog.isShowing() && - !viewModel.isActiveNetwork(chainId) && - !viewModel.isActiveNetwork(chainIdOverride)) - { - openChainSelection(); - } - } - - private void displaySessionRequestDetails(WCPeerMeta peer, long chainId, String displayIcon) - { - Glide.with(this) - .load(displayIcon) - .circleCrop() - .into(icon); - peerName.setText(peer.getName()); - textName.setText(peer.getName()); - peerUrl.setText(peer.getUrl()); - txCount.setText(R.string.empty); - chainName.setChainID(chainId); - chainIcon.setVisibility(View.VISIBLE); - chainIcon.bindData(chainId); - remotePeerMeta = peer; - } - - private void closeConfirmationDialog() - { - if (confirmationDialog != null) - { - if (confirmationDialog.isShowing()) - { // if already opened - confirmationDialog.forceDismiss(); - } - } - } - - private void onEthSign(Long id, WCEthereumSignMessage message, long chainId) - { - Signable signable = null; - lastId = id; - signData = message.getData(); - signType = message.getType(); - - switch (message.getType()) - { - case MESSAGE: - // see https://docs.walletconnect.org/json-rpc-api-methods/ethereum - // WalletConnect doesn't provide access to deprecated eth_sign - // Instead it uses sign_personal for both - // signable = new EthereumMessage(message.getData(), peerUrl.getText().toString(), id, SignMessageType.SIGN_MESSAGE); - // break; - // Drop through - case PERSONAL_MESSAGE: - signable = new EthereumMessage(message.getData(), peerUrl.getText().toString(), id, SignMessageType.SIGN_PERSONAL_MESSAGE); - doSignMessage(signable); - break; - case TYPED_MESSAGE: - signable = new EthereumTypedMessage(message.getData(), peerUrl.getText().toString(), id, new CryptoFunctions()); - if (signable.getChainId() != chainId) - { - showErrorDialogIncompatibleNetwork(signable.getCallbackId(), signable.getChainId(), chainId); - } - else - { - doSignMessage(signable); - } - break; - } - } - - private void showErrorDialogUnsupportedNetwork(long callbackId, long chainId) - { - if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)) - { - runOnUiThread(() -> { - closeErrorDialog(); - dialog = new AWalletAlertDialog(this, AWalletAlertDialog.ERROR); - String message = getString(R.string.error_walletconnect_session_request_unsupported_network, String.valueOf(chainId)); - dialog.setMessage(message); - dialog.setButton(R.string.action_close, v -> { - dialog.dismiss(); - dismissed("", callbackId, false); - finish(); - }); - dialog.setCancelable(false); - dialog.show(); - - viewModel.trackError(Analytics.Error.WALLET_CONNECT, message); - }); - } - } - - private void showErrorDialogIncompatibleNetwork(long callbackId, long requestingChainId, long activeChainId) - { - if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)) - { - runOnUiThread(() -> { - closeErrorDialog(); - dialog = new AWalletAlertDialog(this, AWalletAlertDialog.ERROR); - String message = EthereumNetworkBase.isChainSupported(requestingChainId) ? - getString(R.string.error_eip712_incompatible_network, - EthereumNetworkBase.getShortChainName(requestingChainId), - EthereumNetworkBase.getShortChainName(activeChainId)) : - getString(R.string.error_eip712_unsupported_network, String.valueOf(requestingChainId)); - dialog.setMessage(message); - dialog.setButton(R.string.action_cancel, v -> { - dialog.dismiss(); - dismissed("", callbackId, false); - }); - dialog.setCancelable(false); - dialog.show(); - - viewModel.trackError(Analytics.Error.WALLET_CONNECT, message); - }); - } - } - - private void onFailure(@NonNull Throwable throwable) - { - closeErrorDialog(); - dialog = new AWalletAlertDialog(this, AWalletAlertDialog.ERROR); - dialog.setTitle(R.string.title_dialog_error); - dialog.setMessage(throwable.getMessage()); - dialog.setButton(R.string.try_again, v -> onDefaultWallet(viewModel.getWallet())); - dialog.setSecondaryButton(R.string.action_cancel, v -> { - dialog.dismiss(); - killSession(); - finish(); - }); - dialog.setCancelable(false); - dialog.show(); - - AnalyticsProperties props = new AnalyticsProperties(); - props.put(Analytics.PROPS_ERROR_MESSAGE, throwable.getMessage()); - viewModel.track(Analytics.Action.WALLET_CONNECT_TRANSACTION_FAILED, props); - } - - private void doSignMessage(final Signable signable) - { - confirmationDialog = new ActionSheetSignDialog(this, this, signable); - confirmationDialog.show(); - - viewModel.track(Analytics.Action.WALLET_CONNECT_SIGN_MESSAGE_REQUEST); - } - - @Override - public void signingComplete(SignatureFromKey signature, Signable signable) - { - viewModel.recordSign(signable, getSessionId(), () -> { - viewModel.approveRequest(getApplication(), getSessionId(), signable.getCallbackId(), Numeric.toHexString(signature.signature)); - confirmationDialog.success(); - if (fromDappBrowser) - { - confirmationDialog.forceDismiss(); - switchToDappBrowser(); - } - requestId = 0; - lastId = 0; - signData = null; - updateSignCount(); - }); - } - - @Override - public void signingFailed(Throwable error, Signable message) - { - showErrorDialog(error.getMessage()); - confirmationDialog.dismiss(); - if (fromDappBrowser) switchToDappBrowser(); - requestId = 0; - lastId = 0; - signData = null; - } - - @Override - public WalletType getWalletType() - { - return viewModel.getWallet().type; - } - - private void onEthSignTransaction(Long id, WCEthereumTransaction transaction, long chainId) - { - lastId = id; - final Web3Transaction w3Tx = new Web3Transaction(transaction, id, SignType.SIGN_TX); - final ActionSheetDialog confDialog = generateTransactionRequest(w3Tx, chainId); - if (confDialog != null) - { - confirmationDialog = confDialog; - confirmationDialog.setSignOnly(); //sign transaction only - confirmationDialog.show(); - - viewModel.track(Analytics.Action.WALLET_CONNECT_SIGN_TRANSACTION_REQUEST); - } - } - - private void onEthSendTransaction(Long id, WCEthereumTransaction transaction, long chainId) - { - lastId = id; - final Web3Transaction w3Tx = new Web3Transaction(transaction, id, SignType.SEND_TX); - final ActionSheetDialog confDialog = generateTransactionRequest(w3Tx, chainId); - if (confDialog != null) - { - confirmationDialog = confDialog; - confirmationDialog.show(); - - viewModel.track(Analytics.Action.WALLET_CONNECT_SEND_TRANSACTION_REQUEST); - } - } - - private ActionSheetDialog generateTransactionRequest(Web3Transaction w3Tx, long chainId) - { - try - { - if ((confirmationDialog == null || !confirmationDialog.isShowing()) && - (w3Tx.recipient.equals(Address.EMPTY) && w3Tx.payload != null) // Constructor - || (!w3Tx.recipient.equals(Address.EMPTY) && (w3Tx.payload != null || w3Tx.value != null))) // Raw or Function TX - { - WCPeerMeta remotePeerData = viewModel.getRemotePeer(getSessionId()); - Token token = viewModel.getTokensService().getTokenOrBase(chainId, w3Tx.recipient.toString()); - final ActionSheetDialog confDialog = new ActionSheetDialog(this, w3Tx, token, "", - w3Tx.recipient.toString(), viewModel.getTokensService(), this); - confDialog.setURL(remotePeerData.getUrl()); - confDialog.setCanceledOnTouchOutside(false); - confDialog.waitForEstimate(); - - viewModel.calculateGasEstimate(viewModel.getWallet(), w3Tx, chainId) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(confDialog::setGasEstimate, - Throwable::printStackTrace) - .isDisposed(); - - return confDialog; - } - } - catch (Exception e) - { - Timber.e(e); - } - - return null; - } - - private void killSession() - { - Timber.tag(TAG).d(": Terminate Session: %s", getSessionId()); - if (client != null && session != null && client.isConnected()) - { - viewModel.track(Analytics.Action.WALLET_CONNECT_SESSION_ENDED); - client.killSession(); - viewModel.disconnectSession(this, client.sessionId()); - awWalletConnectClient.updateNotification(); - handler.postDelayed(this::finish, 5000); - } - else - { - finish(); - } - } - - @Override - public void onPause() - { - super.onPause(); - broadcastManager.unregisterReceiver(walletConnectActionReceiver); - } - - @Override - public void onResume() - { - super.onResume(); - viewModel.track(Analytics.Navigation.WALLET_CONNECT_SESSION_DETAIL); - //see if the session is active - setupClient(getSessionId()); - startMessageCheck(); - } - - @Override - public void onDestroy() - { - super.onDestroy(); - if (viewModel != null) viewModel.onDestroy(); - } - - private void showTimeoutDialog() - { - if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)) - { - runOnUiThread(() -> { - closeErrorDialog(); - dialog = new AWalletAlertDialog(this, AWalletAlertDialog.ERROR); - dialog.setTitle(R.string.title_dialog_error); - dialog.setMessage(R.string.walletconnect_timeout); - dialog.setButton(R.string.ok, v -> dialog.dismiss()); - dialog.setSecondaryButton(R.string.action_close, v -> { - dialog.dismiss(); - finish(); - }); - dialog.setCancelable(false); - dialog.show(); - - viewModel.track(Analytics.Action.WALLET_CONNECT_CONNECTION_TIMEOUT); - }); - } - } - - private void showErrorDialog(String message) - { - if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)) - { - runOnUiThread(() -> { - closeErrorDialog(); - dialog = new AWalletAlertDialog(this, AWalletAlertDialog.ERROR); - dialog.setTitle(R.string.title_dialog_error); - dialog.setMessage(message); - dialog.setButton(R.string.try_again, v -> onDefaultWallet(viewModel.getWallet())); - dialog.setSecondaryButton(R.string.action_close, v -> { - dialog.dismiss(); - killSession(); - }); - dialog.setCancelable(false); - dialog.show(); - - viewModel.trackError(Analytics.Error.WALLET_CONNECT, message); - }); - } - } - - private void showErrorDialogCancel(String title, String message) - { - if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)) - { - runOnUiThread(() -> { - closeErrorDialog(); - dialog = new AWalletAlertDialog(this, AWalletAlertDialog.ERROR); - dialog.setTitle(title); - dialog.setMessage(message); - dialog.setButton(R.string.action_cancel, v -> dialog.dismiss()); - dialog.setCancelable(false); - dialog.show(); - - viewModel.trackError(Analytics.Error.WALLET_CONNECT, message); - }); - } - } - - @Override - public void onBackPressed() - { - //TODO: If from phone browser then we should return a code that tells main activity to finish - if (fromDappBrowser) - { - switchToDappBrowser(); - } - else - { - //hand back to phone browser - finish(); - } - } - - @Override - public void finish() - { - super.finish(); - } - - private void endSessionDialog() - { - runOnUiThread(() -> { - closeErrorDialog(); - dialog = new AWalletAlertDialog(this, AWalletAlertDialog.ERROR); - dialog.setTitle(R.string.dialog_title_disconnect_session); - dialog.setButton(R.string.action_close, v -> { - dialog.dismiss(); - killSession(); - }); - dialog.setSecondaryButton(R.string.action_cancel, v -> dialog.dismiss()); - dialog.setCancelable(false); - dialog.show(); - }); - } - - private void showErrorDialogTerminate(String message) - { - if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)) - { - runOnUiThread(() -> { - closeErrorDialog(); - dialog = new AWalletAlertDialog(this, AWalletAlertDialog.ERROR); - dialog.setTitle(R.string.title_dialog_error); - dialog.setMessage(message); - dialog.setButton(R.string.dialog_ok, v -> { - dialog.dismiss(); - finish(); - }); - dialog.setCancelable(false); - dialog.show(); - - viewModel.trackError(Analytics.Error.WALLET_CONNECT, message); - }); - } - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) - { - if (item.getItemId() == android.R.id.home) - { - onBackPressed(); - } - return false; - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) - { - super.onActivityResult(requestCode, resultCode, data); - - if (requestCode >= SignTransactionDialog.REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS && requestCode <= SignTransactionDialog.REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS + 10) - { - if (confirmationDialog != null && confirmationDialog.isShowing()) - confirmationDialog.completeSignRequest(resultCode == RESULT_OK); - } - if (resultCode == RESULT_OK) - { - if (requestCode == C.REQUEST_TRANSACTION_CALLBACK) - { - handleTransactionCallback(resultCode, data); - } - } - else - { - showErrorDialogCancel(peerName.getText().toString(), getString(R.string.message_transaction_not_sent)); - if (requestCode == C.REQUEST_TRANSACTION_CALLBACK) - { - viewModel.rejectRequest(this, getSessionId(), lastId, getString(R.string.message_authentication_failed)); - } - else - { - viewModel.rejectRequest(this, getSessionId(), lastId, getString(R.string.message_reject_request)); - } - } - } - - //return from the openConfirmation above - public void handleTransactionCallback(int resultCode, Intent data) - { - if (data == null) return; - final Web3Transaction web3Tx = data.getParcelableExtra(C.EXTRA_WEB3TRANSACTION); - if (resultCode == RESULT_OK && web3Tx != null) - { - String hashData = data.getStringExtra(C.EXTRA_TRANSACTION_DATA); - viewModel.recordSignTransaction(getApplicationContext(), web3Tx, String.valueOf(viewModel.getChainId(getSessionId())), getSessionId()); - viewModel.approveRequest(this, getSessionId(), lastId, hashData); - } - else if (web3Tx != null) - { - showErrorDialogCancel(getString(R.string.title_wallet_connect), getString(R.string.message_transaction_not_sent)); - viewModel.rejectRequest(this, getSessionId(), lastId, getString(R.string.message_reject_request)); - } - - if (fromDappBrowser) - { - if (confirmationDialog != null) confirmationDialog.forceDismiss(); - switchToDappBrowser(); - } - } - - private void switchToDappBrowser() - { - Intent intent = new Intent(this, HomeActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); - startActivity(intent); - } - - @Override - public void getAuthorisation(SignAuthenticationCallback callback) - { - viewModel.getAuthenticationForSignature(viewModel.getWallet(), this, callback); - } - - @Override - public void sendTransaction(Web3Transaction finalTx) - { - viewModel.requestSignature(finalTx, viewModel.getWallet(), viewModel.getChainId(getSessionId())); - } - - /** - * Complete Hardware sendTx/signTx - */ - @Override - public void completeSendTransaction(Web3Transaction tx, SignatureFromKey signature) - { - viewModel.sendTransaction(viewModel.getWallet(), viewModel.getChainId(getSessionId()), tx, signature); - } - - @Override - public void completeSignTransaction(Web3Transaction w3Tx, SignatureFromKey signature) - { - viewModel.signTransaction(viewModel.getChainId(getSessionId()), w3Tx, signature); - } - - public void txWritten(TransactionReturn txReturn) - { - viewModel.recordSignTransaction(getApplicationContext(), txReturn.tx, String.valueOf(viewModel.getChainId(getSessionId())), getSessionId()); - viewModel.approveRequest(getApplication(), getSessionId(), txReturn.tx.leafPosition, txReturn.hash); - confirmationDialog.transactionWritten(getString(R.string.dialog_title_sign_transaction)); - if (fromDappBrowser) switchToDappBrowser(); - confirmationDialog.transactionWritten(txReturn.hash); - requestId = 0; - updateSignCount(); - - viewModel.track(Analytics.Action.WALLET_CONNECT_TRANSACTION_SUCCESS); - } - - private void txSigned(TransactionReturn sigData) - { - if (sigData != null) - { - viewModel.recordSignTransaction(getApplicationContext(), sigData.tx, String.valueOf(viewModel.getChainId(getSessionId())), getSessionId()); - viewModel.approveRequest(getApplication(), getSessionId(), sigData.tx.leafPosition, sigData.hash); - confirmationDialog.transactionWritten(getString(R.string.dialog_title_sign_transaction)); - if (fromDappBrowser) - { - switchToDappBrowser(); - } - requestId = 0; - updateSignCount(); - } - } - - //Transaction failed to be sent - private void txError(TransactionReturn txError) - { - if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)) - { - runOnUiThread(() -> { - closeErrorDialog(); - dialog = new AWalletAlertDialog(this, AWalletAlertDialog.ERROR); - dialog.setTitle(R.string.invalid_walletconnect_session); - dialog.setTitle(R.string.error_transaction_failed); - dialog.setMessage(txError.throwable.getMessage()); - dialog.setButtonText(R.string.button_ok); - dialog.setButtonListener(v -> { - dialog.dismiss(); - if (fromDappBrowser) switchToDappBrowser(); - }); - dialog.show(); - - AnalyticsProperties props = new AnalyticsProperties(); - props.put(Analytics.PROPS_ERROR_MESSAGE, txError.throwable.getMessage()); - viewModel.track(Analytics.Action.WALLET_CONNECT_TRANSACTION_FAILED, props); - }); - } - //TODO: Report error fail to WalletConnect - } - - @Override - public void dismissed(String txHash, long callbackId, boolean actionCompleted) - { - //actionsheet dismissed - if action not completed then user cancelled - if (!actionCompleted) - { - viewModel.rejectRequest(this, getSessionId(), callbackId, getString(R.string.message_reject_request)); - viewModel.track(Analytics.Action.WALLET_CONNECT_TRANSACTION_CANCELLED); - } - - if (fromDappBrowser) switchToDappBrowser(); - requestId = 0; - confirmationDialog = null; - } - - @Override - public void notifyConfirm(String mode) - { - AnalyticsProperties props = new AnalyticsProperties(); - props.put(Analytics.PROPS_ACTION_SHEET_MODE, mode); - props.put(Analytics.PROPS_ACTION_SHEET_SOURCE, ActionSheetSource.WALLET_CONNECT.getValue()); - viewModel.track(Analytics.Action.ACTION_SHEET_COMPLETED, props); - } - - @Override - public ActivityResultLauncher gasSelectLauncher() - { - return getGasSettings; - } - - @Override - public void signTransaction(Web3Transaction tx) - { - viewModel.requestSignatureOnly(tx, viewModel.getWallet(), viewModel.getChainId(getSessionId())); - } - - @Override - public void notifyWalletConnectApproval(long selectedChain) - { - client.approveSession(Collections.singletonList(viewModel.getWallet().address), selectedChain); - //update client in service - viewModel.putClient(WalletConnectActivity.this, getSessionId(), client); - viewModel.createNewSession(getSessionId(), client.getPeerId(), client.getRemotePeerId(), - new Gson().toJson(session), new Gson().toJson(remotePeerMeta), selectedChain); - chainName.setChainID(selectedChain); - chainIcon.setVisibility(View.VISIBLE); - chainIcon.bindData(selectedChain); - progressBar.setVisibility(View.GONE); - functionBar.setVisibility(View.VISIBLE); - infoLayout.setVisibility(View.VISIBLE); - chainIdOverride = selectedChain; - setupClient(getSessionId()); //should populate this activity - viewModel.track(Analytics.Action.WALLET_CONNECT_SESSION_APPROVED); - if (fromDappBrowser) - { - //switch back to dappBrowser - switchToDappBrowser(); - } - } - - @Override - public GasService getGasService() - { - return viewModel.getGasService(); - } - - @Override - public void denyWalletConnect() - { - client.rejectSession(getString(R.string.message_reject_request)); - viewModel.track(Analytics.Action.WALLET_CONNECT_SESSION_REJECTED); - finish(); - } - - @Override - public void openChainSelection() - { - ActionSheetCallback.super.openChainSelection(); - Intent intent = new Intent(WalletConnectActivity.this, NetworkChooserActivity.class); - intent.putExtra(C.EXTRA_SINGLE_ITEM, true); - intent.putExtra(C.EXTRA_CHAIN_ID, chainIdOverride); - getNetwork.launch(intent); - } - - private void showSwitchChainDialog() - { - try - { - Token baseToken = viewModel.getTokenService().getTokenOrBase(switchChainId, viewModel.defaultWallet().getValue().address); - NetworkInfo newNetwork = EthereumNetworkBase.getNetworkInfo(switchChainId); - NetworkInfo activeNetwork = EthereumNetworkBase.getNetworkInfo(client.chainIdVal()); - if (confirmationDialog != null && confirmationDialog.isShowing()) - return; - confirmationDialog = new ActionSheetDialog(this, this, R.string.switch_chain_request, R.string.switch_and_reload, - switchChainDialogCallbackId, baseToken, activeNetwork, newNetwork); - confirmationDialog.setOnDismissListener(dialog -> { - viewModel.approveSwitchEthChain(WalletConnectActivity.this, switchChainRequestId, currentSessionId, switchChainId, false, chainAvailable); - confirmationDialog.setOnDismissListener(null); // remove from here as dialog is multi-purpose - confirmationDialog = null; - }); - confirmationDialog.setCanceledOnTouchOutside(false); - confirmationDialog.show(); - confirmationDialog.fullExpand(); - - viewModel.track(Analytics.Action.WALLET_CONNECT_SWITCH_NETWORK_REQUEST); - } - catch (Exception e) - { - Timber.e(e); - } - } - - private void onSwitchChainRequest(Intent intent) - { - name = intent.getStringExtra(C.EXTRA_NAME); - switchChainRequestId = intent.getLongExtra(C.EXTRA_WC_REQUEST_ID, -1); - switchChainId = intent.getLongExtra(C.EXTRA_CHAIN_ID, -1); - currentSessionId = intent.getStringExtra(C.EXTRA_SESSION_ID); - Timber.tag(TAG).d("MSG: SWITCH CHAIN: name: %s, chainId: %s", name, switchChainId); - - if (currentSessionId == null || !session.getTopic().equals(currentSessionId)) - { - Timber.tag(TAG).d("Wrong session"); - return; - } - if (switchChainId == -1 || requestId == -1) - { - Timber.tag(TAG).d("Cant find data"); - return; - } - chainAvailable = EthereumNetworkBase.getNetworkInfo(switchChainId) != null; - // reject with error message as the chain is not added - if (!chainAvailable) - { - viewModel.approveSwitchEthChain(WalletConnectActivity.this, requestId, currentSessionId, switchChainId, false, false); - } - else - { - showSwitchChainDialog(); - } - } - - private void onAddChainRequest(Intent intent) - { - long requestId = intent.getLongExtra(C.EXTRA_WC_REQUEST_ID, -1); - String currentSessionId = intent.getStringExtra(C.EXTRA_SESSION_ID); - WalletAddEthereumChainObject chainObject = intent.getParcelableExtra(C.EXTRA_CHAIN_OBJ); - if (chainObject != null) - { - // showing dialog because chain is not added - addEthereumChainPrompt = new AddEthereumChainPrompt( - this, - chainObject, - chainObject1 -> { - this.addEthereumChainPrompt.setOnDismissListener(null); - this.addEthereumChainPrompt.dismiss(); - viewModel.approveAddEthereumChain( - WalletConnectActivity.this, - requestId, - currentSessionId, - chainObject, - true - ); - viewModel.updateSession(currentSessionId, chainObject.getChainId()); - displaySessionStatus(currentSessionId); - } - ); - - addEthereumChainPrompt.setOnDismissListener(dialog -> { - viewModel.approveAddEthereumChain( - WalletConnectActivity.this, - requestId, - currentSessionId, - chainObject, - false - ); - }); - addEthereumChainPrompt.show(); - } - else - { - viewModel.approveAddEthereumChain( - WalletConnectActivity.this, - requestId, - currentSessionId, - chainObject, - false - ); - } - } - - @Override - public void buttonClick(long callbackId, Token baseToken) - { - if (callbackId == switchChainDialogCallbackId && confirmationDialog != null) - { - confirmationDialog.setOnDismissListener(null); - confirmationDialog.dismiss(); - viewModel.approveSwitchEthChain(WalletConnectActivity.this, switchChainRequestId, currentSessionId, switchChainId, true, chainAvailable); - viewModel.updateSession(currentSessionId, switchChainId); - displaySessionStatus(session.getTopic()); - } - } - - private void updateSignCount() - { - ArrayList recordList = viewModel.getSignRecords(getSessionId()); - txCount.setText(String.valueOf(recordList.size())); - if (recordList.size() > 0) - { - txCountLayout.setOnClickListener(v -> { - Intent intent = new Intent(getApplication(), SignDetailActivity.class); - intent.putParcelableArrayListExtra(C.EXTRA_STATE, recordList); - intent.setFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK); - startActivity(intent); - }); - } - } -} diff --git a/app/src/main/java/com/alphawallet/app/ui/WalletConnectSessionActivity.java b/app/src/main/java/com/alphawallet/app/ui/WalletConnectSessionActivity.java index f1bb6af005..62cb08d303 100644 --- a/app/src/main/java/com/alphawallet/app/ui/WalletConnectSessionActivity.java +++ b/app/src/main/java/com/alphawallet/app/ui/WalletConnectSessionActivity.java @@ -352,17 +352,9 @@ class CustomViewHolder extends RecyclerView.ViewHolder public static Intent newIntent(Context context, WalletConnectSessionItem session) { - Intent intent; - if (session instanceof WalletConnectV2SessionItem) - { - intent = new Intent(context, WalletConnectV2Activity.class); - intent.putExtra("session", (WalletConnectV2SessionItem) session); - } - else - { - intent = new Intent(context, WalletConnectActivity.class); - intent.putExtra("session", session.sessionId); - } + Intent intent = new Intent(context, WalletConnectV2Activity.class); + intent.putExtra("session", (WalletConnectV2SessionItem) session); + intent.addFlags(FLAG_ACTIVITY_NEW_TASK); return intent; } diff --git a/app/src/main/java/com/alphawallet/app/ui/WalletsActivity.java b/app/src/main/java/com/alphawallet/app/ui/WalletsActivity.java index 100cd17668..c1aaf2d7c2 100644 --- a/app/src/main/java/com/alphawallet/app/ui/WalletsActivity.java +++ b/app/src/main/java/com/alphawallet/app/ui/WalletsActivity.java @@ -35,7 +35,6 @@ import com.alphawallet.app.repository.EthereumNetworkRepository; import com.alphawallet.app.repository.PreferenceRepositoryType; import com.alphawallet.app.service.KeyService; -import com.alphawallet.app.service.WalletConnectService; import com.alphawallet.app.ui.widget.adapter.WalletsSummaryAdapter; import com.alphawallet.app.viewmodel.WalletsViewModel; import com.alphawallet.app.widget.AWalletAlertDialog; diff --git a/app/src/main/java/com/alphawallet/app/viewmodel/DappBrowserViewModel.java b/app/src/main/java/com/alphawallet/app/viewmodel/DappBrowserViewModel.java index 49f0598d36..6cc8585058 100644 --- a/app/src/main/java/com/alphawallet/app/viewmodel/DappBrowserViewModel.java +++ b/app/src/main/java/com/alphawallet/app/viewmodel/DappBrowserViewModel.java @@ -43,7 +43,6 @@ import com.alphawallet.app.ui.MyAddressActivity; import com.alphawallet.app.ui.QRScanning.QRScannerActivity; import com.alphawallet.app.ui.SendActivity; -import com.alphawallet.app.ui.WalletConnectActivity; import com.alphawallet.app.ui.WalletConnectV2Activity; import com.alphawallet.app.util.DappBrowserUtils; import com.alphawallet.app.walletconnect.util.WalletConnectHelper; @@ -366,16 +365,7 @@ public void stopBalanceUpdate() public void handleWalletConnect(Context context, String url, NetworkInfo activeNetwork) { - Intent intent; - if (WalletConnectHelper.isWalletConnectV1(url)) - { - intent = getIntentOfWalletConnectV1(context, url, activeNetwork); - } - else - { - intent = getIntentOfWalletConnectV2(context, url); - } - + Intent intent = getIntentOfWalletConnectV2(context, url); context.startActivity(intent); } @@ -387,17 +377,6 @@ private Intent getIntentOfWalletConnectV2(Context context, String url) return intent; } - @NonNull - private Intent getIntentOfWalletConnectV1(Context context, String url, NetworkInfo activeNetwork) - { - String importPassData = WalletConnectActivity.WC_LOCAL_PREFIX + url; - Intent intent = new Intent(context, WalletConnectActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); - intent.putExtra(C.EXTRA_CHAIN_ID, activeNetwork.chainId); - intent.putExtra("qrCode", importPassData); - return intent; - } - public TokensService getTokenService() { return tokensService; diff --git a/app/src/main/java/com/alphawallet/app/viewmodel/HomeViewModel.java b/app/src/main/java/com/alphawallet/app/viewmodel/HomeViewModel.java index 607e1afced..ca502bdca7 100644 --- a/app/src/main/java/com/alphawallet/app/viewmodel/HomeViewModel.java +++ b/app/src/main/java/com/alphawallet/app/viewmodel/HomeViewModel.java @@ -60,12 +60,10 @@ import com.alphawallet.app.service.RealmManager; import com.alphawallet.app.service.TokensService; import com.alphawallet.app.service.TransactionsService; -import com.alphawallet.app.service.WalletConnectService; import com.alphawallet.app.ui.AddTokenActivity; import com.alphawallet.app.ui.HomeActivity; import com.alphawallet.app.ui.ImportWalletActivity; import com.alphawallet.app.ui.SendActivity; -import com.alphawallet.app.ui.WalletConnectActivity; import com.alphawallet.app.ui.WalletConnectV2Activity; import com.alphawallet.app.util.QRParser; import com.alphawallet.app.util.RateApp; @@ -73,7 +71,6 @@ import com.alphawallet.app.util.ens.AWEnsResolver; import com.alphawallet.app.walletconnect.WCClient; import com.alphawallet.app.walletconnect.entity.WCUtils; -import com.alphawallet.app.walletconnect.util.WalletConnectHelper; import com.alphawallet.app.widget.EmailPromptView; import com.alphawallet.app.widget.QRCodeActionsView; import com.alphawallet.app.widget.WhatsNewView; @@ -222,15 +219,12 @@ public LiveData defaultWallet() public GasService getGasService() { return gasService; } - public void prepare(Activity activity) + public void prepare() { progress.postValue(false); disposable = genericWalletInteract .find() - .subscribe(w -> { - onDefaultWallet(w); - initWalletConnectSessions(activity, w); - }, this::onError); + .subscribe(this::onDefaultWallet, this::onError); } public void onClean() @@ -462,21 +456,10 @@ public boolean requiresProcessing(String qrCode) private void startWalletConnect(Activity activity, String qrCode) { - Intent intent; - if (WalletConnectHelper.isWalletConnectV1(qrCode)) - { - intent = new Intent(activity, WalletConnectActivity.class); - intent.putExtra("qrCode", qrCode); - //intent.putExtra(C.EXTRA_CHAIN_ID, 0); - } - else - { - intent = new Intent(activity, WalletConnectV2Activity.class); - intent.putExtra("url", qrCode); - } + Intent intent = new Intent(activity, WalletConnectV2Activity.class); + intent.putExtra("url", qrCode); + activity.startActivity(intent); - //setResult(WALLET_CONNECT); - //finish(); } private void showActionSheet(Activity activity, QRResult qrResult) @@ -785,57 +768,6 @@ public void setCurrencyAndLocale(Context context) currencyRepository.setDefaultCurrency(preferenceRepository.getDefaultCurrency()); } - // Restart walletconnect sessions if required - private void initWalletConnectSessions(Activity activity, Wallet wallet) - { - List clientMap = new ArrayList<>(); - long cutOffTime = System.currentTimeMillis() - DateUtils.DAY_IN_MILLIS * 2; - try (Realm realm = realmManager.getRealmInstance(WC_SESSION_DB)) - { - RealmResults items = realm.where(RealmWCSession.class) - .greaterThan("lastUsageTime", cutOffTime) - .sort("lastUsageTime", Sort.DESCENDING) - .findAll(); - - for (RealmWCSession r : items) - { - String peerId = r.getPeerId(); - if (!TextUtils.isEmpty(peerId)) - { - // restart the session if it's not already known by the service - clientMap.add(WCUtils.createWalletConnectSession(activity, wallet, - r.getSession(), peerId, r.getRemotePeerId())); - } - } - } - - if (clientMap.size() > 0) - { - connectServiceAddClients(activity, clientMap); - } - } - - private void connectServiceAddClients(Activity activity, List clientMap) - { - ServiceConnection connection = new ServiceConnection() - { - @Override - public void onServiceConnected(ComponentName name, IBinder service) - { - WalletConnectService walletConnectService = ((WalletConnectService.LocalBinder) service).getService(); - walletConnectService.addClients(clientMap); - } - - @Override - public void onServiceDisconnected(ComponentName name) - { - Timber.d("Service disconnected"); - } - }; - - WCUtils.startServiceLocal(activity, connection, WalletConnectActions.CONNECT); - } - public boolean checkNewWallet(String address) { return preferenceRepository.isNewWallet(address); diff --git a/app/src/main/java/com/alphawallet/app/viewmodel/WalletConnectViewModel.java b/app/src/main/java/com/alphawallet/app/viewmodel/WalletConnectViewModel.java index ea9450a730..79335b1d90 100644 --- a/app/src/main/java/com/alphawallet/app/viewmodel/WalletConnectViewModel.java +++ b/app/src/main/java/com/alphawallet/app/viewmodel/WalletConnectViewModel.java @@ -40,7 +40,6 @@ import com.alphawallet.app.service.RealmManager; import com.alphawallet.app.service.TokensService; import com.alphawallet.app.service.TransactionSendHandlerInterface; -import com.alphawallet.app.service.WalletConnectService; import com.alphawallet.app.walletconnect.AWWalletConnectClient; import com.alphawallet.app.walletconnect.WCClient; import com.alphawallet.app.walletconnect.WCSession; diff --git a/app/src/main/java/com/alphawallet/app/walletconnect/WCClient.kt b/app/src/main/java/com/alphawallet/app/walletconnect/WCClient.kt index 791f38ca34..a4f688c30d 100644 --- a/app/src/main/java/com/alphawallet/app/walletconnect/WCClient.kt +++ b/app/src/main/java/com/alphawallet/app/walletconnect/WCClient.kt @@ -1,22 +1,37 @@ package com.alphawallet.app.walletconnect import com.alphawallet.app.C -import com.alphawallet.app.walletconnect.entity.* +import com.alphawallet.app.walletconnect.entity.InvalidJsonRpcParamsException +import com.alphawallet.app.walletconnect.entity.JsonRpcError +import com.alphawallet.app.walletconnect.entity.JsonRpcErrorResponse +import com.alphawallet.app.walletconnect.entity.JsonRpcRequest +import com.alphawallet.app.walletconnect.entity.MessageType +import com.alphawallet.app.walletconnect.entity.WCEncryptionPayload +import com.alphawallet.app.walletconnect.entity.WCEthereumSignMessage +import com.alphawallet.app.walletconnect.entity.WCEthereumTransaction +import com.alphawallet.app.walletconnect.entity.WCMethod +import com.alphawallet.app.walletconnect.entity.WCPeerMeta +import com.alphawallet.app.walletconnect.entity.WCSessionUpdate +import com.alphawallet.app.walletconnect.entity.WCSocketMessage +import com.alphawallet.app.walletconnect.entity.ethTransactionSerializer import com.alphawallet.app.walletconnect.util.WCCipher import com.alphawallet.app.walletconnect.util.toByteArray import com.alphawallet.app.web3.entity.WalletAddEthereumChainObject -import com.alphawallet.ethereum.EthereumNetworkBase.MAINNET_ID import com.github.salomonbrys.kotson.fromJson import com.github.salomonbrys.kotson.registerTypeAdapter import com.github.salomonbrys.kotson.typeToken import com.google.gson.GsonBuilder import com.google.gson.JsonArray import com.google.gson.JsonSyntaxException -import okhttp3.* +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response +import okhttp3.WebSocket +import okhttp3.WebSocketListener import okio.ByteString -import org.web3j.utils.Numeric import timber.log.Timber -import java.util.* +import java.util.Date +import java.util.UUID import java.util.concurrent.TimeUnit open class WCClient : WebSocketListener() { @@ -52,8 +67,6 @@ open class WCClient : WebSocketListener() { else return null } - private var handshakeId: Long = -1 - var accounts: List? = null private set @@ -67,10 +80,6 @@ open class WCClient : WebSocketListener() { .retryOnConnectionFailure(true) .build(); - fun chainIdVal(): Long { - return chainId?.toLong() ?: 0 - } - var onFailure: (Throwable) -> Unit = { _ -> Unit } var onDisconnect: (code: Int, reason: String) -> Unit = { _, _ -> Unit } var onSessionRequest: (id: Long, peer: WCPeerMeta) -> Unit = { _, _ -> Unit } @@ -94,9 +103,9 @@ open class WCClient : WebSocketListener() { listeners.forEach { it.onOpen(webSocket, response) } val session = - this.session ?: throw IllegalStateException("session can't be null on connection open") + this.session!! val peerId = - this.peerId ?: throw IllegalStateException("peerId can't be null on connection open") + this.peerId!! // The Session.topic channel is used to listen session request messages only. subscribe(session.topic) // The peerId channel is used to listen to all messages sent to this httpClient. @@ -172,39 +181,6 @@ open class WCClient : WebSocketListener() { socket = httpClient.newWebSocket(request, this) } - fun setupSession(accounts: List, _chainId: Long) { - this.chainId = _chainId.toString(); - this.accounts = accounts; - } - - fun approveSession(accounts: List, _chainId: Long): Boolean { - if (handshakeId <= 0) { - onFailure(Throwable("handshakeId must be greater than 0 on session approve")) - } - var useChainId: Long = _chainId - if (this.chainId?.toIntOrNull() != 1) useChainId = _chainId - chainId = useChainId.toString() - this.accounts = accounts; - - val result = WCApproveSessionResponse( - chainId = useChainId, - accounts = accounts, - peerId = peerId, - peerMeta = peerMeta - ) - val response = JsonRpcResponse( - id = handshakeId, - result = result - ) - - return encryptAndSend(gson.toJson(response)) - } - - fun sendPing(): Boolean { - Timber.d("==> ping") - return socket?.send("ping") ?: false - } - fun updateSession( accounts: List? = null, chainId: Long? = null, @@ -224,114 +200,15 @@ open class WCClient : WebSocketListener() { return encryptAndSend(gson.toJson(request)) } - fun rejectSession(message: String = "Session rejected"): Boolean { - check(handshakeId > 0) { "handshakeId must be greater than 0" } - val response = JsonRpcErrorResponse( - id = handshakeId, - error = JsonRpcError.serverError( - message = message - ) - ) - return encryptAndSend(gson.toJson(response)) - } - fun killSession(): Boolean { updateSession(approved = false) return disconnect() } - fun approveRequest(id: Long, result: T): Boolean { - val response = JsonRpcResponse( - id = id, - result = result - ) - return encryptAndSend(gson.toJson(response)) - } - - fun rejectRequest(id: Long, message: String = "Rejected by the user"): Boolean { - val response = JsonRpcErrorResponse( - id = id, - error = JsonRpcError.serverError( - message = message - ) - ) - return encryptAndSend(gson.toJson(response)) - } - - fun switchChain( - requestId: Long, - chainId: Long, - success: Boolean, - chainAvailable: Boolean = true - ): Boolean { - Timber.tag(TAG).d( - "switchChain: id: %s, chainId: %s, success: %s, chainAvailable: %s", - requestId, - chainId, - success, - chainAvailable - ); - var response:String; - if (!chainAvailable) { - val errorResponse = JsonRpcErrorResponse( - id = requestId, - error = JsonRpcError.unrecognisedChain( - message = "Unrecognized chain ID" - ) - ) - return encryptAndSend(gson.toJson(errorResponse)) - } - if (success) { - this.chainId = chainId.toString(); - updateSession() // will use updated chainId - response = gson.toJson(JsonRpcResponse( - id = requestId, // json rpc request id - result = null - )) - } else { - response = gson.toJson(JsonRpcErrorResponse( - id = requestId, - error = JsonRpcError.serverError( - message = "Rejected by user" - )) - ) - } - return encryptAndSend(response) - } - - fun addChain( - requestId: Long, - chainObj: WalletAddEthereumChainObject, - success: Boolean - ): Boolean { - Timber.tag(TAG).d( - "addChain: id: %s, chainId: %s, success: %s", - requestId, - chainObj.getChainId(), - success - ); - return if (success) { - this.chainId = chainObj.getChainId().toString() - updateSession() // updated session with new chain Id - encryptAndSend( - gson.toJson( - JsonRpcResponse(id = requestId, result = null) - ) - ) - } else { - val errorResponse = JsonRpcErrorResponse( - id = requestId, - error = JsonRpcError.serverError("Rejected by user") - ) - encryptAndSend(gson.toJson(errorResponse)) - } - } - private fun decryptMessage(text: String): String { val message = gson.fromJson(text) val encrypted = gson.fromJson(message.payload) - val session = this.session - ?: throw IllegalStateException("Session is null") + val session = this.session!! return String(WCCipher.decrypt(encrypted, session.key.toByteArray()), Charsets.UTF_8) } @@ -365,91 +242,6 @@ open class WCClient : WebSocketListener() { private fun handleRequest(request: JsonRpcRequest) { Timber.tag(TAG).d("handleRequest: %s", request.toString()) - when (request.method) { - WCMethod.SESSION_REQUEST -> { - val param = gson.fromJson>(request.params) - .firstOrNull() ?: throw InvalidJsonRpcParamsException(request.id) - handshakeId = request.id - remotePeerId = param.peerId - chainId = when (param.chainId.isNullOrEmpty()) { - true -> MAINNET_ID.toString() - false -> param.chainId - } - onSessionRequest(request.id, param.peerMeta) - } - WCMethod.SESSION_UPDATE -> { - val param = gson.fromJson>(request.params) - .firstOrNull() ?: throw InvalidJsonRpcParamsException(request.id) - if (!param.approved) { - killSession() - } - } - WCMethod.ETH_SIGN -> { - signRequest(request, WCEthereumSignMessage.WCSignType.MESSAGE) - } - WCMethod.ETH_PERSONAL_SIGN -> { - signRequest(request, WCEthereumSignMessage.WCSignType.PERSONAL_MESSAGE) - } - WCMethod.ETH_SIGN_TYPE_DATA, - WCMethod.ETH_SIGN_TYPE_DATA_V3, - WCMethod.ETH_SIGN_TYPE_DATA_V4 -> { - signRequest(request, WCEthereumSignMessage.WCSignType.TYPED_MESSAGE) - } - WCMethod.ETH_SIGN_TRANSACTION -> { - val param = gson.fromJson>(request.params) - .firstOrNull() ?: throw InvalidJsonRpcParamsException(request.id) - onEthSignTransaction(request.id, param) - } - WCMethod.ETH_SEND_TRANSACTION -> { - val param = gson.fromJson>(request.params) - .firstOrNull() ?: throw InvalidJsonRpcParamsException(request.id) - onEthSendTransaction(request.id, param) - } - WCMethod.GET_ACCOUNTS -> { - onGetAccounts(request.id) - } - WCMethod.SWITCH_ETHEREUM_CHAIN -> { - handleSwitchChain(request) - } - WCMethod.ADD_ETHEREUM_CHAIN -> { - handleAddChain(request) - } - else -> {} - } - } - - private fun handleAddChain(request: JsonRpcRequest) - { - Timber.d("WCMethod: addEthereumChain") - val param: WCAddEthChain = gson.fromJson>(request.params) - .firstOrNull() ?: throw InvalidJsonRpcParamsException(request.id) - Timber.tag(TAG).d("addChainRequest: $param") - onAddEthereumChain(request.id, param.toWalletAddEthereumObject()) - } - - private fun handleSwitchChain(request: JsonRpcRequest) - { - Timber.d("WCMethod: switchEthereumChain") - val param = gson.fromJson>(request.params) - .firstOrNull() ?: throw InvalidJsonRpcParamsException(request.id) - val newChainId: Long = Numeric.toBigInt(param.chainId).toLong(); - if (newChainId != chainIdVal()) { - onSwitchEthereumChain(request.id, newChainId) - } else { - // auto accept if same chain - switchChain(request.id, chainIdVal(), true); - } - } - - private fun signRequest(request: JsonRpcRequest, signType: WCEthereumSignMessage.WCSignType) - { - val params = gson.fromJson>(request.params) - if (params.size < 2) - throw InvalidJsonRpcParamsException(request.id) - onEthSign( - request.id, - WCEthereumSignMessage(params, signType) - ) } private fun subscribe(topic: String): Boolean { @@ -466,8 +258,7 @@ open class WCClient : WebSocketListener() { private fun encryptAndSend(result: String): Boolean { Timber.d("==> message $result") - val session = this.session - ?: throw IllegalStateException("Session is null") + val session = this.session!! val payload = gson.toJson( WCCipher.encrypt( result.toByteArray(Charsets.UTF_8), @@ -491,21 +282,4 @@ open class WCClient : WebSocketListener() { fun disconnect(): Boolean { return socket?.close(1000, null) ?: false } - - fun addSocketListener(listener: WebSocketListener) { - listeners.add(listener) - } - - fun removeSocketListener(listener: WebSocketListener) { - listeners.remove(listener) - } - - fun resetState() { - handshakeId = -1 - isConnected = false - session = null - peerId = null - remotePeerId = null - peerMeta = null - } } diff --git a/app/src/main/java/com/alphawallet/app/walletconnect/entity/WCUtils.java b/app/src/main/java/com/alphawallet/app/walletconnect/entity/WCUtils.java index 72f8b0ea36..761003fc5e 100644 --- a/app/src/main/java/com/alphawallet/app/walletconnect/entity/WCUtils.java +++ b/app/src/main/java/com/alphawallet/app/walletconnect/entity/WCUtils.java @@ -10,8 +10,6 @@ import com.alphawallet.app.R; import com.alphawallet.app.entity.Wallet; import com.alphawallet.app.entity.WalletConnectActions; -import com.alphawallet.app.repository.entity.RealmWCSession; -import com.alphawallet.app.service.WalletConnectService; import com.alphawallet.app.walletconnect.WCClient; import com.alphawallet.app.walletconnect.WCSession; diff --git a/app/src/main/java/com/alphawallet/app/widget/ActionSheetDialog.java b/app/src/main/java/com/alphawallet/app/widget/ActionSheetDialog.java index 68f971897b..8a9e546fa6 100644 --- a/app/src/main/java/com/alphawallet/app/widget/ActionSheetDialog.java +++ b/app/src/main/java/com/alphawallet/app/widget/ActionSheetDialog.java @@ -37,7 +37,6 @@ import com.alphawallet.app.service.TokensService; import com.alphawallet.app.ui.HomeActivity; import com.alphawallet.app.ui.TransactionSuccessActivity; -import com.alphawallet.app.ui.WalletConnectActivity; import com.alphawallet.app.ui.widget.entity.ActionSheetCallback; import com.alphawallet.app.ui.widget.entity.GasWidgetInterface; import com.alphawallet.app.util.Utils; @@ -128,10 +127,6 @@ public ActionSheetDialog(@NonNull Activity activity, Web3Transaction tx, Token t { mode = ActionSheetMode.SEND_TRANSACTION_DAPP; } - else if (activity instanceof WalletConnectActivity) - { - mode = ActionSheetMode.SEND_TRANSACTION_WC; - } else { mode = ActionSheetMode.SEND_TRANSACTION; @@ -845,13 +840,6 @@ public void gotSignature(SignatureFromKey signature) actionSheetCallback.getAuthorisation(signCallback); } - //Takes gas estimate from calling activity (eg WalletConnectActivity) and updates dialog -// public void setGasEstimate(BigInteger estimate) -// { -// gasWidgetInterface.setGasEstimate(estimate); -// functionBar.setPrimaryButtonEnabled(true); -// } - @Override public void setGasEstimate(GasEstimate estimate) { From 87af5128a2a7e7e69901f87ca983ce727195f5d1 Mon Sep 17 00:00:00 2001 From: James Brown Date: Sun, 10 Mar 2024 22:36:00 +1100 Subject: [PATCH 08/11] Remove WC1 code --- .../app/interact/WalletConnectInteract.java | 69 ------ .../app/ui/DappBrowserFragment.java | 4 +- .../app/ui/WalletConnectSessionActivity.java | 17 +- .../alphawallet/app/ui/WalletFragment.java | 4 +- .../alphawallet/app/ui/WalletsActivity.java | 4 +- .../app/viewmodel/HomeViewModel.java | 12 - .../app/viewmodel/WalletConnectViewModel.java | 231 ------------------ .../walletconnect/AWWalletConnectClient.java | 21 +- .../app/walletconnect/entity/WCUtils.java | 50 ---- .../com/alphawallet/app/widget/TokenIcon.java | 5 + 10 files changed, 24 insertions(+), 393 deletions(-) delete mode 100644 app/src/main/java/com/alphawallet/app/walletconnect/entity/WCUtils.java diff --git a/app/src/main/java/com/alphawallet/app/interact/WalletConnectInteract.java b/app/src/main/java/com/alphawallet/app/interact/WalletConnectInteract.java index 21863c5fea..55e75aae6c 100644 --- a/app/src/main/java/com/alphawallet/app/interact/WalletConnectInteract.java +++ b/app/src/main/java/com/alphawallet/app/interact/WalletConnectInteract.java @@ -1,18 +1,8 @@ package com.alphawallet.app.interact; -import android.content.ComponentName; -import android.content.Context; -import android.content.ServiceConnection; -import android.os.IBinder; - -import com.alphawallet.app.entity.WalletConnectActions; import com.alphawallet.app.entity.walletconnect.WalletConnectSessionItem; import com.alphawallet.app.entity.walletconnect.WalletConnectV2SessionItem; -import com.alphawallet.app.repository.entity.RealmWCSession; import com.alphawallet.app.service.RealmManager; -import com.alphawallet.app.viewmodel.WalletConnectViewModel; -import com.alphawallet.app.walletconnect.WCClient; -import com.alphawallet.app.walletconnect.entity.WCUtils; import com.walletconnect.web3.wallet.client.Wallet; import com.walletconnect.web3.wallet.client.Web3Wallet; @@ -21,9 +11,6 @@ import javax.inject.Inject; -import io.realm.Realm; -import io.realm.RealmResults; -import io.realm.Sort; import timber.log.Timber; public class WalletConnectInteract @@ -45,7 +32,6 @@ public List getSessions() { List result = new ArrayList<>(); result.addAll(getWalletConnectV2SessionItems()); - result.addAll(getWalletConnectV1SessionItems()); //now sort for active/newness result.sort((l, r) -> Long.compare(r.expiryTime, l.expiryTime)); @@ -53,61 +39,6 @@ public List getSessions() return result; } - public void fetchSessions(Context context, SessionFetchCallback sessionFetchCallback) - { - ServiceConnection connection = new ServiceConnection() - { - @Override - public void onServiceConnected(ComponentName name, IBinder service) - { - WalletConnectService walletConnectService = ((WalletConnectService.LocalBinder) service).getService(); - fetch(walletConnectService, sessionFetchCallback); - } - - @Override - public void onServiceDisconnected(ComponentName name) - { - } - }; - - WCUtils.startServiceLocal(context, connection, WalletConnectActions.CONNECT); - } - - private void fetch(WalletConnectService walletConnectService, SessionFetchCallback sessionFetchCallback) - { - List result = new ArrayList<>(); - List sessionItems = getWalletConnectV1SessionItems(); - for (WalletConnectSessionItem item : sessionItems) - { - WCClient wcClient = walletConnectService.getClient(item.sessionId); - if (wcClient != null && wcClient.isConnected()) - { - result.add(item); - } - } - - result.addAll(getWalletConnectV2SessionItems()); - sessionFetchCallback.onFetched(result); - } - - private List getWalletConnectV1SessionItems() - { - List sessions = new ArrayList<>(); - try (Realm realm = realmManager.getRealmInstance(WalletConnectViewModel.WC_SESSION_DB)) - { - RealmResults items = realm.where(RealmWCSession.class) - .sort("lastUsageTime", Sort.DESCENDING) - .findAll(); - - for (RealmWCSession r : items) - { - sessions.add(new WalletConnectSessionItem(r)); - } - } - - return sessions; - } - private List getWalletConnectV2SessionItems() { List result = new ArrayList<>(); diff --git a/app/src/main/java/com/alphawallet/app/ui/DappBrowserFragment.java b/app/src/main/java/com/alphawallet/app/ui/DappBrowserFragment.java index 212b8f7f26..030575160c 100644 --- a/app/src/main/java/com/alphawallet/app/ui/DappBrowserFragment.java +++ b/app/src/main/java/com/alphawallet/app/ui/DappBrowserFragment.java @@ -790,11 +790,11 @@ private void launchNetworkPicker() private void launchWalletConnectSessionCancel() { - String sessionId = walletConnectSession != null ? viewModel.getSessionId(walletConnectSession) : ""; + /*String sessionId = walletConnectSession != null ? viewModel.getSessionId(walletConnectSession) : ""; Intent bIntent = new Intent(getContext(), WalletConnectService.class); bIntent.setAction(String.valueOf(WalletConnectActions.CLOSE.ordinal())); bIntent.putExtra("session", sessionId); - requireActivity().startService(bIntent); + requireActivity().startService(bIntent);*/ reloadPage(); } diff --git a/app/src/main/java/com/alphawallet/app/ui/WalletConnectSessionActivity.java b/app/src/main/java/com/alphawallet/app/ui/WalletConnectSessionActivity.java index 62cb08d303..b17caf3a50 100644 --- a/app/src/main/java/com/alphawallet/app/ui/WalletConnectSessionActivity.java +++ b/app/src/main/java/com/alphawallet/app/ui/WalletConnectSessionActivity.java @@ -192,12 +192,12 @@ private void openDeleteMenu(View v) popupMenu.setOnMenuItemClickListener(item -> { if (item.getItemId() == R.id.action_delete_empty) { - viewModel.removeSessionsWithoutSignRecords(this); + //viewModel.removeSessionsWithoutSignRecords(this); return true; } else if (item.getItemId() == R.id.action_delete_all) { - viewModel.removeInactiveSessions(this); + //viewModel.removeInactiveSessions(this); return true; } return false; @@ -207,16 +207,7 @@ else if (item.getItemId() == R.id.action_delete_all) private void setupClient(final WalletConnectSessionItem session, final CustomAdapter.CustomViewHolder holder) { - if (session.wcVersion == 1) - { - viewModel.getClient(this, session.sessionId, client -> handler.post(() -> { - setStatusIconActive(holder, (client != null && client.isConnected())); - })); - } - else - { - setStatusIconActive(holder, (session.expiryTime > System.currentTimeMillis())); - } + setStatusIconActive(holder, (session.expiryTime > System.currentTimeMillis())); } private void setStatusIconActive(final CustomAdapter.CustomViewHolder holder, boolean active) @@ -244,7 +235,7 @@ private void dialogConfirmDelete(WalletConnectSessionItem session) @Override public void onSessionDisconnected() { - runOnUiThread(() -> awWalletConnectClient.updateNotification()); + //runOnUiThread(() -> awWalletConnectClient.updateNotification()); } })); cDialog.setSecondaryButtonText(R.string.action_cancel); diff --git a/app/src/main/java/com/alphawallet/app/ui/WalletFragment.java b/app/src/main/java/com/alphawallet/app/ui/WalletFragment.java index 29ed85bd21..95e5adb331 100644 --- a/app/src/main/java/com/alphawallet/app/ui/WalletFragment.java +++ b/app/src/main/java/com/alphawallet/app/ui/WalletFragment.java @@ -386,7 +386,7 @@ private void refreshList() adapter.clear(); viewModel.prepare(); viewModel.notifyRefresh(); - awWalletConnectClient.updateNotification(); + //awWalletConnectClient.updateNotification(); }); } @@ -595,7 +595,7 @@ private void onTokens(TokenCardMeta[] tokens) if (currentTabPos.equals(TokenFilter.ALL)) { - awWalletConnectClient.updateNotification(); + //awWalletConnectClient.updateNotification(); } else { diff --git a/app/src/main/java/com/alphawallet/app/ui/WalletsActivity.java b/app/src/main/java/com/alphawallet/app/ui/WalletsActivity.java index c1aaf2d7c2..00e8638361 100644 --- a/app/src/main/java/com/alphawallet/app/ui/WalletsActivity.java +++ b/app/src/main/java/com/alphawallet/app/ui/WalletsActivity.java @@ -430,10 +430,10 @@ private void updateCurrentWallet(Wallet wallet) viewModel.stopUpdates(); - Intent bIntent = new Intent(this, WalletConnectService.class); + /*Intent bIntent = new Intent(this, WalletConnectService.class); bIntent.setAction(String.valueOf(WalletConnectActions.DISCONNECT.ordinal())); bIntent.putExtra("wallet", selectedWallet); - startService(bIntent); + startService(bIntent);*/ selectedWallet = wallet; } diff --git a/app/src/main/java/com/alphawallet/app/viewmodel/HomeViewModel.java b/app/src/main/java/com/alphawallet/app/viewmodel/HomeViewModel.java index ca502bdca7..12f55ea286 100644 --- a/app/src/main/java/com/alphawallet/app/viewmodel/HomeViewModel.java +++ b/app/src/main/java/com/alphawallet/app/viewmodel/HomeViewModel.java @@ -1,21 +1,16 @@ package com.alphawallet.app.viewmodel; -import static com.alphawallet.app.viewmodel.WalletConnectViewModel.WC_SESSION_DB; import static com.alphawallet.ethereum.EthereumNetworkBase.MAINNET_ID; import android.app.Activity; -import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; -import android.content.ServiceConnection; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Handler; -import android.os.IBinder; import android.text.TextUtils; -import android.text.format.DateUtils; import android.view.View; import android.widget.Toast; @@ -37,7 +32,6 @@ import com.alphawallet.app.entity.Transaction; import com.alphawallet.app.entity.Version; import com.alphawallet.app.entity.Wallet; -import com.alphawallet.app.entity.WalletConnectActions; import com.alphawallet.app.entity.analytics.QrScanResultType; import com.alphawallet.app.entity.attestation.ImportAttestation; import com.alphawallet.app.interact.FetchWalletsInteract; @@ -49,7 +43,6 @@ import com.alphawallet.app.repository.LocaleRepositoryType; import com.alphawallet.app.repository.PreferenceRepositoryType; import com.alphawallet.app.repository.TokenRepository; -import com.alphawallet.app.repository.entity.RealmWCSession; import com.alphawallet.app.router.ExternalBrowserRouter; import com.alphawallet.app.router.ImportTokenRouter; import com.alphawallet.app.router.MyAddressRouter; @@ -69,8 +62,6 @@ import com.alphawallet.app.util.RateApp; import com.alphawallet.app.util.Utils; import com.alphawallet.app.util.ens.AWEnsResolver; -import com.alphawallet.app.walletconnect.WCClient; -import com.alphawallet.app.walletconnect.entity.WCUtils; import com.alphawallet.app.widget.EmailPromptView; import com.alphawallet.app.widget.QRCodeActionsView; import com.alphawallet.app.widget.WhatsNewView; @@ -100,9 +91,6 @@ import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; -import io.realm.Realm; -import io.realm.RealmResults; -import io.realm.Sort; import okhttp3.OkHttpClient; import okhttp3.Request; import timber.log.Timber; diff --git a/app/src/main/java/com/alphawallet/app/viewmodel/WalletConnectViewModel.java b/app/src/main/java/com/alphawallet/app/viewmodel/WalletConnectViewModel.java index 79335b1d90..3d8a1f7d07 100644 --- a/app/src/main/java/com/alphawallet/app/viewmodel/WalletConnectViewModel.java +++ b/app/src/main/java/com/alphawallet/app/viewmodel/WalletConnectViewModel.java @@ -45,7 +45,6 @@ import com.alphawallet.app.walletconnect.WCSession; import com.alphawallet.app.walletconnect.entity.GetClientCallback; import com.alphawallet.app.walletconnect.entity.WCPeerMeta; -import com.alphawallet.app.walletconnect.entity.WCUtils; import com.alphawallet.app.web3.entity.WalletAddEthereumChainObject; import com.alphawallet.app.web3.entity.Web3Transaction; import com.alphawallet.hardware.SignatureFromKey; @@ -133,35 +132,6 @@ public class WalletConnectViewModel extends BaseViewModel implements Transaction .subscribe(w -> this.wallet = w, this::onError); } - public void startService(Context context) - { - ServiceConnection connection = new ServiceConnection() - { - @Override - public void onServiceConnected(ComponentName name, IBinder service) - { - WalletConnectService walletConnectService = ((WalletConnectService.LocalBinder) service).getService(); - Timber.tag(TAG).d("Service connected"); - for (String sessionId : clientBuffer.keySet()) - { - Timber.tag(TAG).d("put from buffer: %s", sessionId); - WCClient c = clientBuffer.get(sessionId); - walletConnectService.putClient(sessionId, c); - } - clientBuffer.clear(); - serviceReady.postValue(true); - } - - @Override - public void onServiceDisconnected(ComponentName name) - { - Timber.tag(TAG).d("Service disconnected"); - } - }; - - WCUtils.startServiceLocal(context, connection, WalletConnectActions.CONNECT); - } - public void prepare() { prepareDisposable = genericWalletInteract @@ -503,112 +473,6 @@ public MutableLiveData> sessions() return sessions; } - public void removePendingRequest(Activity activity, long id) - { - ServiceConnection connection = new ServiceConnection() - { - @Override - public void onServiceConnected(ComponentName name, IBinder service) - { - WalletConnectService walletConnectService = ((WalletConnectService.LocalBinder) service).getService(); - walletConnectService.removePendingRequest(id); - } - - @Override - public void onServiceDisconnected(ComponentName name) - { - //walletConnectService = null; - Timber.tag(TAG).d("Service disconnected"); - } - }; - - WCUtils.startServiceLocal(activity, connection, WalletConnectActions.CONNECT); - } - - public void getClient(Activity activity, String sessionId, GetClientCallback clientCb) - { - ServiceConnection connection = new ServiceConnection() - { - @Override - public void onServiceConnected(ComponentName name, IBinder service) - { - WalletConnectService walletConnectService = ((WalletConnectService.LocalBinder) service).getService(); - clientCb.getClient(walletConnectService.getClient(sessionId)); - } - - @Override - public void onServiceDisconnected(ComponentName name) - { - Timber.tag(TAG).d("Service disconnected"); - } - }; - - WCUtils.startServiceLocal(activity, connection, WalletConnectActions.CONNECT); - } - - public void putClient(Activity activity, String sessionId, WCClient client) - { - ServiceConnection connection = new ServiceConnection() - { - @Override - public void onServiceConnected(ComponentName name, IBinder service) - { - WalletConnectService walletConnectService = ((WalletConnectService.LocalBinder) service).getService(); - walletConnectService.putClient(sessionId, client); - awWalletConnectClient.updateNotification(); - } - - @Override - public void onServiceDisconnected(ComponentName name) - { - Timber.tag(TAG).d("Service disconnected"); - } - }; - - WCUtils.startServiceLocal(activity, connection, WalletConnectActions.CONNECT); - } - - public void disconnectSession(Activity activity, String sessionId) - { - ServiceConnection connection = new ServiceConnection() - { - @Override - public void onServiceConnected(ComponentName name, IBinder service) - { - WalletConnectService walletConnectService = ((WalletConnectService.LocalBinder) service).getService(); - walletConnectService.terminateClient(sessionId); - } - - @Override - public void onServiceDisconnected(ComponentName name) - { - Timber.tag(TAG).d("Service disconnected"); - } - }; - - WCUtils.startServiceLocal(activity, connection, WalletConnectActions.CONNECT); - } - - public void rejectRequest(Context ctx, String sessionId, long id, String message) - { - Intent bIntent = new Intent(ctx, WalletConnectService.class); - bIntent.setAction(String.valueOf(WalletConnectActions.REJECT.ordinal())); - bIntent.putExtra("sessionId", sessionId); - bIntent.putExtra("id", id); - bIntent.putExtra("message", message); - ctx.startService(bIntent); - } - - public void approveRequest(Context ctx, String sessionId, long id, String message) - { - Intent bIntent = new Intent(ctx, WalletConnectService.class); - bIntent.setAction(String.valueOf(WalletConnectActions.APPROVE.ordinal())); - bIntent.putExtra("sessionId", sessionId); - bIntent.putExtra("id", id); - bIntent.putExtra("message", message); - ctx.startService(bIntent); - } - public String getNetworkSymbol(long chainId) { NetworkInfo info = findDefaultNetworkInteract.getNetworkInfo(chainId); @@ -619,51 +483,6 @@ public String getNetworkSymbol(long chainId) return info.symbol; } - public void prepareIfRequired() - { - if (prepareDisposable == null) - { - prepare(); - } - } - - public void approveSwitchEthChain(Context context, long requestId, String sessionId, long chainId, boolean approve, boolean chainAvailable) - { - Intent i = new Intent(context, WalletConnectService.class); - i.setAction(String.valueOf(WalletConnectActions.SWITCH_CHAIN.ordinal())); - i.putExtra(C.EXTRA_WC_REQUEST_ID, requestId); - i.putExtra(C.EXTRA_SESSION_ID, sessionId); - i.putExtra(C.EXTRA_CHAIN_ID, chainId); - i.putExtra(C.EXTRA_APPROVED, approve); - i.putExtra(C.EXTRA_CHAIN_AVAILABLE, chainAvailable); - context.startService(i); - } - - public void approveAddEthereumChain(Context context, - long requestId, - String sessionId, - WalletAddEthereumChainObject chainObject, - boolean approved) - { - Intent i = new Intent(context, WalletConnectService.class); - i.setAction(String.valueOf(WalletConnectActions.ADD_CHAIN.ordinal())); - i.putExtra(C.EXTRA_WC_REQUEST_ID, requestId); - i.putExtra(C.EXTRA_SESSION_ID, sessionId); - i.putExtra(C.EXTRA_CHAIN_OBJ, chainObject); - i.putExtra(C.EXTRA_APPROVED, approved); - - if (approved) - { - // add only if not present - if (!isChainAdded(chainObject.getChainId())) - { - ethereumNetworkRepository.saveCustomRPCNetwork(chainObject.chainName, extractRpc(chainObject), chainObject.getChainId(), - chainObject.nativeCurrency.symbol, "", "", false, -1L); - } - } - context.startService(i); - } - private String extractRpc(WalletAddEthereumChainObject chainObject) { for (String thisRpc : chainObject.rpcUrls) @@ -710,13 +529,6 @@ public void endSession(String sessionId) } } - public void removeSessionsWithoutSignRecords(Context context) - { - getInactiveSessionIds(context, sessions -> { - deleteSessionsFromRealm(filterSessionsWithoutSignRecords(sessions), this::updateSessions); - }); - } - @NonNull private ArrayList filterSessionsWithoutSignRecords(List sessions) { @@ -736,49 +548,6 @@ public void updateSessions() sessions.postValue(walletConnectInteract.getSessions()); } - public void removeInactiveSessions(Context context) - { - getInactiveSessionIds(context, list -> { - deleteSessionsFromRealm(list, this::updateSessions); - }); - } - - // connects to service to check session state and gives inactive sessions - private void getInactiveSessionIds(Context context, GenericCallback> callback) - { - List sessionItems = walletConnectInteract.getSessions(); - ArrayList inactiveSessions = new ArrayList<>(); - ServiceConnection connection = new ServiceConnection() - { - @Override - public void onServiceConnected(ComponentName name, IBinder service) - { - WalletConnectService walletConnectService = ((WalletConnectService.LocalBinder) service).getService(); - // loop & populate sessions which are inactive - for (WalletConnectSessionItem item : sessionItems) - { - WCClient wcClient = walletConnectService.getClient(item.sessionId); - // if client is not connected ie: session inactive - if (wcClient == null || !wcClient.isConnected()) - { - inactiveSessions.add(item.sessionId); - } - } - callback.call(inactiveSessions); // return inactive sessions to caller - } - - @Override - public void onServiceDisconnected(ComponentName name) - { - //walletConnectService = null; - Timber.tag(TAG).d("Service disconnected"); - } - }; - Intent i = new Intent(context, WalletConnectService.class); // not specifying action as no need. we just need to bind to service - context.startService(i); - context.bindService(i, connection, Service.BIND_ABOVE_CLIENT); - } - // deletes the RealmWCSession objects with the given sessionIds present in the list private void deleteSessionsFromRealm(List sessionIds, Runnable onSuccess) { diff --git a/app/src/main/java/com/alphawallet/app/walletconnect/AWWalletConnectClient.java b/app/src/main/java/com/alphawallet/app/walletconnect/AWWalletConnectClient.java index 1f871d40f9..d72761661a 100644 --- a/app/src/main/java/com/alphawallet/app/walletconnect/AWWalletConnectClient.java +++ b/app/src/main/java/com/alphawallet/app/walletconnect/AWWalletConnectClient.java @@ -92,10 +92,10 @@ public AWWalletConnectClient(Context context, WalletConnectInteract walletConnec hasConnection = false; } - public void onSessionDelete(@NonNull Model.SessionDelete deletedSession) + /*public void onSessionDelete(@NonNull Model.SessionDelete deletedSession) { updateNotification(); - } + }*/ public void onSessionProposal(@NonNull Model.SessionProposal sessionProposal) { @@ -216,7 +216,7 @@ public void approve(Model.SessionProposal sessionProposal, List selected Params.SessionApprove approve = new Params.SessionApprove(proposerPublicKey, buildNamespaces(sessionProposal, selectedAccounts), sessionProposal.getRelayProtocol()); Web3Wallet.INSTANCE.approveSession(approve, sessionApprove -> { new Handler(Looper.getMainLooper()).postDelayed(() -> { - updateNotification(); + //updateNotification(); callback.onSessionProposalApproved(); }, 500); return null; @@ -288,14 +288,6 @@ public MutableLiveData> sessionItemMutableLiveDat return sessionItemMutableLiveData; } - public void updateNotification() - { - walletConnectInteract.fetchSessions(context, items -> { - sessionItemMutableLiveData.postValue(items); - updateService(context, items); - }); - } - private void updateService(Context context, List walletConnectSessionItems) { try @@ -338,7 +330,6 @@ public void disconnect(String sessionId, WalletConnectV2Callback callback) { Web3Wallet.INSTANCE.disconnectSession(new Params.SessionDisconnect(sessionId), sd -> null, this::onDisconnectError); callback.onSessionDisconnected(); - updateNotification(); } private Unit onDisconnectError(Model.Error error) @@ -666,6 +657,12 @@ public void onSessionRequest(@NonNull Model.SessionRequest sessionRequest, @NonN } } + @Override + public void onSessionDelete(@NonNull Model.SessionDelete sessionDelete) + { + + } + public interface WalletConnectV2Callback { default void onSessionProposalApproved() diff --git a/app/src/main/java/com/alphawallet/app/walletconnect/entity/WCUtils.java b/app/src/main/java/com/alphawallet/app/walletconnect/entity/WCUtils.java deleted file mode 100644 index 761003fc5e..0000000000 --- a/app/src/main/java/com/alphawallet/app/walletconnect/entity/WCUtils.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.alphawallet.app.walletconnect.entity; - -import android.app.Activity; -import android.app.ActivityManager; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; - -import com.alphawallet.app.C; -import com.alphawallet.app.R; -import com.alphawallet.app.entity.Wallet; -import com.alphawallet.app.entity.WalletConnectActions; -import com.alphawallet.app.walletconnect.WCClient; -import com.alphawallet.app.walletconnect.WCSession; - -import java.util.ArrayList; -import java.util.Collections; - -public abstract class WCUtils -{ - public static void startServiceLocal(Context context, ServiceConnection connection, WalletConnectActions action) - { - Intent i = new Intent(context, WalletConnectService.class); - i.setAction(String.valueOf(action.ordinal())); - ActivityManager.RunningAppProcessInfo myProcess = new ActivityManager.RunningAppProcessInfo(); - ActivityManager.getMyMemoryState(myProcess); - boolean isInBackground = myProcess.importance != ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; - if (!isInBackground) - { - context.startService(i); - if (connection != null) context.bindService(i, connection, Context.BIND_ABOVE_CLIENT); - } - } - - public static WCClient createWalletConnectSession(Activity activity, Wallet wallet, WCSession session, String peerId, String remotePeerId) - { - WCClient client = new WCClient(); - - WCPeerMeta peerMeta = new WCPeerMeta( - activity.getString(R.string.app_name), - C.ALPHAWALLET_WEB, - wallet.address, - new ArrayList<>(Collections.singleton(C.ALPHAWALLET_LOGO_URI)) - ); - - client.connect(session, peerMeta, peerId, remotePeerId); - - return client; - } -} diff --git a/app/src/main/java/com/alphawallet/app/widget/TokenIcon.java b/app/src/main/java/com/alphawallet/app/widget/TokenIcon.java index 837cd8e926..c2752b29cd 100644 --- a/app/src/main/java/com/alphawallet/app/widget/TokenIcon.java +++ b/app/src/main/java/com/alphawallet/app/widget/TokenIcon.java @@ -265,6 +265,11 @@ private void displayTokenIcon(String iconUrl) { setupDefaultIcon(); + if (token.tokenInfo.address.equalsIgnoreCase("0xfaafdc07907ff5120a76b34b731b278c38d6043c")) + { + System.out.println("YOLESS"); + } + final RequestOptions optionalCircleCrop = squareToken || iconUrl.startsWith(ALPHAWALLET_REPO_NAME) ? new RequestOptions() : new RequestOptions().circleCrop(); currentRq = Glide.with(this) From f050cc4c16c8e98643ed6f723b8348610ccfd27a Mon Sep 17 00:00:00 2001 From: James Brown Date: Mon, 11 Mar 2024 00:22:25 +1100 Subject: [PATCH 09/11] Remove WC1 code --- app/build.gradle | 2 +- .../main/java/com/alphawallet/app/service/OpenSeaService.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index a0118326c0..30888af765 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -31,7 +31,7 @@ detekt { } android_lint_reporter { - lintFilePath = "./build/reports/lint-results-analyticsDebug.xml" + //lintFilePath = "./build/reports/lint-results-analyticsDebug.xml" detektFilePath = "./build/reports/detekt/detekt.xml" githubOwner = "AlphaWallet" githubRepositoryName = "alpha-wallet-android" diff --git a/app/src/main/java/com/alphawallet/app/service/OpenSeaService.java b/app/src/main/java/com/alphawallet/app/service/OpenSeaService.java index c69bef76aa..04792d2849 100644 --- a/app/src/main/java/com/alphawallet/app/service/OpenSeaService.java +++ b/app/src/main/java/com/alphawallet/app/service/OpenSeaService.java @@ -33,6 +33,7 @@ import java.math.BigInteger; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; @@ -200,7 +201,7 @@ private void processOpenseaTokens(Map foundTokens, for (int i = 0; i < assets.length(); i++) { JSONObject assetJSON = assets.getJSONObject(i); - String tokenStandard = assetJSON.getString("token_standard").toLowerCase(); + String tokenStandard = assetJSON.getString("token_standard").toLowerCase(Locale.ROOT); if (!TextUtils.isEmpty(tokenStandard)) { From 02551a200aed76da575fae97f107b97c1fcfe2d1 Mon Sep 17 00:00:00 2001 From: James Brown Date: Mon, 11 Mar 2024 08:41:02 +1100 Subject: [PATCH 10/11] Update for test --- .github/workflows/lint-pr.yml | 2 +- app/build.gradle | 6 +++--- app/src/main/AndroidManifest.xml | 1 - gradle.properties | 1 - 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/lint-pr.yml b/.github/workflows/lint-pr.yml index ab02a66df9..1b2f235d2a 100644 --- a/.github/workflows/lint-pr.yml +++ b/.github/workflows/lint-pr.yml @@ -27,5 +27,5 @@ jobs: PR_NUMBER: ${{ github.event.number }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - ./gradlew report -PgithubPullRequestId=$PR_NUMBER -PgithubToken=$GITHUB_TOKEN + ./gradlew report --no-configuration-cache -PgithubPullRequestId=$PR_NUMBER -PgithubToken=$GITHUB_TOKEN diff --git a/app/build.gradle b/app/build.gradle index 30888af765..73d0ceea0d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -31,7 +31,7 @@ detekt { } android_lint_reporter { - //lintFilePath = "./build/reports/lint-results-analyticsDebug.xml" + lintFilePath = "./build/reports/lint-results-analyticsDebug.xml" detektFilePath = "./build/reports/detekt/detekt.xml" githubOwner = "AlphaWallet" githubRepositoryName = "alpha-wallet-android" @@ -220,7 +220,7 @@ dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') implementation fileTree(include: ['*.aar'], dir: 'libs') //NB: Downgrade jackson due to bug in 2.15 releases that makes it incompatible with Gradle 8 - //noinspection GradleDependency + //noinspection UseTomlInstead,GradleDependency implementation platform('com.fasterxml.jackson:jackson-bom:2.13.5') //Don't upgrade from 2.13.5 due to Android API24 compatibility implementation 'com.fasterxml.jackson.core:jackson-core' implementation 'com.fasterxml.jackson.core:jackson-databind' @@ -323,7 +323,7 @@ dependencies { testImplementation group: 'org.powermock', name: 'powermock-module-junit4-rule-agent', version: '2.0.9' testImplementation group: 'org.powermock', name: 'powermock-module-junit4', version: '2.0.9' testImplementation group: 'org.powermock', name: 'powermock-api-mockito2', version: '2.0.9' - testImplementation group: 'org.json', name: 'json', version: '20240303' + testImplementation group: 'org.json', name: 'json', version: '20220320' // Component tests: Updating these appears to break the tests. testImplementation 'org.robolectric:robolectric:4.8.2' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 63bd3fef89..40117de5e5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -27,7 +27,6 @@ Date: Mon, 11 Mar 2024 12:58:46 +1100 Subject: [PATCH 11/11] Add Walletconnect icon --- app/build.gradle | 13 ++- .../alphawallet/app/entity/SuggestEIP1559.kt | 8 +- .../alphawallet/app/service/GasService.java | 2 +- .../alphawallet/app/ui/SearchActivity.java | 2 - .../app/ui/WalletConnectSessionActivity.java | 4 +- .../alphawallet/app/ui/WalletFragment.java | 28 +++++- .../app/ui/widget/TokensAdapterCallback.java | 4 +- .../app/ui/widget/adapter/TokensAdapter.java | 43 +++++--- .../ui/widget/holder/SearchTokensHolder.java | 29 +++++- .../walletconnect/AWWalletConnectClient.java | 25 ++++- .../main/res/drawable/ic_wallet_connect.xml | 29 +++--- .../res/layout/layout_manage_token_search.xml | 99 +++++++++++-------- 12 files changed, 195 insertions(+), 91 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 73d0ceea0d..cfe9db5872 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -160,6 +160,9 @@ android { targetCompatibility JavaVersion.VERSION_17 sourceCompatibility JavaVersion.VERSION_17 } + kotlinOptions { + jvmTarget = '17' + } externalNativeBuild { cmake { path "src/main/cpp/CMakeLists.txt" @@ -219,9 +222,9 @@ dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') implementation fileTree(include: ['*.aar'], dir: 'libs') - //NB: Downgrade jackson due to bug in 2.15 releases that makes it incompatible with Gradle 8 - //noinspection UseTomlInstead,GradleDependency - implementation platform('com.fasterxml.jackson:jackson-bom:2.13.5') //Don't upgrade from 2.13.5 due to Android API24 compatibility + + //noinspection BomWithoutPlatform,GradleDependency + implementation platform('com.fasterxml.jackson:jackson-bom:2.13.5') //Do not upgrade! 2.13.5 is latest library with Android API24 compatibility implementation 'com.fasterxml.jackson.core:jackson-core' implementation 'com.fasterxml.jackson.core:jackson-databind' @@ -282,8 +285,8 @@ dependencies { //Timber implementation libs.timber - //noinspection UseTomlInstead - implementation platform('com.walletconnect:android-bom:1.13.1') + //noinspection UseTomlInstead,GradleDependency + implementation platform('com.walletconnect:android-bom:1.13.1') //TODO: Upgrade implementation("com.walletconnect:android-core", { exclude group: 'org.web3j', module: '*' }) diff --git a/app/src/main/java/com/alphawallet/app/entity/SuggestEIP1559.kt b/app/src/main/java/com/alphawallet/app/entity/SuggestEIP1559.kt index 299e9de7af..f03d1e145b 100644 --- a/app/src/main/java/com/alphawallet/app/entity/SuggestEIP1559.kt +++ b/app/src/main/java/com/alphawallet/app/entity/SuggestEIP1559.kt @@ -29,7 +29,7 @@ private const val extraPriorityFeeRatio = 0.25 // extra priority fee offere private const val fallbackPriorityFee = 2000000000L // priority fee offered when there are no recent transactions private const val MIN_PRIORITY_FEE = 100000000L // Minimum priority fee in Wei, 0.1 Gwei -fun SuggestEIP1559(gasService: GasService, feeHistory: FeeHistory): Single> { +fun suggestEIP1559(gasService: GasService, feeHistory: FeeHistory): Single> { return suggestPriorityFee(parseLong(feeHistory.oldestBlock.removePrefix("0x"), 16), feeHistory, gasService) .flatMap { priorityFee -> calculateResult(priorityFee, feeHistory) } } @@ -74,10 +74,10 @@ private fun calculateResult(priorityFee: BigInteger, feeHistory: FeeHistory): Si val result = mutableMapOf() for (timeFactor in maxTimeFactor downTo 0) { var bf: BigInteger - if (timeFactor < 1e-6) { - bf = baseFee.last() + bf = if (timeFactor < 1e-6) { + baseFee.last() } else { - bf = predictMinBaseFee(baseFee, order, timeFactor.toDouble(), consistentBaseFee) + predictMinBaseFee(baseFee, order, timeFactor.toDouble(), consistentBaseFee) } var t = BigDecimal(usePriorityFee) if (bf > maxBaseFee) { diff --git a/app/src/main/java/com/alphawallet/app/service/GasService.java b/app/src/main/java/com/alphawallet/app/service/GasService.java index 3ffd5c79f4..db891d9ab7 100644 --- a/app/src/main/java/com/alphawallet/app/service/GasService.java +++ b/app/src/main/java/com/alphawallet/app/service/GasService.java @@ -480,7 +480,7 @@ private Single> useCalculationIfRequired(Ma private Single> getEIP1559FeeStructureCalculation() { return getChainFeeHistory(100, "latest", "") - .flatMap(feeHistory -> SuggestEIP1559Kt.SuggestEIP1559(this, feeHistory)); + .flatMap(feeHistory -> SuggestEIP1559Kt.suggestEIP1559(this, feeHistory)); } private void handleError(Throwable err) diff --git a/app/src/main/java/com/alphawallet/app/ui/SearchActivity.java b/app/src/main/java/com/alphawallet/app/ui/SearchActivity.java index 19916e7a69..31d52c4409 100644 --- a/app/src/main/java/com/alphawallet/app/ui/SearchActivity.java +++ b/app/src/main/java/com/alphawallet/app/ui/SearchActivity.java @@ -22,9 +22,7 @@ import com.alphawallet.app.ui.widget.TokensAdapterCallback; import com.alphawallet.app.ui.widget.adapter.TokensAdapter; import com.alphawallet.app.ui.widget.entity.SearchToolbarCallback; -import com.alphawallet.app.util.Utils; import com.alphawallet.app.viewmodel.WalletViewModel; -import com.alphawallet.app.widget.AWalletAlertDialog; import com.alphawallet.app.widget.SearchToolbar; import org.web3j.crypto.WalletUtils; diff --git a/app/src/main/java/com/alphawallet/app/ui/WalletConnectSessionActivity.java b/app/src/main/java/com/alphawallet/app/ui/WalletConnectSessionActivity.java index b17caf3a50..9506215d4f 100644 --- a/app/src/main/java/com/alphawallet/app/ui/WalletConnectSessionActivity.java +++ b/app/src/main/java/com/alphawallet/app/ui/WalletConnectSessionActivity.java @@ -269,7 +269,7 @@ public CustomAdapter.CustomViewHolder onCreateViewHolder(@NonNull ViewGroup pare View itemView = LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_wc_session, parent, false); - return new CustomAdapter.CustomViewHolder(itemView); + return new CustomViewHolder(itemView); } @Override @@ -317,7 +317,7 @@ public void updateList(List list) notifyDataSetChanged(); } - class CustomViewHolder extends RecyclerView.ViewHolder + static class CustomViewHolder extends RecyclerView.ViewHolder { final ImageView icon; final ImageView statusIcon; diff --git a/app/src/main/java/com/alphawallet/app/ui/WalletFragment.java b/app/src/main/java/com/alphawallet/app/ui/WalletFragment.java index 95e5adb331..520238d1f3 100644 --- a/app/src/main/java/com/alphawallet/app/ui/WalletFragment.java +++ b/app/src/main/java/com/alphawallet/app/ui/WalletFragment.java @@ -386,7 +386,6 @@ private void refreshList() adapter.clear(); viewModel.prepare(); viewModel.notifyRefresh(); - //awWalletConnectClient.updateNotification(); }); } @@ -407,6 +406,7 @@ public void comeIntoFocus() viewModel.startUpdateListener(); viewModel.getTokensService().startUpdateCycleIfRequired(); } + checkWalletConnect(); } @Override @@ -566,6 +566,7 @@ public void onResume() if (viewModel == null) { requireActivity().recreate(); + return; } else { @@ -581,6 +582,16 @@ public void onResume() viewModel.startUpdateListener(); viewModel.getTokensService().startUpdateCycleIfRequired(); } + + checkWalletConnect(); + } + + private void checkWalletConnect() + { + if (adapter != null) + { + adapter.checkWalletConnect(); + } } private void onTokens(TokenCardMeta[] tokens) @@ -595,7 +606,7 @@ private void onTokens(TokenCardMeta[] tokens) if (currentTabPos.equals(TokenFilter.ALL)) { - //awWalletConnectClient.updateNotification(); + checkWalletConnect(); } else { @@ -807,6 +818,19 @@ public void onSearchClicked() //startActivity(intent); } + @Override + public void onWCClicked() + { + Intent intent = awWalletConnectClient.getSessionIntent(getContext()); + startActivity(intent); + } + + @Override + public boolean hasWCSession() + { + return awWalletConnectClient != null && awWalletConnectClient.hasWalletConnectSessions(); + } + @Override public void onSwitchClicked() { diff --git a/app/src/main/java/com/alphawallet/app/ui/widget/TokensAdapterCallback.java b/app/src/main/java/com/alphawallet/app/ui/widget/TokensAdapterCallback.java index f95b76b696..a858e0d8b4 100644 --- a/app/src/main/java/com/alphawallet/app/ui/widget/TokensAdapterCallback.java +++ b/app/src/main/java/com/alphawallet/app/ui/widget/TokensAdapterCallback.java @@ -14,5 +14,7 @@ public interface TokensAdapterCallback default void reloadTokens() { }; default void onBuyToken() { } default void onSearchClicked() { }; - default void onSwitchClicked() {}; + default void onSwitchClicked() { }; + default void onWCClicked() { }; + default boolean hasWCSession() { return false; }; } diff --git a/app/src/main/java/com/alphawallet/app/ui/widget/adapter/TokensAdapter.java b/app/src/main/java/com/alphawallet/app/ui/widget/adapter/TokensAdapter.java index 183a849b5d..391544f0a0 100644 --- a/app/src/main/java/com/alphawallet/app/ui/widget/adapter/TokensAdapter.java +++ b/app/src/main/java/com/alphawallet/app/ui/widget/adapter/TokensAdapter.java @@ -13,11 +13,9 @@ import com.alphawallet.app.entity.CustomViewSettings; import com.alphawallet.app.entity.TokenFilter; import com.alphawallet.app.entity.tokendata.TokenGroup; -import com.alphawallet.app.entity.tokens.Attestation; import com.alphawallet.app.entity.tokens.Token; import com.alphawallet.app.entity.tokens.TokenCardMeta; import com.alphawallet.app.entity.walletconnect.WalletConnectSessionItem; -import com.alphawallet.app.repository.TokensRealmSource; import com.alphawallet.app.service.AssetDefinitionService; import com.alphawallet.app.service.TokensService; import com.alphawallet.app.ui.widget.TokensAdapterCallback; @@ -169,7 +167,7 @@ public BinderViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int vie break; case SearchTokensHolder.VIEW_TYPE: - holder = new SearchTokensHolder(R.layout.layout_manage_token_search, parent, tokensAdapterCallback::onSearchClicked); + holder = new SearchTokensHolder(R.layout.layout_manage_token_search, parent, tokensAdapterCallback); break; case WarningHolder.VIEW_TYPE: @@ -301,11 +299,11 @@ public void updateToken(TokenCardMeta token) } else { - SortedItem headerItem = new HeaderItem(token.group); + SortedItem headerItem = new HeaderItem(token.group); items.add(tsi); items.add(headerItem); - SortedItem chainItem = new ChainItem(token.getChain(), token.group); + SortedItem chainItem = new ChainItem(token.getChain(), token.group); if (doesNotExist(chainItem)) { items.add(chainItem); @@ -318,12 +316,12 @@ public void updateToken(TokenCardMeta token) } } - private boolean doesNotExist(SortedItem token) + private boolean doesNotExist(SortedItem token) { return findItem(token) == -1; } - private int findItem(SortedItem tsi) + private int findItem(SortedItem tsi) { for (int i = 0; i < items.size(); i++) { @@ -339,9 +337,8 @@ private void removeMatchingTokenDifferentWeight(TokenCardMeta token) { for (int i = 0; i < items.size(); i++) { - if (items.get(i) instanceof TokenSortedItem) + if (items.get(i) instanceof TokenSortedItem tsi) { - TokenSortedItem tsi = (TokenSortedItem) items.get(i); if (tsi.value.equals(token)) { if (tsi.value.getNameWeight() != token.getNameWeight()) @@ -523,9 +520,8 @@ private void filterAdapterItems() for (int i = 0; i < items.size(); i++) { Object si = items.get(i); - if (si instanceof TokenSortedItem) + if (si instanceof TokenSortedItem tsi) { - TokenSortedItem tsi = (TokenSortedItem) si; if (canDisplayToken(tsi.value)) { filterTokens.add(tsi.value); @@ -567,9 +563,8 @@ public int getScrollPosition() for (int i = 0; i < items.size(); i++) { Object si = items.get(i); - if (si instanceof TokenSortedItem) + if (si instanceof TokenSortedItem tsi) { - TokenSortedItem tsi = (TokenSortedItem) si; TokenCardMeta token = tsi.value; if (scrollToken.equals(token)) { @@ -654,4 +649,26 @@ public void addToken(SortedItem token) { items.add(token); } + + public void checkWalletConnect() + { + //activate WC logo in search bar if we have active WC sessions + for (int i = 0; i < items.size(); i++) + { + Object si = items.get(i); + if (si instanceof ManageTokensSearchItem manageTokensSearchItem && manageTokensSearchItem.view instanceof SearchTokensHolder sth) + { + if (tokensAdapterCallback.hasWCSession()) + { + sth.enableWalletConnect(); + } + else + { + sth.hideWalletConnect(); + } + + break; + } + } + } } diff --git a/app/src/main/java/com/alphawallet/app/ui/widget/holder/SearchTokensHolder.java b/app/src/main/java/com/alphawallet/app/ui/widget/holder/SearchTokensHolder.java index 65ce0de7de..5545360c2b 100644 --- a/app/src/main/java/com/alphawallet/app/ui/widget/holder/SearchTokensHolder.java +++ b/app/src/main/java/com/alphawallet/app/ui/widget/holder/SearchTokensHolder.java @@ -4,11 +4,13 @@ import android.view.View; import android.view.ViewGroup; import android.widget.EditText; +import android.widget.ImageView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.alphawallet.app.R; +import com.alphawallet.app.ui.widget.TokensAdapterCallback; import com.alphawallet.app.ui.widget.entity.ManageTokensData; public class SearchTokensHolder extends BinderViewHolder { @@ -20,7 +22,9 @@ public interface SearchHandler { final EditText editSearch; final SearchHandler searchHandler; + final SearchHandler onWCClicked; final View searchTokenClick; + final ImageView walletConnect; String wallet; @Override @@ -38,11 +42,32 @@ public void bind(@Nullable ManageTokensData data, @NonNull Bundle addition) { }); } - public SearchTokensHolder(int res_id, ViewGroup parent, SearchHandler handler) { + public SearchTokensHolder(int res_id, ViewGroup parent, TokensAdapterCallback tCallback) { super(res_id, parent); this.editSearch = findViewById(R.id.edit_search); - this.searchHandler = handler; + this.searchHandler = tCallback::onSearchClicked; this.searchTokenClick = findViewById(R.id.click_layer); + this.walletConnect = findViewById(R.id.icon_wc_active); this.wallet = null; + this.onWCClicked = tCallback::onWCClicked; + + if (tCallback.hasWCSession()) + { + enableWalletConnect(); + } + } + + public void enableWalletConnect() + { + walletConnect.setVisibility(View.VISIBLE); + walletConnect.setOnClickListener(v -> { + if (onWCClicked != null) onWCClicked.onFocus(); + }); + } + + public void hideWalletConnect() + { + walletConnect.setVisibility(View.GONE); + walletConnect.setOnClickListener(null); } } diff --git a/app/src/main/java/com/alphawallet/app/walletconnect/AWWalletConnectClient.java b/app/src/main/java/com/alphawallet/app/walletconnect/AWWalletConnectClient.java index d72761661a..e30689d8d3 100644 --- a/app/src/main/java/com/alphawallet/app/walletconnect/AWWalletConnectClient.java +++ b/app/src/main/java/com/alphawallet/app/walletconnect/AWWalletConnectClient.java @@ -33,6 +33,7 @@ import com.alphawallet.app.repository.PreferenceRepositoryType; import com.alphawallet.app.service.GasService; import com.alphawallet.app.service.WalletConnectV2Service; +import com.alphawallet.app.ui.WalletConnectSessionActivity; import com.alphawallet.app.ui.WalletConnectV2Activity; import com.alphawallet.app.ui.widget.entity.ActionSheetCallback; import com.alphawallet.app.walletconnect.util.WCMethodChecker; @@ -43,7 +44,6 @@ import com.alphawallet.token.entity.EthereumMessage; import com.alphawallet.token.entity.SignMessageType; import com.alphawallet.token.entity.Signable; -import org.web3j.utils.Numeric; import com.walletconnect.android.Core; import com.walletconnect.android.CoreClient; import com.walletconnect.android.cacao.signature.SignatureType; @@ -53,6 +53,8 @@ import com.walletconnect.web3.wallet.client.Wallet.Model.Session; import com.walletconnect.web3.wallet.client.Web3Wallet; +import org.web3j.utils.Numeric; + import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -111,6 +113,11 @@ public void onSessionProposal(@NonNull Model.SessionProposal sessionProposal) context.startActivity(intent); } + public boolean hasWalletConnectSessions() + { + return !walletConnectInteract.getSessions().isEmpty(); + } + private boolean validChainId(List chains) { for (String chainId : chains) @@ -663,6 +670,22 @@ public void onSessionDelete(@NonNull Model.SessionDelete sessionDelete) } + public Intent getSessionIntent(Context appContext) + { + Intent intent; + List sessions = walletConnectInteract.getSessions(); + if (sessions.size() == 1) + { + intent = WalletConnectSessionActivity.newIntent(appContext, sessions.get(0)); + } + else + { + intent = new Intent(appContext, WalletConnectSessionActivity.class); + } + + return intent; + } + public interface WalletConnectV2Callback { default void onSessionProposalApproved() diff --git a/app/src/main/res/drawable/ic_wallet_connect.xml b/app/src/main/res/drawable/ic_wallet_connect.xml index 8d0ff691cc..b477ea3a18 100644 --- a/app/src/main/res/drawable/ic_wallet_connect.xml +++ b/app/src/main/res/drawable/ic_wallet_connect.xml @@ -1,18 +1,13 @@ - - - - + + + + + + + + + + + + diff --git a/app/src/main/res/layout/layout_manage_token_search.xml b/app/src/main/res/layout/layout_manage_token_search.xml index 5158de135f..bd63b0cbbe 100644 --- a/app/src/main/res/layout/layout_manage_token_search.xml +++ b/app/src/main/res/layout/layout_manage_token_search.xml @@ -1,52 +1,69 @@ - - + + + + android:id="@+id/layout_search_tokens" + android:layout_width="0dp" + android:layout_height="@dimen/massive_44" + android:layout_marginStart="@dimen/tiny_8" + android:layout_marginTop="@dimen/tiny_8" + android:layout_marginEnd="@dimen/tiny_8" + android:layout_marginBottom="@dimen/tiny_8" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toStartOf="@+id/icon_wc_active" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + android:background="@drawable/background_round_search" + android:gravity="center_vertical" + android:orientation="horizontal"> + android:layout_width="@dimen/base_24" + android:layout_height="@dimen/base_24" + android:layout_marginStart="@dimen/small_12" + android:src="@drawable/ic_search_small" + app:tint="?android:textColorSecondary" /> + android:id="@+id/edit_search" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1" + android:layout_marginStart="@dimen/tiny_8" + android:layout_marginEnd="@dimen/standard_16" + android:background="@null" + android:clickable="false" + android:focusable="false" + android:fontFamily="@font/font_regular" + android:hint="@string/search_for_tokens" + android:imeOptions="actionSearch" + android:maxLength="20" + android:maxLines="1" /> + android:id="@+id/click_layer" + android:layout_width="0dp" + android:layout_height="@dimen/optimal_30" + app:layout_constraintStart_toStartOf="@+id/layout_search_tokens" + app:layout_constraintEnd_toEndOf="@+id/layout_search_tokens" + app:layout_constraintTop_toTopOf="@+id/layout_search_tokens" + app:layout_constraintBottom_toBottomOf="@+id/layout_search_tokens" + android:background="@color/transparent" /> - \ No newline at end of file +