From e2ac63dd708d9f7d8d54d5a4412cb37a5db89aa6 Mon Sep 17 00:00:00 2001 From: Denis Rodionov Date: Fri, 10 May 2024 19:52:33 +0700 Subject: [PATCH 1/3] Sample Android app on Kotlin Features: Mainnet and Testnet Check Eth balance Deposit Eth --- android/.gitignore | 15 ++ android/app/.gitignore | 1 + android/app/build.gradle.kts | 81 ++++++++ android/app/proguard-rules.pro | 37 ++++ android/app/src/main/AndroidManifest.xml | 29 +++ .../app/src/main/ic_launcher-playstore.png | Bin 0 -> 14835 bytes .../main/java/com/zk/android/app/ZkSynApp.kt | 7 + .../java/com/zk/android/app/di/ZkModule.kt | 48 +++++ .../app/domain/balance/BalanceModelDomain.kt | 9 + .../app/domain/balance/GetBalanceUseCase.kt | 35 ++++ .../app/domain/deposit/DepositModelDomain.kt | 8 + .../app/domain/deposit/DepositUseCase.kt | 37 ++++ .../zk/android/app/network/WalletProvider.kt | 22 +++ .../com/zk/android/app/network/ZkConfig.kt | 9 + .../android/app/presentation/MainActivity.kt | 27 +++ .../presentation/transfer/TransferFragment.kt | 95 +++++++++ .../presentation/transfer/TransferState.kt | 40 ++++ .../transfer/TransferViewModel.kt | 75 +++++++ .../transfer/model/BalanceModel.kt | 10 + .../transfer/model/ModelMapper.kt | 19 ++ .../transfer/widget/FromWalletView.kt | 35 ++++ .../transfer/widget/FromWalletViewModel.kt | 6 + .../transfer/widget/ToWalletView.kt | 29 +++ .../transfer/widget/ToWalletViewModel.kt | 6 + .../java/com/zk/android/app/utils/EthExt.kt | 27 +++ .../com/zk/android/app/utils/FragmentExt.kt | 78 ++++++++ .../java/com/zk/android/app/utils/ViewExt.kt | 12 ++ .../res/animator/views_animation_selector.xml | 20 ++ .../drawable-nodpi/img_splash_brand_white.png | Bin 0 -> 9859 bytes .../src/main/res/drawable/bg_gray_content.xml | 5 + .../src/main/res/drawable/bg_gray_overlay.xml | 5 + .../app/src/main/res/drawable/ic_ethereum.xml | 17 ++ .../app/src/main/res/drawable/img_zk_logo.png | Bin 0 -> 2604 bytes .../app/src/main/res/layout/activity_main.xml | 23 +++ .../src/main/res/layout/fragment_transfer.xml | 132 +++++++++++++ .../src/main/res/layout/view_from_balance.xml | 72 +++++++ .../src/main/res/layout/view_to_balance.xml | 58 ++++++ .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + .../src/main/res/mipmap-hdpi/ic_launcher.webp | Bin 0 -> 962 bytes .../mipmap-hdpi/ic_launcher_foreground.webp | Bin 0 -> 716 bytes .../res/mipmap-hdpi/ic_launcher_round.webp | Bin 0 -> 2426 bytes .../src/main/res/mipmap-mdpi/ic_launcher.webp | Bin 0 -> 758 bytes .../mipmap-mdpi/ic_launcher_foreground.webp | Bin 0 -> 488 bytes .../res/mipmap-mdpi/ic_launcher_round.webp | Bin 0 -> 1560 bytes .../main/res/mipmap-xhdpi/ic_launcher.webp | Bin 0 -> 1268 bytes .../mipmap-xhdpi/ic_launcher_foreground.webp | Bin 0 -> 1010 bytes .../res/mipmap-xhdpi/ic_launcher_round.webp | Bin 0 -> 3388 bytes .../main/res/mipmap-xxhdpi/ic_launcher.webp | Bin 0 -> 1846 bytes .../mipmap-xxhdpi/ic_launcher_foreground.webp | Bin 0 -> 1568 bytes .../res/mipmap-xxhdpi/ic_launcher_round.webp | Bin 0 -> 5392 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.webp | Bin 0 -> 2580 bytes .../ic_launcher_foreground.webp | Bin 0 -> 1734 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.webp | Bin 0 -> 7752 bytes .../app/src/main/res/navigation/nav_graph.xml | 12 ++ .../main/res/raw/lottie_connection_error.json | 1 + .../app/src/main/res/raw/lottie_loading.json | 1 + .../app/src/main/res/raw/lottie_sending.json | 1 + .../app/src/main/res/values-v27/themes.xml | 7 + .../app/src/main/res/values-v31/themes.xml | 6 + android/app/src/main/res/values/buttons.xml | 18 ++ android/app/src/main/res/values/colors.xml | 13 ++ .../res/values/ic_launcher_background.xml | 4 + android/app/src/main/res/values/strings.xml | 18 ++ android/app/src/main/res/values/themes.xml | 24 +++ .../app/src/main/res/values/typography.xml | 26 +++ android/app/src/main/res/xml/backup_rules.xml | 13 ++ .../main/res/xml/data_extraction_rules.xml | 19 ++ android/build.gradle.kts | 7 + android/gradle.properties | 23 +++ android/gradle/libs.versions.toml | 49 +++++ android/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 59203 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 + android/gradlew | 185 ++++++++++++++++++ android/gradlew.bat | 89 +++++++++ android/settings.gradle.kts | 24 +++ 76 files changed, 1685 insertions(+) create mode 100644 android/.gitignore create mode 100644 android/app/.gitignore create mode 100644 android/app/build.gradle.kts create mode 100644 android/app/proguard-rules.pro create mode 100644 android/app/src/main/AndroidManifest.xml create mode 100644 android/app/src/main/ic_launcher-playstore.png create mode 100644 android/app/src/main/java/com/zk/android/app/ZkSynApp.kt create mode 100644 android/app/src/main/java/com/zk/android/app/di/ZkModule.kt create mode 100644 android/app/src/main/java/com/zk/android/app/domain/balance/BalanceModelDomain.kt create mode 100644 android/app/src/main/java/com/zk/android/app/domain/balance/GetBalanceUseCase.kt create mode 100644 android/app/src/main/java/com/zk/android/app/domain/deposit/DepositModelDomain.kt create mode 100644 android/app/src/main/java/com/zk/android/app/domain/deposit/DepositUseCase.kt create mode 100644 android/app/src/main/java/com/zk/android/app/network/WalletProvider.kt create mode 100644 android/app/src/main/java/com/zk/android/app/network/ZkConfig.kt create mode 100644 android/app/src/main/java/com/zk/android/app/presentation/MainActivity.kt create mode 100644 android/app/src/main/java/com/zk/android/app/presentation/transfer/TransferFragment.kt create mode 100644 android/app/src/main/java/com/zk/android/app/presentation/transfer/TransferState.kt create mode 100644 android/app/src/main/java/com/zk/android/app/presentation/transfer/TransferViewModel.kt create mode 100644 android/app/src/main/java/com/zk/android/app/presentation/transfer/model/BalanceModel.kt create mode 100644 android/app/src/main/java/com/zk/android/app/presentation/transfer/model/ModelMapper.kt create mode 100644 android/app/src/main/java/com/zk/android/app/presentation/transfer/widget/FromWalletView.kt create mode 100644 android/app/src/main/java/com/zk/android/app/presentation/transfer/widget/FromWalletViewModel.kt create mode 100644 android/app/src/main/java/com/zk/android/app/presentation/transfer/widget/ToWalletView.kt create mode 100644 android/app/src/main/java/com/zk/android/app/presentation/transfer/widget/ToWalletViewModel.kt create mode 100644 android/app/src/main/java/com/zk/android/app/utils/EthExt.kt create mode 100644 android/app/src/main/java/com/zk/android/app/utils/FragmentExt.kt create mode 100644 android/app/src/main/java/com/zk/android/app/utils/ViewExt.kt create mode 100644 android/app/src/main/res/animator/views_animation_selector.xml create mode 100644 android/app/src/main/res/drawable-nodpi/img_splash_brand_white.png create mode 100644 android/app/src/main/res/drawable/bg_gray_content.xml create mode 100644 android/app/src/main/res/drawable/bg_gray_overlay.xml create mode 100644 android/app/src/main/res/drawable/ic_ethereum.xml create mode 100644 android/app/src/main/res/drawable/img_zk_logo.png create mode 100644 android/app/src/main/res/layout/activity_main.xml create mode 100644 android/app/src/main/res/layout/fragment_transfer.xml create mode 100644 android/app/src/main/res/layout/view_from_balance.xml create mode 100644 android/app/src/main/res/layout/view_to_balance.xml create mode 100644 android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 android/app/src/main/res/mipmap-hdpi/ic_launcher.webp create mode 100644 android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp create mode 100644 android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp create mode 100644 android/app/src/main/res/mipmap-mdpi/ic_launcher.webp create mode 100644 android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp create mode 100644 android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp create mode 100644 android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp create mode 100644 android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp create mode 100644 android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp create mode 100644 android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp create mode 100644 android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp create mode 100644 android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp create mode 100644 android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp create mode 100644 android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp create mode 100644 android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp create mode 100644 android/app/src/main/res/navigation/nav_graph.xml create mode 100644 android/app/src/main/res/raw/lottie_connection_error.json create mode 100644 android/app/src/main/res/raw/lottie_loading.json create mode 100644 android/app/src/main/res/raw/lottie_sending.json create mode 100644 android/app/src/main/res/values-v27/themes.xml create mode 100644 android/app/src/main/res/values-v31/themes.xml create mode 100644 android/app/src/main/res/values/buttons.xml create mode 100644 android/app/src/main/res/values/colors.xml create mode 100644 android/app/src/main/res/values/ic_launcher_background.xml create mode 100644 android/app/src/main/res/values/strings.xml create mode 100644 android/app/src/main/res/values/themes.xml create mode 100644 android/app/src/main/res/values/typography.xml create mode 100644 android/app/src/main/res/xml/backup_rules.xml create mode 100644 android/app/src/main/res/xml/data_extraction_rules.xml create mode 100644 android/build.gradle.kts create mode 100644 android/gradle.properties create mode 100644 android/gradle/libs.versions.toml create mode 100644 android/gradle/wrapper/gradle-wrapper.jar create mode 100644 android/gradle/wrapper/gradle-wrapper.properties create mode 100644 android/gradlew create mode 100644 android/gradlew.bat create mode 100644 android/settings.gradle.kts diff --git a/android/.gitignore b/android/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/android/.gitignore @@ -0,0 +1,15 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/android/app/.gitignore b/android/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/android/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts new file mode 100644 index 0000000..eb46507 --- /dev/null +++ b/android/app/build.gradle.kts @@ -0,0 +1,81 @@ +plugins { + alias(libs.plugins.androidApplication) + alias(libs.plugins.jetbrainsKotlinAndroid) + alias(libs.plugins.daggerHilt) + alias(libs.plugins.kotlinKapt) +} + +android { + namespace = "com.zk.android.app" + compileSdk = 34 + + defaultConfig { + applicationId = "com.zk.android.app" + minSdk = 24 + targetSdk = 33 + versionCode = 1 + versionName = "1.0" + //TODO Insert your key here + //buildConfigField("String", "PRIVATE_KEY", "\"\"") + } + flavorDimensions.add("default") + + productFlavors { + create("mainnet") { + applicationIdSuffix = ".debug" + buildConfigField("String", "RPC_ETH_URL", "\"https://rpc.ankr.com/eth\"") + buildConfigField("String", "ZK_ERA_URL", "\"https://mainnet.era.zksync.io\"") + buildConfigField("String", "ZK_ERA_SOCKET", "\"wss://mainnet.era.zksync.io/ws\"") + } + create("sepolia") { + buildConfigField("String", "RPC_ETH_URL", "\"https://rpc.ankr.com/eth_sepolia\"") + buildConfigField("String", "ZK_ERA_URL", "\"https://sepolia.era.zksync.dev\"") + buildConfigField("String", "ZK_ERA_SOCKET", "\"wss://sepolia.era.zksync.dev/ws\"") + } + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + } + } + /*compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + }*/ + kotlinOptions { + jvmTarget = "1.8" + } + buildFeatures { + viewBinding = true + buildConfig = true + } +} + +dependencies { + + //Zk + implementation(libs.zksync) + implementation(libs.web3j.core) + implementation(libs.web3j.crypto) + + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.core.splashscreen) + implementation(libs.androidx.appcompat) + implementation(libs.androidx.constraintlayout) + implementation(libs.androidx.navigation.fragment.ktx) + implementation(libs.androidx.navigation.ui.ktx) + implementation(libs.lifecycle.viewmodel.ktx) + implementation(libs.lifecycle.runtime.ktx) + + implementation(libs.material) + implementation(libs.orbit.viewmodel) + implementation(libs.lottie) + + // Dependency injection + implementation(libs.hilt.android) + kapt(libs.hilt.compiler) + kapt(libs.androidx.hilt.compiler) + implementation(libs.hilt.navigation.fragment) +} \ No newline at end of file diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro new file mode 100644 index 0000000..6ca1e84 --- /dev/null +++ b/android/app/proguard-rules.pro @@ -0,0 +1,37 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile + +# JSR 305 annotations are for embedding nullability information. +-dontwarn javax.annotation.** + +# A resource is loaded with a relative path so the package of this class must be preserved. +-keeppackagenames okhttp3.internal.publicsuffix.* +-adaptresourcefilenames okhttp3/internal/publicsuffix/PublicSuffixDatabase.gz + +# Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java. +-dontwarn org.codehaus.mojo.animal_sniffer.* + +# OkHttp platform used only on JVM and when Conscrypt and other security providers are available. +-dontwarn okhttp3.internal.platform.** +-dontwarn org.conscrypt.** +-dontwarn org.bouncycastle.** +-dontwarn org.openjsse.** \ No newline at end of file diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..002840e --- /dev/null +++ b/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/ic_launcher-playstore.png b/android/app/src/main/ic_launcher-playstore.png new file mode 100644 index 0000000000000000000000000000000000000000..7c8170ea889e8e0f690b3a3026fbd62bcb8cdb45 GIT binary patch literal 14835 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 literal 0 HcmV?d00001 diff --git a/android/app/src/main/java/com/zk/android/app/ZkSynApp.kt b/android/app/src/main/java/com/zk/android/app/ZkSynApp.kt new file mode 100644 index 0000000..00df95b --- /dev/null +++ b/android/app/src/main/java/com/zk/android/app/ZkSynApp.kt @@ -0,0 +1,7 @@ +package com.zk.android.app + +import android.app.Application +import dagger.hilt.android.HiltAndroidApp + +@HiltAndroidApp +class ZkSynApp : Application() \ No newline at end of file diff --git a/android/app/src/main/java/com/zk/android/app/di/ZkModule.kt b/android/app/src/main/java/com/zk/android/app/di/ZkModule.kt new file mode 100644 index 0000000..4531a35 --- /dev/null +++ b/android/app/src/main/java/com/zk/android/app/di/ZkModule.kt @@ -0,0 +1,48 @@ +package com.zk.android.app.di + +import com.zk.android.app.BuildConfig +import com.zk.android.app.network.ZkConfig +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import io.zksync.protocol.ZkSync +import org.web3j.crypto.Credentials +import org.web3j.protocol.Web3j +import org.web3j.protocol.http.HttpService +import javax.inject.Singleton + + +@Module +@InstallIn(SingletonComponent::class) +class ZkModule { + + @Provides + @Singleton + fun provideConfig(): ZkConfig { + return ZkConfig( + BuildConfig.RPC_ETH_URL, + BuildConfig.ZK_ERA_URL, + BuildConfig.ZK_ERA_SOCKET, + BuildConfig.PRIVATE_KEY + ) + } + + @Provides + @Singleton + fun provideWeb3j(config: ZkConfig): Web3j { + return Web3j.build(HttpService(config.rpc)) + } + + @Provides + @Singleton + fun provideZkSync(config: ZkConfig): ZkSync { + return ZkSync.build(HttpService(config.url)) + } + + @Provides + @Singleton + fun provideCredentials(config: ZkConfig): Credentials { + return Credentials.create(config.privateKey) + } +} \ No newline at end of file diff --git a/android/app/src/main/java/com/zk/android/app/domain/balance/BalanceModelDomain.kt b/android/app/src/main/java/com/zk/android/app/domain/balance/BalanceModelDomain.kt new file mode 100644 index 0000000..0c9519e --- /dev/null +++ b/android/app/src/main/java/com/zk/android/app/domain/balance/BalanceModelDomain.kt @@ -0,0 +1,9 @@ +package com.zk.android.app.domain.balance + +import java.math.BigInteger + + +data class BalanceModelDomain ( + val l1Balance: BigInteger, + val l2Balance: BigInteger, +) \ No newline at end of file diff --git a/android/app/src/main/java/com/zk/android/app/domain/balance/GetBalanceUseCase.kt b/android/app/src/main/java/com/zk/android/app/domain/balance/GetBalanceUseCase.kt new file mode 100644 index 0000000..11b8634 --- /dev/null +++ b/android/app/src/main/java/com/zk/android/app/domain/balance/GetBalanceUseCase.kt @@ -0,0 +1,35 @@ +package com.zk.android.app.domain.balance + +import com.zk.android.app.network.WalletProvider +import com.zk.android.app.utils.sendSafe +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.withContext +import java.math.BigInteger +import javax.inject.Inject + + +class GetBalanceUseCase @Inject constructor( + private val walletProvider: WalletProvider +) { + + suspend fun getBalance(): Result = withContext(Dispatchers.IO) { + val wallet = walletProvider.create().getOrElse { + return@withContext Result.failure(it) + } + + val l1Balance: BigInteger = wallet.balanceL1.sendSafe().getOrElse { + return@withContext Result.failure(it) + } + val l2Balance: BigInteger = wallet.balance.sendSafe().getOrElse { + return@withContext Result.failure(it) + } + + return@withContext Result.success( + BalanceModelDomain( + l1Balance, + l2Balance + ) + ) + } +} \ No newline at end of file diff --git a/android/app/src/main/java/com/zk/android/app/domain/deposit/DepositModelDomain.kt b/android/app/src/main/java/com/zk/android/app/domain/deposit/DepositModelDomain.kt new file mode 100644 index 0000000..2e71914 --- /dev/null +++ b/android/app/src/main/java/com/zk/android/app/domain/deposit/DepositModelDomain.kt @@ -0,0 +1,8 @@ +package com.zk.android.app.domain.deposit + +import com.zk.android.app.domain.balance.BalanceModelDomain + + +data class DepositModelDomain ( + val newBalance: BalanceModelDomain +) \ No newline at end of file diff --git a/android/app/src/main/java/com/zk/android/app/domain/deposit/DepositUseCase.kt b/android/app/src/main/java/com/zk/android/app/domain/deposit/DepositUseCase.kt new file mode 100644 index 0000000..ee5f596 --- /dev/null +++ b/android/app/src/main/java/com/zk/android/app/domain/deposit/DepositUseCase.kt @@ -0,0 +1,37 @@ +package com.zk.android.app.domain.deposit + +import com.zk.android.app.domain.balance.GetBalanceUseCase +import com.zk.android.app.network.WalletProvider +import com.zk.android.app.utils.toWei +import com.zk.android.app.utils.waitForTransactionReceiptSafe +import io.zksync.transaction.type.DepositTransaction +import io.zksync.utils.ZkSyncAddresses +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import java.math.BigInteger +import javax.inject.Inject + + +class DepositUseCase @Inject constructor( + private val walletProvider: WalletProvider, + private val balanceUseCase: GetBalanceUseCase +) { + + suspend fun deposit(amount: Float): Result = withContext(Dispatchers.IO) { + val wallet = walletProvider.create().getOrElse { + return@withContext Result.failure(it) + } + + val sendAmount: BigInteger = amount.toWei() + + val transaction = DepositTransaction(ZkSyncAddresses.ETH_ADDRESS, sendAmount) + val hash = wallet.deposit(transaction).sendAsync().join().result + wallet.transactionReceiptProcessorL1.waitForTransactionReceiptSafe(hash) + + val newBalance = balanceUseCase.getBalance().getOrElse { + return@withContext Result.failure(it) + } + + return@withContext Result.success(DepositModelDomain(newBalance)) + } +} \ No newline at end of file diff --git a/android/app/src/main/java/com/zk/android/app/network/WalletProvider.kt b/android/app/src/main/java/com/zk/android/app/network/WalletProvider.kt new file mode 100644 index 0000000..9e3d511 --- /dev/null +++ b/android/app/src/main/java/com/zk/android/app/network/WalletProvider.kt @@ -0,0 +1,22 @@ +package com.zk.android.app.network + +import androidx.annotation.WorkerThread +import io.zksync.protocol.ZkSync +import io.zksync.protocol.account.Wallet +import org.web3j.crypto.Credentials +import org.web3j.protocol.Web3j +import javax.inject.Inject + + +class WalletProvider @Inject constructor( + private val web3: Web3j, + private val zkSync: ZkSync, + private val credentials: Credentials +) { + + @WorkerThread + //TODO can we use single Wallet instance? + fun create(): Result { + return runCatching { Wallet(web3, zkSync, credentials) } + } +} \ No newline at end of file diff --git a/android/app/src/main/java/com/zk/android/app/network/ZkConfig.kt b/android/app/src/main/java/com/zk/android/app/network/ZkConfig.kt new file mode 100644 index 0000000..c210778 --- /dev/null +++ b/android/app/src/main/java/com/zk/android/app/network/ZkConfig.kt @@ -0,0 +1,9 @@ +package com.zk.android.app.network + + +data class ZkConfig( + val rpc: String, + val url: String, + val socket: String, + val privateKey: String +) \ No newline at end of file diff --git a/android/app/src/main/java/com/zk/android/app/presentation/MainActivity.kt b/android/app/src/main/java/com/zk/android/app/presentation/MainActivity.kt new file mode 100644 index 0000000..2cea8a1 --- /dev/null +++ b/android/app/src/main/java/com/zk/android/app/presentation/MainActivity.kt @@ -0,0 +1,27 @@ +package com.zk.android.app.presentation + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.navigation.findNavController +import androidx.navigation.ui.AppBarConfiguration +import com.zk.android.app.R +import com.zk.android.app.databinding.ActivityMainBinding +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class MainActivity : AppCompatActivity() { + + private lateinit var appBarConfiguration: AppBarConfiguration + private lateinit var binding: ActivityMainBinding + + override fun onCreate(savedInstanceState: Bundle?) { + setTheme(R.style.Theme_ZkSyncApp) + super.onCreate(savedInstanceState) + + binding = ActivityMainBinding.inflate(layoutInflater) + setContentView(binding.root) + + val navController = findNavController(R.id.nav_host_fragment_content_main) + appBarConfiguration = AppBarConfiguration(navController.graph) + } +} \ No newline at end of file diff --git a/android/app/src/main/java/com/zk/android/app/presentation/transfer/TransferFragment.kt b/android/app/src/main/java/com/zk/android/app/presentation/transfer/TransferFragment.kt new file mode 100644 index 0000000..5a456b3 --- /dev/null +++ b/android/app/src/main/java/com/zk/android/app/presentation/transfer/TransferFragment.kt @@ -0,0 +1,95 @@ +package com.zk.android.app.presentation.transfer + + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.core.view.isVisible +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import com.zk.android.app.databinding.FragmentTransferBinding +import com.zk.android.app.utils.viewLifecycle +import dagger.hilt.android.AndroidEntryPoint +import org.orbitmvi.orbit.viewmodel.observe + +@AndroidEntryPoint +internal class TransferFragment : Fragment() { + + private val viewModel: TransferViewModel by viewModels() + private var viewBinding: FragmentTransferBinding by viewLifecycle() + + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + viewBinding = FragmentTransferBinding.inflate(inflater, container, false) + return viewBinding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupListeners() + viewModel.observe( + lifecycleOwner = viewLifecycleOwner, + state = ::handleState, + sideEffect = ::handleSideEffect + ) + } + + private fun setupListeners() { + viewBinding.btnTransfer.setOnClickListener { + val amount = viewBinding.balanceFrom.getAmount() + viewModel.obtainAction(Action.ClickSend(amount)) + } + viewBinding.btnConnection.setOnClickListener { viewModel.obtainAction(Action.ClickConnect) } + } + + private fun handleState(state: ViewState) { + applyStateVisibility(state) + when (state) { + is ViewState.Connection -> showConnection(state) + is ViewState.Data -> showData(state) + } + } + + private fun applyStateVisibility(state: ViewState) { + viewBinding.groupConnection.isVisible = state !is ViewState.Data + viewBinding.btnConnection.isVisible = state is ViewState.Connection.Error + viewBinding.groupData.isVisible = state is ViewState.Data + } + + private fun showConnection(state: ViewState.Connection) { + with(viewBinding) { + connectionImg.setAnimation(state.animationRes) + connectionImg.playAnimation() + connectionText.setText(state.descRes) + } + } + + private fun showData(state: ViewState.Data) { + viewBinding.balanceFrom.setupModel(state.balance.fromWalletModel) + viewBinding.balanceTo.setupModel(state.balance.toWalletModel) + viewBinding.balanceFrom.isEnabled = !state.inProgress + viewBinding.balanceTo.isEnabled = !state.inProgress + viewBinding.btnTransfer.isEnabled = !state.inProgress + viewBinding.balanceSending.isVisible = state.inProgress + } + + private fun handleSideEffect(effect: Effect) { + when (effect) { + is Navigation -> handleNavigation(effect) + is ViewEffect -> handleEffect(effect) + } + } + + private fun handleNavigation(navigation: Navigation) {} + private fun handleEffect(effect: ViewEffect) { + when (effect) { + is ViewEffect.ShowToast -> showToast(effect) + } + } + + private fun showToast(effect: ViewEffect.ShowToast) { + Toast.makeText(requireContext(), effect.textResId, Toast.LENGTH_LONG).show() + } +} \ No newline at end of file diff --git a/android/app/src/main/java/com/zk/android/app/presentation/transfer/TransferState.kt b/android/app/src/main/java/com/zk/android/app/presentation/transfer/TransferState.kt new file mode 100644 index 0000000..1925ff2 --- /dev/null +++ b/android/app/src/main/java/com/zk/android/app/presentation/transfer/TransferState.kt @@ -0,0 +1,40 @@ +package com.zk.android.app.presentation.transfer + +import androidx.annotation.RawRes +import androidx.annotation.StringRes +import com.zk.android.app.R +import com.zk.android.app.presentation.transfer.model.BalanceModel + +internal sealed class ViewState { + sealed class Connection( + @RawRes val animationRes: Int, + @StringRes val descRes: Int + ) : ViewState() { + data object Loading : Connection( + R.raw.lottie_loading, + R.string.connection + ) + + data object Error : Connection( + R.raw.lottie_connection_error, + R.string.connection_error + ) + } + + data class Data(val balance: BalanceModel, val inProgress: Boolean = false) : ViewState() +} + +internal sealed interface Effect + +internal sealed class Navigation : Effect { + +} + +internal sealed class ViewEffect : Effect { + class ShowToast(@StringRes val textResId: Int) : ViewEffect() +} + +internal sealed class Action { + data object ClickConnect : Action() + class ClickSend(val amount: Float) : Action() +} \ No newline at end of file diff --git a/android/app/src/main/java/com/zk/android/app/presentation/transfer/TransferViewModel.kt b/android/app/src/main/java/com/zk/android/app/presentation/transfer/TransferViewModel.kt new file mode 100644 index 0000000..6855e85 --- /dev/null +++ b/android/app/src/main/java/com/zk/android/app/presentation/transfer/TransferViewModel.kt @@ -0,0 +1,75 @@ +package com.zk.android.app.presentation.transfer + +import android.app.Application +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.viewModelScope +import com.zk.android.app.R +import com.zk.android.app.domain.balance.GetBalanceUseCase +import com.zk.android.app.domain.deposit.DepositUseCase +import com.zk.android.app.presentation.transfer.model.ModelMapper +import com.zk.android.app.presentation.transfer.widget.FromWalletViewModel +import dagger.hilt.android.lifecycle.HiltViewModel +import org.orbitmvi.orbit.ContainerHost +import org.orbitmvi.orbit.container +import org.orbitmvi.orbit.syntax.simple.intent +import org.orbitmvi.orbit.syntax.simple.postSideEffect +import org.orbitmvi.orbit.syntax.simple.reduce +import javax.inject.Inject + +@HiltViewModel +internal class TransferViewModel @Inject constructor( + private val application: Application, + private val mapper: ModelMapper, + private val balanceUseCase: GetBalanceUseCase, + private val depositUseCase: DepositUseCase +) : AndroidViewModel(application), ContainerHost { + + override val container = viewModelScope.container(ViewState.Connection.Loading) + + init { + connect() + } + + private fun connect() = intent { + reduce { ViewState.Connection.Loading } + val result = balanceUseCase.getBalance().getOrElse { + reduce { ViewState.Connection.Error } + return@intent + } + val state = ViewState.Data( + mapper.map(result) + ) + reduce { state } + } + + fun obtainAction(action: Action) { + when (action) { + is Action.ClickConnect -> processClickConnect() + is Action.ClickSend -> processClickSend(action) + } + } + + private fun processClickConnect() = intent { + connect() + } + + private fun processClickSend(action: Action.ClickSend) = intent { + val currentState = state as? ViewState.Data ?: return@intent + val amount = action.amount + reduce { + currentState.copy( + balance = currentState.balance.copy(fromWalletModel = FromWalletViewModel(amount.toString())), + inProgress = true + ) + } + val result = depositUseCase.deposit(action.amount).getOrElse { + reduce { ViewState.Connection.Error } + return@intent + } + val state = ViewState.Data( + mapper.map(result.newBalance) + ) + reduce { state } + postSideEffect(ViewEffect.ShowToast(R.string.deposit_completed)) + } +} \ No newline at end of file diff --git a/android/app/src/main/java/com/zk/android/app/presentation/transfer/model/BalanceModel.kt b/android/app/src/main/java/com/zk/android/app/presentation/transfer/model/BalanceModel.kt new file mode 100644 index 0000000..4f89b96 --- /dev/null +++ b/android/app/src/main/java/com/zk/android/app/presentation/transfer/model/BalanceModel.kt @@ -0,0 +1,10 @@ +package com.zk.android.app.presentation.transfer.model + +import com.zk.android.app.presentation.transfer.widget.FromWalletViewModel +import com.zk.android.app.presentation.transfer.widget.ToWalletViewModel + + +data class BalanceModel( + val fromWalletModel: FromWalletViewModel, + val toWalletModel: ToWalletViewModel, +) \ No newline at end of file diff --git a/android/app/src/main/java/com/zk/android/app/presentation/transfer/model/ModelMapper.kt b/android/app/src/main/java/com/zk/android/app/presentation/transfer/model/ModelMapper.kt new file mode 100644 index 0000000..cbf5426 --- /dev/null +++ b/android/app/src/main/java/com/zk/android/app/presentation/transfer/model/ModelMapper.kt @@ -0,0 +1,19 @@ +package com.zk.android.app.presentation.transfer.model + +import com.zk.android.app.domain.balance.BalanceModelDomain +import com.zk.android.app.presentation.transfer.widget.FromWalletViewModel +import com.zk.android.app.presentation.transfer.widget.ToWalletViewModel +import com.zk.android.app.utils.weiToEther +import javax.inject.Inject + + +class ModelMapper @Inject constructor() { + fun map(balanceModelDomain: BalanceModelDomain): BalanceModel { + return with(balanceModelDomain) { + BalanceModel( + FromWalletViewModel(l1Balance.weiToEther().toString()), + ToWalletViewModel(l2Balance.weiToEther().toString()) + ) + } + } +} \ No newline at end of file diff --git a/android/app/src/main/java/com/zk/android/app/presentation/transfer/widget/FromWalletView.kt b/android/app/src/main/java/com/zk/android/app/presentation/transfer/widget/FromWalletView.kt new file mode 100644 index 0000000..9d840a7 --- /dev/null +++ b/android/app/src/main/java/com/zk/android/app/presentation/transfer/widget/FromWalletView.kt @@ -0,0 +1,35 @@ +package com.zk.android.app.presentation.transfer.widget + +import android.content.Context +import android.util.AttributeSet +import android.view.LayoutInflater +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.view.setPadding +import com.zk.android.app.R +import com.zk.android.app.databinding.ViewFromBalanceBinding +import com.zk.android.app.utils.toPixelI + + +class FromWalletView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : ConstraintLayout(context, attrs, defStyleAttr) { + + private val binding = ViewFromBalanceBinding.inflate(LayoutInflater.from(context), this) + + init { + setBackgroundResource(R.drawable.bg_gray_content) + setPadding(context.toPixelI(16)) + } + + fun setupModel(model: FromWalletViewModel) { + binding.amount.setText(model.balance) + binding.amount.clearFocus() + } + + fun getAmount(): Float { + val text = binding.amount.text?.toString() ?: return 0f + return text.toFloat() + } +} \ No newline at end of file diff --git a/android/app/src/main/java/com/zk/android/app/presentation/transfer/widget/FromWalletViewModel.kt b/android/app/src/main/java/com/zk/android/app/presentation/transfer/widget/FromWalletViewModel.kt new file mode 100644 index 0000000..6347dea --- /dev/null +++ b/android/app/src/main/java/com/zk/android/app/presentation/transfer/widget/FromWalletViewModel.kt @@ -0,0 +1,6 @@ +package com.zk.android.app.presentation.transfer.widget + + +data class FromWalletViewModel ( + val balance: String +) \ No newline at end of file diff --git a/android/app/src/main/java/com/zk/android/app/presentation/transfer/widget/ToWalletView.kt b/android/app/src/main/java/com/zk/android/app/presentation/transfer/widget/ToWalletView.kt new file mode 100644 index 0000000..0c4ccec --- /dev/null +++ b/android/app/src/main/java/com/zk/android/app/presentation/transfer/widget/ToWalletView.kt @@ -0,0 +1,29 @@ +package com.zk.android.app.presentation.transfer.widget + +import android.content.Context +import android.util.AttributeSet +import android.view.LayoutInflater +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.view.setPadding +import com.zk.android.app.R +import com.zk.android.app.databinding.ViewToBalanceBinding +import com.zk.android.app.utils.toPixelI + + +class ToWalletView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : ConstraintLayout(context, attrs, defStyleAttr) { + + private val binding = ViewToBalanceBinding.inflate(LayoutInflater.from(context), this) + + init { + setBackgroundResource(R.drawable.bg_gray_content) + setPadding(context.toPixelI(16)) + } + + fun setupModel(model: ToWalletViewModel) { + binding.balanceValue.text = model.balance + } +} \ No newline at end of file diff --git a/android/app/src/main/java/com/zk/android/app/presentation/transfer/widget/ToWalletViewModel.kt b/android/app/src/main/java/com/zk/android/app/presentation/transfer/widget/ToWalletViewModel.kt new file mode 100644 index 0000000..024c582 --- /dev/null +++ b/android/app/src/main/java/com/zk/android/app/presentation/transfer/widget/ToWalletViewModel.kt @@ -0,0 +1,6 @@ +package com.zk.android.app.presentation.transfer.widget + + +data class ToWalletViewModel ( + val balance: String +) \ No newline at end of file diff --git a/android/app/src/main/java/com/zk/android/app/utils/EthExt.kt b/android/app/src/main/java/com/zk/android/app/utils/EthExt.kt new file mode 100644 index 0000000..56ad3e3 --- /dev/null +++ b/android/app/src/main/java/com/zk/android/app/utils/EthExt.kt @@ -0,0 +1,27 @@ +package com.zk.android.app.utils + +import org.web3j.protocol.core.RemoteCall +import org.web3j.protocol.core.methods.response.TransactionReceipt +import org.web3j.tx.response.TransactionReceiptProcessor +import java.math.BigInteger + +fun RemoteCall.sendSafe(): Result { + return runCatching { send() } +} + +fun TransactionReceiptProcessor.waitForTransactionReceiptSafe(transactionHash: String): Result { + return runCatching { waitForTransactionReceipt(transactionHash) } +} + +fun BigInteger.weiToEther(): Double { + val etherInWei = BigInteger.TEN.pow(18) // 1 ether = 10^18 wei + return toDouble() / etherInWei.toDouble() +} + +fun Float.toWei(): BigInteger { + // Ethereum wei is the smallest denomination of Ether (1 Ether = 10^18 wei) + val weiPerEther = BigInteger.TEN.pow(18) + + // Convert the Float value to BigInteger by multiplying it by the wei per Ether + return (this * weiPerEther.toFloat()).toBigDecimal().toBigInteger() +} \ No newline at end of file diff --git a/android/app/src/main/java/com/zk/android/app/utils/FragmentExt.kt b/android/app/src/main/java/com/zk/android/app/utils/FragmentExt.kt new file mode 100644 index 0000000..847c105 --- /dev/null +++ b/android/app/src/main/java/com/zk/android/app/utils/FragmentExt.kt @@ -0,0 +1,78 @@ +package com.zk.android.app.utils + +import androidx.fragment.app.Fragment +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.LifecycleOwner +import kotlin.properties.ReadWriteProperty +import kotlin.reflect.KProperty + +fun Fragment.viewLifecycle(onDestroyFinalize: ((T?) -> Unit)? = null): ReadWriteProperty = + object : ReadWriteProperty, DefaultLifecycleObserver { + + private var field: T? = null + + private var lifecycleOwner: LifecycleOwner? = null + + init { + viewLifecycleOwnerLiveData + .observe(this@viewLifecycle) { newViewLifecycleOwner -> + lifecycleOwner + ?.lifecycle + ?.removeObserver(this) + + lifecycleOwner = newViewLifecycleOwner.also { + it.lifecycle.addObserver(this) + } + } + } + + override fun onDestroy(owner: LifecycleOwner) { + super.onDestroy(owner) + onDestroyFinalize?.invoke(field) + field = null + lifecycleOwner = null + } + + override fun getValue(thisRef: Fragment, property: KProperty<*>): T { + return this.field!! + } + + override fun setValue(thisRef: Fragment, property: KProperty<*>, value: T) { + this.field = value + } + } + +fun Fragment.viewLifecycleNullable(): ReadWriteProperty = + object : ReadWriteProperty, DefaultLifecycleObserver { + + private var field: T? = null + + private var lifecycleOwner: LifecycleOwner? = null + + init { + viewLifecycleOwnerLiveData + .observe(this@viewLifecycleNullable) { newViewLifecycleOwner -> + lifecycleOwner + ?.lifecycle + ?.removeObserver(this) + + lifecycleOwner = newViewLifecycleOwner.also { + it.lifecycle.addObserver(this) + } + } + } + + override fun onDestroy(owner: LifecycleOwner) { + super.onDestroy(owner) + field = null + lifecycleOwner = null + } + + override fun getValue(thisRef: Fragment, property: KProperty<*>): T? { + return this.field + } + + override fun setValue(thisRef: Fragment, property: KProperty<*>, value: T?) { + this.field = value + } + } \ No newline at end of file diff --git a/android/app/src/main/java/com/zk/android/app/utils/ViewExt.kt b/android/app/src/main/java/com/zk/android/app/utils/ViewExt.kt new file mode 100644 index 0000000..b4959ae --- /dev/null +++ b/android/app/src/main/java/com/zk/android/app/utils/ViewExt.kt @@ -0,0 +1,12 @@ +package com.zk.android.app.utils + +import android.content.Context +import android.util.TypedValue + +fun Context.toPixelF(dpSize: Int) = toPixelF(dpSize.toFloat()) + +fun Context.toPixelF(dpSizeF: Float) = TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, dpSizeF, resources.displayMetrics +) + +fun Context.toPixelI(dpSize: Int) = toPixelF(dpSize).toInt() \ No newline at end of file diff --git a/android/app/src/main/res/animator/views_animation_selector.xml b/android/app/src/main/res/animator/views_animation_selector.xml new file mode 100644 index 0000000..06dbb13 --- /dev/null +++ b/android/app/src/main/res/animator/views_animation_selector.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/drawable-nodpi/img_splash_brand_white.png b/android/app/src/main/res/drawable-nodpi/img_splash_brand_white.png new file mode 100644 index 0000000000000000000000000000000000000000..338f232bc6488e71343518e1ac024b7437be712e GIT binary patch literal 9859 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 wCNnK^a!gbCKQzAd{~yIFzS=tEb1L+)e%U literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/drawable/bg_gray_content.xml b/android/app/src/main/res/drawable/bg_gray_content.xml new file mode 100644 index 0000000..a8ddd26 --- /dev/null +++ b/android/app/src/main/res/drawable/bg_gray_content.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/drawable/bg_gray_overlay.xml b/android/app/src/main/res/drawable/bg_gray_overlay.xml new file mode 100644 index 0000000..c8f1222 --- /dev/null +++ b/android/app/src/main/res/drawable/bg_gray_overlay.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/drawable/ic_ethereum.xml b/android/app/src/main/res/drawable/ic_ethereum.xml new file mode 100644 index 0000000..3de1fc9 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_ethereum.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/res/drawable/img_zk_logo.png b/android/app/src/main/res/drawable/img_zk_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..3c860858f32911c6e8e2b04d0794d9695735807f GIT binary patch literal 2604 zcmds33s)0K7ESt}@1FpgfYyc4)9kv?goc#||=hW%C_f~yh)veq2 zR?g;4{6%I<%_tPgq96Hf+awAD+uLn6~~M`_sg?a*-3WgB}fEJ`jdq&;mU_`!&d7V zVv2JoEP>X=@Y$4l*5=C21*R@rxa>7n=LIh{7HyfQu|{gUf1=gW`l+AnQ#HV<&5!QJ8q6>{^zy$AGCqAyTu6!s6LaUqMA9la)_R9!ig^khTNgOureBnY z>L4S>La$Sy1`O2brvqBC5w+{Wi8!sq2%-)@b%J?Zkt^$|6O)HZKJQ;fFqv0Q%M+H7 zyrU^O^S~)I5;*PZKj(q38Ux_QE9rj#r<1^K&pY&{2B-dF*VRF#AqLTx0y)PegW-YU zEE2fC#(Ez3;oJe1tR0x&Q!zRBAu-Rg*95>i9Q7)-3sDYCPfzz%RaJ#mO9g-Z=IlTY z&SV-gEZyjkQJlAqw-NDLPqo9vUl>AmMH2G>PLUip59S&(VS5Q~j&a$}uzfym9>`@o z!R}nt7-5z&ENvW-EPB30jtLFFOELO!jgE&Pzxq;#aB+S2D8zx9ACIU_cP z;I>j$z*m&+Uq5Ybuj71HmYl82xs_H!ml+5!uA6_kuY_0QL zxvE-9YHESTuZ|s%>RMjN(m@W_uG69I&Twk74N=zFvkd`!*EN*@7DZhO z3%MM`IjfE0WXFDm1nf}8+Ud*snssJKz%l(TYX^bukhrv-oe`&I@c^=m%ISiIKOzD z*ZIn#T}`(XS;;@LWzqzKUGF6@f1Yp(!kw z9E74iOU4R3R=Po!ADKvN%WwsvUtiH58ozs7cSn}-9Au&2Of2D}hsIu9zpA~xSbyQ> z<4fPnex8|llQH^Mmf5kmt>pjFTH45%`FYN5XX9V<`q9DT0NIN8`;$&~#e>$Cgs9bq zeUT@G$}_mf%H4w3w~7LD$q^GiQU3u~?n^S+3C8pzC$acii^KQtfy3@R01dgrk;_wY zljsYD_#~Ye_xe=DanMW7PtxVn<*G?h@z$8*5qy80{B%DW=Gd_ey76IT-J`DWvn z-?QV5yh!56LZnMxxu~?`)#5<}?xJAngCXDh2>4SuzQRzUi_+EGMKKk!eA&8p zBTANre^}G?l!k3Agv{*kge(*9mI0GbYuNm%bOQvwIhAs94zPJ2S84d< zxhDjXg(`)Hiz&bL`OIN-DFA#$*?wdWBf$z_4EK4@n=S&A!2dit$Nbb0Y)Uhx!2c*` zC`WSpitGHuP;!f)5YCZ_b^@~PJrVO{4Q7B$ebpW!>)fCSnC`L=#w(oldl~GM$&kGV zM~JAuxaNMPt1YprVf5(0I?bQzDFWV9ACunn%u+99KjN3CO{vI7R?x;xXhVSD`2PSo C`m~<_ literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/layout/activity_main.xml b/android/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..9cc8825 --- /dev/null +++ b/android/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,23 @@ + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/layout/fragment_transfer.xml b/android/app/src/main/res/layout/fragment_transfer.xml new file mode 100644 index 0000000..ceb20ca --- /dev/null +++ b/android/app/src/main/res/layout/fragment_transfer.xml @@ -0,0 +1,132 @@ + + + + + + + + +