├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── kotlin │ │ └── com │ │ └── github │ │ └── willjgriff │ │ └── ethereumwallet │ │ └── data │ │ └── ethereum │ │ └── GethDelegatesAndroidTestKotlin.kt │ ├── main │ ├── AndroidManifest.xml │ ├── kotlin │ │ └── com │ │ │ └── github │ │ │ └── willjgriff │ │ │ └── ethereumwallet │ │ │ ├── EthereumWalletApplication.kt │ │ │ ├── data │ │ │ └── SharedPreferencesManager.kt │ │ │ ├── di │ │ │ ├── AppComponent.kt │ │ │ ├── AppInjector.kt │ │ │ ├── ControllerScope.kt │ │ │ └── modules │ │ │ │ ├── AppModule.kt │ │ │ │ └── EthereumModule.kt │ │ │ ├── ethereum │ │ │ ├── EthereumManager.kt │ │ │ ├── GethAdapterFactory.kt │ │ │ ├── address │ │ │ │ ├── AccountsExtensions.kt │ │ │ │ ├── ActiveAddress.kt │ │ │ │ ├── AddressAdapter.kt │ │ │ │ ├── AddressManager.kt │ │ │ │ ├── balance │ │ │ │ │ ├── AddressBalance.kt │ │ │ │ │ └── AddressBalanceAdapter.kt │ │ │ │ └── model │ │ │ │ │ └── DomainAddress.kt │ │ │ ├── common │ │ │ │ ├── UnitConverterExtensions.kt │ │ │ │ └── model │ │ │ │ │ ├── Denomination.kt │ │ │ │ │ └── EtherAmount.kt │ │ │ ├── icap │ │ │ │ ├── BaseConverter.kt │ │ │ │ ├── IbanChecksumUtils.kt │ │ │ │ ├── IbanGenerator.kt │ │ │ │ └── IbanParam.kt │ │ │ ├── node │ │ │ │ ├── DomainNode.kt │ │ │ │ ├── NewBlockHeaderAdapter.kt │ │ │ │ ├── NodeDetails.kt │ │ │ │ ├── NodeDetailsAdapter.kt │ │ │ │ ├── NodeDetailsExtensions.kt │ │ │ │ └── model │ │ │ │ │ └── DomainBlockHeader.kt │ │ │ ├── transaction │ │ │ │ ├── SendTransactionGenerator.kt │ │ │ │ ├── TransactionAdapter.kt │ │ │ │ ├── TransactionManager.kt │ │ │ │ └── model │ │ │ │ │ └── SendTransaction.kt │ │ │ └── transactions │ │ │ │ ├── BlocksSearchedLogger.kt │ │ │ │ ├── TransactionsAdapter.kt │ │ │ │ ├── TransactionsManager.kt │ │ │ │ ├── model │ │ │ │ ├── BlockRange.kt │ │ │ │ ├── DomainBlock.kt │ │ │ │ └── DomainTransaction.kt │ │ │ │ └── storage │ │ │ │ ├── SharedPrefsTransactionsStorage.kt │ │ │ │ └── TransactionsStorage.kt │ │ │ ├── mvp │ │ │ ├── BaseMvpAlertDialog.kt │ │ │ ├── BaseMvpControllerKotlin.kt │ │ │ ├── BaseMvpPresenterKotlin.kt │ │ │ └── MvpPresenter.kt │ │ │ ├── ui │ │ │ ├── MainActivity.kt │ │ │ ├── error │ │ │ │ └── ErrorDisplayer.kt │ │ │ ├── navigation │ │ │ │ ├── NavigationController.kt │ │ │ │ ├── NavigationControllerFactory.kt │ │ │ │ └── NavigationToolbarListener.kt │ │ │ ├── screens │ │ │ │ ├── createaccount │ │ │ │ │ ├── CreateAccountCompletedListener.kt │ │ │ │ │ ├── CreateAccountController.kt │ │ │ │ │ ├── PreNavigationCreateAccountController.kt │ │ │ │ │ ├── SettingsCreateAccountController.kt │ │ │ │ │ ├── di │ │ │ │ │ │ └── CreateAccountComponent.kt │ │ │ │ │ └── mvp │ │ │ │ │ │ ├── CreateAccountPresenter.kt │ │ │ │ │ │ └── CreateAccountView.kt │ │ │ │ ├── nodedetails │ │ │ │ │ ├── NodeDetailsController.kt │ │ │ │ │ ├── di │ │ │ │ │ │ └── NodeDetailsComponent.kt │ │ │ │ │ ├── list │ │ │ │ │ │ ├── NodeDetailsHeadersAdapter.kt │ │ │ │ │ │ └── NodeDetailsPeersAdapter.kt │ │ │ │ │ └── mvp │ │ │ │ │ │ ├── NodeDetailsPresenter.kt │ │ │ │ │ │ └── NodeDetailsView.kt │ │ │ │ ├── receive │ │ │ │ │ ├── ReceiveController.kt │ │ │ │ │ ├── di │ │ │ │ │ │ └── ReceiveComponent.kt │ │ │ │ │ └── mvp │ │ │ │ │ │ ├── ReceivePresenter.kt │ │ │ │ │ │ └── ReceiveView.kt │ │ │ │ ├── send │ │ │ │ │ ├── SendController.kt │ │ │ │ │ ├── di │ │ │ │ │ │ └── SendComponent.kt │ │ │ │ │ ├── model │ │ │ │ │ │ └── WholeTransaction.kt │ │ │ │ │ └── mvp │ │ │ │ │ │ ├── SendPresenter.kt │ │ │ │ │ │ └── SendView.kt │ │ │ │ ├── settings │ │ │ │ │ ├── ChangeAddressController.kt │ │ │ │ │ ├── DeleteAddressAlertDialog.kt │ │ │ │ │ ├── SettingsController.kt │ │ │ │ │ ├── di │ │ │ │ │ │ └── SettingsComponent.kt │ │ │ │ │ ├── list │ │ │ │ │ │ ├── ChangeAddressAdapter.kt │ │ │ │ │ │ ├── ChangeAddressHeaderViewHolder.kt │ │ │ │ │ │ └── ChangeAddressItemViewHolder.kt │ │ │ │ │ └── mvp │ │ │ │ │ │ ├── ChangeAddressPresenter.kt │ │ │ │ │ │ ├── ChangeAddressView.kt │ │ │ │ │ │ ├── DeleteAddressPresenter.kt │ │ │ │ │ │ ├── DeleteAddressView.kt │ │ │ │ │ │ ├── SettingsPresenter.kt │ │ │ │ │ │ └── SettingsView.kt │ │ │ │ └── transactions │ │ │ │ │ ├── SelectBlockRangeAlertDialog.kt │ │ │ │ │ ├── TransactionsController.kt │ │ │ │ │ ├── adapters │ │ │ │ │ └── TransactionsAdapter.kt │ │ │ │ │ ├── di │ │ │ │ │ └── TransactionsComponent.kt │ │ │ │ │ ├── mvp │ │ │ │ │ ├── SelectBlockRangePresenter.kt │ │ │ │ │ ├── SelectBlockRangeView.kt │ │ │ │ │ ├── TransactionsPresenter.kt │ │ │ │ │ └── TransactionsView.kt │ │ │ │ │ └── viewholders │ │ │ │ │ └── TransactionViewHolder.kt │ │ │ ├── utils │ │ │ │ ├── InflaterExtensions.kt │ │ │ │ ├── SizeConverterExtensions.kt │ │ │ │ ├── ViewExtensions.kt │ │ │ │ └── listdecorator │ │ │ │ │ └── EvenPaddingDecorator.kt │ │ │ └── widget │ │ │ │ └── validatedtextinput │ │ │ │ ├── RxTextInputValidation.kt │ │ │ │ ├── ValidatedTextInputLayout.kt │ │ │ │ ├── ValidatorFactory.kt │ │ │ │ └── validators │ │ │ │ ├── NotEmptyValidator.kt │ │ │ │ └── Validator.kt │ │ │ └── utils │ │ │ ├── ErrorExtensions.kt │ │ │ ├── ObservableExtensions.kt │ │ │ └── resettablelazy │ │ │ ├── Funcs.kt │ │ │ ├── ResettableLazy.kt │ │ │ └── ResettableLazyManager.kt │ └── res │ │ ├── drawable │ │ ├── ic_node_status_24dp.xml │ │ ├── ic_receive_24dp.xml │ │ ├── ic_send_24dp.xml │ │ ├── ic_settings_black_24dp.xml │ │ ├── ic_transactions_24dp.xml │ │ └── selector_navigation_icon_color.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── controller_create_account.xml │ │ ├── controller_create_account_pre_navigation.xml │ │ ├── controller_create_account_settings.xml │ │ ├── controller_list.xml │ │ ├── controller_navigation.xml │ │ ├── controller_node_status.xml │ │ ├── controller_receive.xml │ │ ├── controller_send.xml │ │ ├── controller_settings.xml │ │ ├── controller_settings_change_address.xml │ │ ├── controller_transactions.xml │ │ ├── toolbar.xml │ │ ├── view_change_address_header.xml │ │ ├── view_change_address_item.xml │ │ ├── view_controller_settings_delete_validated_input.xml │ │ ├── view_controller_transactions_search_range_dialog.xml │ │ ├── view_node_details_header_item.xml │ │ ├── view_node_status_peer_item.xml │ │ ├── view_transaction_item.xml │ │ └── view_validated_text_input_layout.xml │ │ ├── menu │ │ └── menu_bottom_navigation.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap │ │ └── ethereum_logo.png │ │ └── values │ │ ├── attrs.xml │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ ├── styles.xml │ │ └── text_styles.xml │ └── test │ ├── kotlin │ └── com │ │ └── github │ │ └── willjgriff │ │ └── ethereumwallet │ │ └── ethereum │ │ ├── account │ │ └── AddressManagerTest.kt │ │ ├── common │ │ └── model │ │ │ ├── EtherAmountTest.kt │ │ │ └── UnitConverterExtensionsTest.kt │ │ ├── icap │ │ ├── BaseConverterTest.kt │ │ ├── IbanChecksumUtilsTest.kt │ │ └── IbanGeneratorTest.kt │ │ ├── payment │ │ └── SendDomainTransactionGeneratorTest.kt │ │ └── transaction │ │ └── transactions │ │ └── BlocksSearchedLoggerTest.kt │ └── resources │ └── mockito-extensions │ └── org.mockito.plugins.MockMaker ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the ART/Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | 16 | # Gradle files 17 | .gradle/ 18 | build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | 29 | # Android Studio Navigation editor temp files 30 | .navigation/ 31 | 32 | # Android Studio captures folder 33 | captures/ 34 | 35 | # Intellij 36 | *.iml 37 | .idea/ 38 | 39 | # Keystore files 40 | *.jks -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # android-ethereum-wallet 2 | 3 | ***For the time being I have stopped working on this app in favour of learning more about Ethereum and specifically Solidity. See https://github.com/willjgriff/solidity-playground*** 4 | 5 | This app will be a front end for an Ethereum light client. To begin with I will use the Go Ethereum implementation since it is easy to import. However, this may drain the mobile's resources, it at least seems to use a lot of storage in preliminary builds once fully sycn'd (400MB+). I believe the current version of the light client doesn't remove unused data (mainly block headers I think) but in the future it should do, reducing this size. I may also experiment with connecting to an externally hosted node. 6 | 7 | It will allow for basic account/address creation and transfer to/from it. The account may be extractable for use in other wallets. The behaviour and capability of the app will be largely dependant on the services provided by the light client. 8 | 9 | This app uses a structure similar to my Skeleton app. It is written in Kotlin and uses MVP, Dagger2, RxJava2 and only uses one Activity with Conductor for View management. 10 | 11 | I've created Geth adapters that wrap around the Geth light client classes to decouple them from the rest of the app. I also cannot Unit Test anything that uses the Geth light client classes without instrumentation (a link error is returned when run as standard JUnit tests). Decoupling the Geth classes from the rest of the code allows me to Unit Test the Ethereum manager classes without accessing the actual Geth classes. I'm not testing much at the moment generally. More will come later. 12 | 13 | Current progress (the UI is still very basic, it will be made better if completed):
14 | [x] Creation, deletion and switching of account addresses from the Settings screen.
15 | [ ] Importing and exporting account addresses.
16 | [x] Node status screen which displays syncing updates.
17 | [x] Receive screen balance updates.
18 | [x] Send screen submit transaction.
19 | [ ] Creation of QR code for receiving ether.
20 | [ ] Creation of send transaction from QR code.
21 | [ ] Transactions list, outgoing and incoming. (I have made an implementation for this but it is buggy. It is also inefficient to get and store the transaction list manually. This will be switched to use a third party, probably etherscan.io) 22 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the ART/Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | 16 | # Gradle files 17 | .gradle/ 18 | build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | 29 | # Android Studio Navigation editor temp files 30 | .navigation/ 31 | 32 | # Android Studio captures folder 33 | captures/ 34 | 35 | # Intellij 36 | *.iml 37 | .idea/workspace.xml 38 | 39 | # Keystore files 40 | *.jks 41 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | 5 | android { 6 | compileSdkVersion 25 7 | buildToolsVersion "25.0.2" 8 | defaultConfig { 9 | applicationId "com.github.willjgriff.ethereumwallet" 10 | minSdkVersion 16 11 | targetSdkVersion 25 12 | versionCode 1 13 | versionName "1.0" 14 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 15 | } 16 | buildTypes { 17 | debug { 18 | minifyEnabled false 19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 20 | } 21 | release { 22 | minifyEnabled true 23 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 24 | } 25 | } 26 | sourceSets { 27 | main.java.srcDirs += 'src/main/kotlin' 28 | test.java.srcDirs += 'src/test/kotlin' 29 | androidTest.java.srcDirs += 'src/androidTest/kotlin' 30 | } 31 | } 32 | 33 | kapt { 34 | generateStubs = true 35 | } 36 | 37 | ext { 38 | supportLib = '25.3.1' 39 | constraint = '1.0.2' 40 | timber = '4.5.1' 41 | conductor = '2.0.7' 42 | rxJava2 = '2.0.4' 43 | rxAndroid2 = '2.0.1' 44 | rxBinding = '2.0.0' 45 | geth = '1.6.0' 46 | dagger = '2.10' 47 | javax = '1.0' 48 | butterknife = '8.4.0' 49 | 50 | junit = '4.12' 51 | mockito = '2.7.0' 52 | robolectric = '3.2.2' 53 | kotMockito = '1.1.0' 54 | kluent = '1.14' 55 | 56 | dexmaker = '2.2.0' 57 | findbugs = '3.0.1' 58 | espresso = '2.2.2' 59 | } 60 | 61 | dependencies { 62 | compile fileTree(dir: 'libs', include: ['*.jar']) 63 | compile "com.android.support:appcompat-v7:${supportLib}" 64 | compile "com.android.support.constraint:constraint-layout:${constraint}" 65 | compile "com.android.support:cardview-v7:${supportLib}" 66 | compile "com.android.support:design:${supportLib}" 67 | 68 | compile "com.google.code.gson:gson:2.8.0" 69 | compile "com.jakewharton.timber:timber:${timber}" 70 | compile "com.bluelinelabs:conductor:${conductor}" 71 | compile "com.bluelinelabs:conductor-rxlifecycle2:${conductor}" 72 | compile "io.reactivex.rxjava2:rxjava:${rxJava2}" 73 | compile "io.reactivex.rxjava2:rxandroid:${rxAndroid2}" 74 | compile "com.jakewharton.rxbinding2:rxbinding:$rxBinding" 75 | 76 | compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" 77 | compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 78 | compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" 79 | compile "org.ethereum:geth:${geth}" 80 | 81 | compile "com.google.dagger:dagger:${dagger}" 82 | provided "javax.annotation:jsr250-api:${javax}" 83 | kapt "com.google.dagger:dagger-compiler:${dagger}" 84 | 85 | // TODO: Remove for Prod 86 | debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5' 87 | releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5' 88 | testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5' 89 | 90 | testCompile "junit:junit:${junit}" 91 | testCompile "org.mockito:mockito-core:${mockito}" 92 | testCompile "org.robolectric:robolectric:${robolectric}" 93 | testCompile("com.nhaarman:mockito-kotlin:${kotMockito}", { 94 | exclude group: 'org.jetbrains.kotlin', module: 'kotlin-reflect' 95 | }) 96 | testCompile "org.jetbrains.kotlin:kotlin-test-junit:${kotlin_version}" 97 | testCompile "org.amshove.kluent:kluent:${kluent}" 98 | 99 | // AndroidTest version of Mockito 100 | androidTestCompile "com.linkedin.dexmaker:dexmaker-mockito:${dexmaker}" 101 | androidTestCompile "com.google.code.findbugs:jsr305:${findbugs}" 102 | androidTestCompile("com.android.support.test.espresso:espresso-core:${espresso}", { 103 | exclude group: 'com.android.support', module: 'support-annotations' 104 | }) 105 | } 106 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/Will/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /app/src/androidTest/kotlin/com/github/willjgriff/ethereumwallet/data/ethereum/GethDelegatesAndroidTestKotlin.kt: -------------------------------------------------------------------------------- 1 | //package com.github.willjgriff.ethereumwallet.data.ethereum 2 | // 3 | //import android.accounts.AccountManager 4 | //import android.support.test.InstrumentationRegistry 5 | //import android.support.test.runner.AndroidJUnit4 6 | //import com.github.wiljgriff.ethereumwallet.ethereum.account.AccountsBridge 7 | //import com.github.wiljgriff.ethereumwallet.ethereum.account.WalletAccountManager 8 | //import com.github.wiljgriff.ethereumwallet.ethereum.account.delegates.AccountManagerDelegate 9 | //import org.ethereum.geth.AccountManager 10 | //import org.ethereum.geth.Geth 11 | //import org.junit.After 12 | //import org.junit.Assert.* 13 | //import org.junit.Before 14 | //import org.junit.Test 15 | //import org.junit.runner.RunWith 16 | //import java.io.File 17 | // 18 | ///** 19 | // * Created by Will on 06/02/2017. 20 | // */ 21 | //@RunWith(AndroidJUnit4::class) 22 | //class GethDelegatesAndroidTestKotlin { 23 | // 24 | // private val PASSWORD = "password" 25 | // private lateinit var subject: AccountsBridge 26 | // 27 | // private val appContext = InstrumentationRegistry.getTargetContext() 28 | // private val keystoreLocation = appContext.getFilesDir().toString() + "/keystore_location/" 29 | // 30 | // @Before 31 | // fun setupEthereumManagerAndroidTestKotlin() { 32 | // subject = AccountManagerDelegate(AccountManager(keystoreLocation, Geth.LightScryptN, Geth.LightScryptP)) 33 | // } 34 | // 35 | // @After 36 | // fun cleanEthereumManagerAndroidTestKotlin() { 37 | // File(keystoreLocation).deleteRecursively() 38 | // } 39 | // 40 | // @Test 41 | // fun getAccounts_withNewKeystoreReturnsAccountsWithSize0() { 42 | // val accounts = subject.getAccounts() 43 | // assertEquals(0, accounts.size()) 44 | // } 45 | // 46 | // @Test 47 | // fun newAccount_shouldNotBeNull() { 48 | // val account = subject.newAccount(PASSWORD) 49 | // assertNotNull(account) 50 | // } 51 | // 52 | // @Test 53 | // fun getAccounts_withAccountShouldHaveSize1() { 54 | // subject.newAccount(PASSWORD) 55 | // val accounts = subject.getAccounts() 56 | // assertEquals(1, accounts.size()) 57 | // } 58 | // 59 | // @Test 60 | // fun getAccountAtPosition1_returnsAccountAtPosition1WithSameFilePath() { 61 | // val account1Path = subject.newAccount(PASSWORD).getFile() 62 | // val account2Path = subject.newAccount(PASSWORD).getFile() 63 | // 64 | // val actualAccountPath = subject.getAccounts().get(1).getFile(); 65 | // 66 | // assertEquals(account2Path, actualAccountPath) 67 | // assertNotEquals(account1Path, actualAccountPath) 68 | // } 69 | // 70 | // @Test 71 | // fun getAddressHex_isTheSameAfterFetchingFromAccountManager() { 72 | // val address = subject.newAccount(PASSWORD).getAddress().getHex() 73 | // val fetched = subject.getAccounts().get(0).getAddress().getHex() 74 | // assertEquals(address, fetched) 75 | // } 76 | // 77 | // @Test 78 | // fun accountsSize_isCorrectAfterAddingAndDeletingAccounts() { 79 | // val account1 = subject.newAccount(PASSWORD) 80 | // subject.newAccount(PASSWORD) 81 | // subject.deleteAccount(account1, PASSWORD) 82 | // 83 | // val actualSize = subject.getAccounts().size() 84 | // 85 | // assertEquals(1, actualSize) 86 | // } 87 | //} -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 15 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/EthereumWalletApplication.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | import com.github.willjgriff.ethereumwallet.di.AppInjector 6 | import com.squareup.leakcanary.LeakCanary 7 | import timber.log.Timber 8 | 9 | /** 10 | * Created by williamgriffiths on 03/05/2017. 11 | */ 12 | class EthereumWalletApplication : Application() { 13 | 14 | // TODO: Remove for production, just for testing purposes. 15 | companion object { 16 | private lateinit var appObject: EthereumWalletApplication 17 | val app: Context 18 | get() = appObject 19 | } 20 | 21 | override fun onCreate() { 22 | super.onCreate() 23 | appObject = this 24 | setupLeakCanary() 25 | setupTimber() 26 | setupDagger() 27 | } 28 | 29 | private fun setupLeakCanary() { 30 | if (LeakCanary.isInAnalyzerProcess(this)) { 31 | // This process is dedicated to LeakCanary for heap analysis. 32 | // You should not init your app in this process. 33 | return 34 | } 35 | LeakCanary.install(this) 36 | } 37 | 38 | private fun setupTimber() { 39 | if (BuildConfig.DEBUG) { 40 | Timber.plant(Timber.DebugTree()) 41 | } 42 | } 43 | 44 | private fun setupDagger() { 45 | AppInjector.init(this) 46 | // This currently starts the Ethereum node, consider adding a call for this 47 | AppInjector.appComponent.inject(this) 48 | } 49 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/data/SharedPreferencesManager.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.data 2 | 3 | import android.content.SharedPreferences 4 | import com.google.gson.Gson 5 | import com.google.gson.reflect.TypeToken 6 | 7 | /** 8 | * Created by williamgriffiths on 24/04/2017. 9 | */ 10 | class SharedPreferencesManager(private val sharedPreferences: SharedPreferences, private val gson: Gson) { 11 | 12 | fun writeObjectToPreferences(key: String, value: T) { 13 | val json = gson.toJson(value) 14 | return sharedPreferences.edit().putString(key, json).apply() 15 | } 16 | 17 | fun readObjectFromPreferences(key: String, returnType: Class): T? { 18 | val readObject = sharedPreferences.getString(key, "") 19 | return gson.fromJson(readObject, returnType) 20 | } 21 | 22 | fun readComplexObjectFromPreferences(key: String, typeToken: TypeToken): T? { 23 | val readObject = sharedPreferences.getString(key, "") 24 | return gson.fromJson(readObject, typeToken.type) 25 | } 26 | 27 | fun removeObjectFromPrefs(key: String) { 28 | sharedPreferences.edit().remove(key).apply() 29 | } 30 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/di/AppComponent.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.di 2 | 3 | import com.github.willjgriff.ethereumwallet.EthereumWalletApplication 4 | import com.github.willjgriff.ethereumwallet.di.modules.AppModule 5 | import com.github.willjgriff.ethereumwallet.di.modules.EthereumModule 6 | import com.github.willjgriff.ethereumwallet.ethereum.address.AddressManager 7 | import com.github.willjgriff.ethereumwallet.ethereum.address.balance.AddressBalance 8 | import com.github.willjgriff.ethereumwallet.ethereum.node.NodeDetails 9 | import com.github.willjgriff.ethereumwallet.ethereum.transaction.TransactionManager 10 | import com.github.willjgriff.ethereumwallet.ethereum.transactions.TransactionsManager 11 | import com.github.willjgriff.ethereumwallet.ui.MainActivity 12 | import dagger.Component 13 | import javax.inject.Singleton 14 | 15 | /** 16 | * Created by williamgriffiths on 17/04/2017. 17 | */ 18 | @Singleton 19 | @Component(modules = arrayOf( 20 | AppModule::class, 21 | EthereumModule::class) 22 | ) 23 | interface AppComponent { 24 | 25 | fun provideNodeDetails(): NodeDetails 26 | 27 | fun provideAccountManager(): AddressManager 28 | 29 | fun providesAccountBalance(): AddressBalance 30 | 31 | fun providesTransactionManager(): TransactionManager 32 | 33 | fun providesTransactionsManager(): TransactionsManager 34 | 35 | fun inject(mainActivity: MainActivity) 36 | 37 | fun inject(ethereumWalletApplication: EthereumWalletApplication) 38 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/di/AppInjector.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.di 2 | 3 | import android.content.Context 4 | import com.github.willjgriff.ethereumwallet.di.modules.AppModule 5 | 6 | /** 7 | * Created by williamgriffiths on 17/04/2017. 8 | */ 9 | object AppInjector { 10 | 11 | lateinit var appComponent: AppComponent 12 | set 13 | 14 | fun init(context: Context) { 15 | appComponent = DaggerAppComponent.builder() 16 | .appModule(AppModule(context)) 17 | .build() 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/di/ControllerScope.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.di 2 | 3 | import javax.inject.Scope 4 | 5 | /** 6 | * Created by williamgriffiths on 17/04/2017. 7 | */ 8 | @MustBeDocumented 9 | @Scope 10 | @Retention(AnnotationRetention.RUNTIME) 11 | annotation class ControllerScope -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/di/modules/AppModule.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.di.modules 2 | 3 | import android.content.Context 4 | import android.content.SharedPreferences 5 | import android.preference.PreferenceManager 6 | import dagger.Module 7 | import dagger.Provides 8 | import javax.inject.Singleton 9 | 10 | /** 11 | * Created by williamgriffiths on 17/04/2017. 12 | */ 13 | @Module 14 | class AppModule(private val context: Context) { 15 | 16 | @Provides 17 | @Singleton 18 | fun providesContext(): Context { 19 | return context 20 | } 21 | 22 | @Provides 23 | @Singleton 24 | fun providesSharedPreferences(): SharedPreferences { 25 | return PreferenceManager.getDefaultSharedPreferences(context) 26 | } 27 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/di/modules/EthereumModule.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.di.modules 2 | 3 | import android.content.Context 4 | import com.github.willjgriff.ethereumwallet.ethereum.EthereumManager 5 | import dagger.Module 6 | import dagger.Provides 7 | import javax.inject.Singleton 8 | 9 | /** 10 | * Created by williamgriffiths on 17/04/2017. 11 | */ 12 | @Module 13 | class EthereumModule { 14 | 15 | @Provides 16 | @Singleton 17 | fun provideEthereumManager(context: Context) = 18 | EthereumManager(context) 19 | 20 | @Provides 21 | @Singleton 22 | fun provideNodeDetails(ethereumManager: EthereumManager) = 23 | ethereumManager.nodeDetails 24 | 25 | @Provides 26 | @Singleton 27 | fun provideWalletAccountManager(ethereumManager: EthereumManager) = 28 | ethereumManager.addressManager 29 | 30 | @Provides 31 | @Singleton 32 | fun provideAccountBalance(ethereumManager: EthereumManager) = 33 | ethereumManager.accountBalance 34 | 35 | @Provides 36 | @Singleton 37 | fun provideTransactionManager(ethereumManager: EthereumManager) = 38 | ethereumManager.transactionManager 39 | 40 | @Provides 41 | @Singleton 42 | fun provideTransactionsManager(ethereumManager: EthereumManager) = 43 | ethereumManager.transactionsManager 44 | 45 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ethereum/EthereumManager.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ethereum 2 | 3 | import android.content.Context 4 | import android.preference.PreferenceManager 5 | import com.github.willjgriff.ethereumwallet.ethereum.address.ActiveAddress 6 | import com.github.willjgriff.ethereumwallet.ethereum.address.AddressManager 7 | import com.github.willjgriff.ethereumwallet.ethereum.address.balance.AddressBalance 8 | import com.github.willjgriff.ethereumwallet.ethereum.node.DomainNode 9 | import com.github.willjgriff.ethereumwallet.ethereum.node.NodeDetails 10 | import com.github.willjgriff.ethereumwallet.ethereum.transaction.TransactionManager 11 | import com.github.willjgriff.ethereumwallet.ethereum.transactions.TransactionsManager 12 | import com.github.willjgriff.ethereumwallet.ethereum.transactions.storage.SharedPrefsTransactionsStorage 13 | import com.github.willjgriff.ethereumwallet.ethereum.transactions.storage.TransactionsStorage 14 | 15 | /** 16 | * Created by williamgriffiths on 17/04/2017. 17 | * 18 | * This class should not include any objects from the chosen Ethereum implementation, currently Geth. 19 | * Those objects should be configured and injected into Adapters in the [GethAdapterFactory]. 20 | * The objects the [EthereumManager] returns should only use these adapters and no Ethereum related objects. 21 | */ 22 | class EthereumManager(context: Context) { 23 | 24 | private val nodeFilePathMain = context.filesDir.toString() + "/ethereum_node/" 25 | private val keyStoreFilePath = context.filesDir.toString() + "/ethereum_key_store/" 26 | 27 | private val domainNode = DomainNode(nodeFilePathMain) 28 | private val gethAdapterFactory = GethAdapterFactory(domainNode, keyStoreFilePath) 29 | private val activeAccountAddress = ActiveAddress(PreferenceManager.getDefaultSharedPreferences(context)) 30 | 31 | /** This can be switched for anything that implements [TransactionsStorage] */ 32 | var transactionsStorage = SharedPrefsTransactionsStorage(context) 33 | 34 | val nodeDetails = NodeDetails(gethAdapterFactory.nodeDetailsAdapter, gethAdapterFactory.newBlockHeaderAdapter) 35 | val addressManager = AddressManager(gethAdapterFactory.accountsAdapter, activeAccountAddress) 36 | val accountBalance = AddressBalance(gethAdapterFactory.accountBalanceAdapter, addressManager) 37 | val transactionManager = TransactionManager(addressManager, gethAdapterFactory.transactionAdapter) 38 | val transactionsManager = TransactionsManager(transactionsStorage, gethAdapterFactory.transactionsAdapter, gethAdapterFactory.newBlockHeaderAdapter, addressManager) 39 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ethereum/GethAdapterFactory.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ethereum 2 | 3 | import com.github.willjgriff.ethereumwallet.ethereum.address.AddressAdapter 4 | import com.github.willjgriff.ethereumwallet.ethereum.address.balance.AddressBalanceAdapter 5 | import com.github.willjgriff.ethereumwallet.ethereum.node.DomainNode 6 | import com.github.willjgriff.ethereumwallet.ethereum.node.NewBlockHeaderAdapter 7 | import com.github.willjgriff.ethereumwallet.ethereum.node.NodeDetailsAdapter 8 | import com.github.willjgriff.ethereumwallet.ethereum.transaction.TransactionAdapter 9 | import com.github.willjgriff.ethereumwallet.ethereum.transactions.TransactionsAdapter 10 | import org.ethereum.geth.Context 11 | import org.ethereum.geth.Geth 12 | import org.ethereum.geth.KeyStore 13 | 14 | /** 15 | * Created by williamgriffiths on 17/04/2017. 16 | * 17 | * This class should contain as few non-Geth related objects as possible and return Adapters 18 | * that abstract the Geth related classes away from the rest of the app. 19 | */ 20 | class GethAdapterFactory(domainNode: DomainNode, keyStoreFilePath: String) { 21 | 22 | private val node = domainNode.node 23 | private val ethClient = node.ethereumClient 24 | private val context = Context() 25 | private val keyStore = KeyStore(keyStoreFilePath, Geth.LightScryptN, Geth.LightScryptP) 26 | 27 | val nodeDetailsAdapter = NodeDetailsAdapter(node, ethClient, context) 28 | val newBlockHeaderAdapter = NewBlockHeaderAdapter(ethClient, context) 29 | val accountsAdapter = AddressAdapter(keyStore) 30 | val accountBalanceAdapter = AddressBalanceAdapter(ethClient, context) 31 | val transactionAdapter = TransactionAdapter(keyStore, ethClient, context) 32 | val transactionsAdapter = TransactionsAdapter(ethClient, context) 33 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ethereum/address/AccountsExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ethereum.address 2 | 3 | import com.github.willjgriff.ethereumwallet.ethereum.address.model.DomainAddress 4 | import org.ethereum.geth.Account 5 | import org.ethereum.geth.Accounts 6 | 7 | /** 8 | * Created by williamgriffiths on 18/04/2017. 9 | */ 10 | fun List.getGethAccountFromAddress(address: DomainAddress): Account = 11 | filter { it.address.hex == address.hex } 12 | .singleOrNull() ?: Account() 13 | 14 | fun Accounts.asList(): List = 15 | 0L.rangeTo(size() - 1) 16 | .map { get(it) } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ethereum/address/ActiveAddress.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ethereum.address 2 | 3 | import android.content.SharedPreferences 4 | 5 | /** 6 | * Created by williamgriffiths on 28/02/2017. 7 | */ 8 | class ActiveAddress(private val sharedPreferences: SharedPreferences) { 9 | 10 | private val KEY_ACTIVE_ACCOUNT = ActiveAddress::class.qualifiedName + ";KEY_ACTIVE_ACCOUNT"; 11 | private val DEFAULT_ADDRESS_VALUE = "" 12 | 13 | fun getHex(): String = sharedPreferences 14 | .getString(KEY_ACTIVE_ACCOUNT, DEFAULT_ADDRESS_VALUE) 15 | 16 | fun setHex(address: String) = sharedPreferences 17 | .edit() 18 | .putString(KEY_ACTIVE_ACCOUNT, address) 19 | .apply() 20 | 21 | fun deleteActiveAddress() = setHex(DEFAULT_ADDRESS_VALUE) 22 | 23 | fun hasActiveAddress(): Boolean = getHex() != DEFAULT_ADDRESS_VALUE 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ethereum/address/AddressAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ethereum.address 2 | 3 | import com.github.willjgriff.ethereumwallet.ethereum.address.model.DomainAddress 4 | import org.ethereum.geth.KeyStore 5 | 6 | 7 | /** 8 | * Created by williamgriffiths on 16/04/2017. 9 | */ 10 | class AddressAdapter(val keyStore: KeyStore) { 11 | 12 | fun newAddress(password: String): DomainAddress { 13 | val newAccount = keyStore.newAccount(password) 14 | return DomainAddress.fromAddress(newAccount.address) 15 | } 16 | 17 | fun getAddresses(): List = keyStore.accounts 18 | .asList() 19 | .map { DomainAddress.fromAddress(it.address) } 20 | 21 | fun deleteAddress(activeAddress: DomainAddress, password: String) { 22 | val gethAccount = keyStore.accounts 23 | .asList() 24 | .getGethAccountFromAddress(activeAddress) 25 | keyStore.deleteAccount(gethAccount, password) 26 | } 27 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ethereum/address/AddressManager.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ethereum.address 2 | 3 | import com.github.willjgriff.ethereumwallet.ethereum.address.model.DomainAddress 4 | import timber.log.Timber 5 | 6 | /** 7 | * Created by Will on 06/02/2017. 8 | */ 9 | class AddressManager(private val addressAdapter: AddressAdapter, 10 | private val activeAddress: ActiveAddress) { 11 | 12 | fun createActiveAddress(password: String) { 13 | val newAddress = addressAdapter.newAddress(password) 14 | activeAddress.setHex(newAddress.hex) 15 | } 16 | 17 | fun getActiveAddress(): DomainAddress = getAllAddresses() 18 | .filter { it.hex == activeAddress.getHex() } 19 | .singleOrNull() ?: DomainAddress() 20 | 21 | fun hasAddress() = getAllAddresses().isNotEmpty() 22 | 23 | fun getAllAddresses(): List = addressAdapter.getAddresses() 24 | 25 | fun deleteActiveAddress(password: String): Boolean { 26 | try { 27 | if (activeAddress.hasActiveAddress()) { 28 | addressAdapter.deleteAddress(getActiveAddress(), password) 29 | activeAddress.deleteActiveAddress() 30 | } 31 | } catch (exception: Exception) { 32 | Timber.i(exception, "Account not deleted, password probably incorrect") 33 | } 34 | 35 | return !activeAddress.hasActiveAddress() 36 | } 37 | 38 | fun setActiveAccount(address: DomainAddress) { 39 | activeAddress.setHex(address.hex) 40 | } 41 | 42 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ethereum/address/balance/AddressBalance.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ethereum.address.balance 2 | 3 | import com.github.willjgriff.ethereumwallet.ethereum.address.AddressManager 4 | import com.github.willjgriff.ethereumwallet.ethereum.common.fromWeiTo 5 | import com.github.willjgriff.ethereumwallet.ethereum.common.model.Denomination 6 | import com.github.willjgriff.ethereumwallet.utils.androidIoSchedule 7 | import com.github.willjgriff.ethereumwallet.utils.replayEmissions 8 | import io.reactivex.Observable 9 | import java.math.BigInteger 10 | import java.util.concurrent.TimeUnit 11 | 12 | /** 13 | * Created by williamgriffiths on 15/04/2017. 14 | */ 15 | class AddressBalance(private val addressBalanceAdapter: AddressBalanceAdapter, private val addressManager: AddressManager) { 16 | 17 | private val EMISSION_INTERVAL_SECONDS = 1L 18 | private val storedAddressBalances: MutableMap> = mutableMapOf() 19 | private val storedPendingAddressBalances: MutableMap> = mutableMapOf() 20 | 21 | fun getBalanceAtActiveAddress(): Observable { 22 | val activeAddress = addressManager.getActiveAddress().hex 23 | return storedAddressBalances.getOrPut(activeAddress, 24 | { getBigIntReplayObservableForFunc { addressBalanceAdapter.getBalanceAt(activeAddress) } }) 25 | } 26 | 27 | fun getPendingBalanceAtActiveAddress(): Observable { 28 | val activeAddress = addressManager.getActiveAddress().hex 29 | return storedPendingAddressBalances.getOrPut(activeAddress, 30 | { getBigIntReplayObservableForFunc { addressBalanceAdapter.getPendingBalanceAt(activeAddress) } }) 31 | } 32 | 33 | private fun getBigIntReplayObservableForFunc(function: () -> BigInteger): Observable = Observable 34 | .interval(EMISSION_INTERVAL_SECONDS, TimeUnit.SECONDS) 35 | .map { function } 36 | .map { it.invoke() } 37 | .map { it.fromWeiTo(Denomination.ETHER) } 38 | .map { it.toString() } 39 | .distinctUntilChanged() 40 | .replayEmissions(1) 41 | .androidIoSchedule() 42 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ethereum/address/balance/AddressBalanceAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ethereum.address.balance 2 | 3 | import org.ethereum.geth.Address 4 | import org.ethereum.geth.Context 5 | import org.ethereum.geth.EthereumClient 6 | import java.math.BigInteger 7 | 8 | /** 9 | * Created by williamgriffiths on 17/04/2017. 10 | */ 11 | class AddressBalanceAdapter(private val ethereumClient: EthereumClient, 12 | private val context: Context) { 13 | 14 | private val DEFAULT_TO_CURRENT_BLOCK_VALUE = -1L 15 | 16 | fun getBalanceAt(addressString: String): BigInteger = try { 17 | val bigIntBalance = ethereumClient.getBalanceAt(context, Address(addressString), DEFAULT_TO_CURRENT_BLOCK_VALUE) 18 | BigInteger(bigIntBalance.string()) 19 | } catch (exception: Exception) { 20 | BigInteger("0") 21 | } 22 | 23 | fun getPendingBalanceAt(addressString: String): BigInteger = try { 24 | val bigIntBalance = ethereumClient.getPendingBalanceAt(context, Address(addressString)) 25 | BigInteger(bigIntBalance.string()) 26 | } catch (exception: Exception) { 27 | BigInteger("0") 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ethereum/address/model/DomainAddress.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ethereum.address.model 2 | 3 | import org.ethereum.geth.Address 4 | 5 | /** 6 | * Created by williamgriffiths on 16/04/2017. 7 | */ 8 | data class DomainAddress(val hex: String = "NO_ADDRESS_HEX") { 9 | 10 | companion object Factory { 11 | fun fromAddress(address: Address): DomainAddress = 12 | DomainAddress(address.hex) 13 | } 14 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ethereum/common/UnitConverterExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ethereum.common 2 | 3 | import com.github.willjgriff.ethereumwallet.ethereum.common.model.Denomination 4 | import java.math.BigDecimal 5 | import java.math.BigInteger 6 | 7 | /** 8 | * Created by williamgriffiths on 20/04/2017. 9 | * 10 | * See for unit conversion: 11 | * https://ethereum.stackexchange.com/questions/253/the-ether-denominations-are-called-finney-szabo-and-wei-what-who-are-these-na 12 | */ 13 | fun BigInteger.fromWeiTo(denomination: Denomination): BigDecimal = 14 | BigDecimal(toString()) 15 | .divide(BigDecimal(denomination.numberOfWei)) 16 | 17 | fun BigDecimal.fromDenominationToWei(denomination: Denomination): BigInteger = 18 | multiply(BigDecimal(denomination.numberOfWei)) 19 | .toBigInteger() -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ethereum/common/model/Denomination.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ethereum.common.model 2 | 3 | /** 4 | * Created by williamgriffiths on 05/05/2017. 5 | */ 6 | enum class Denomination(val numberOfWei: Long) { 7 | WEI(1), 8 | KWEI(1000), 9 | MWEI(1000000), 10 | SHANNON(1000000000), GWEI(1000000000), 11 | SZABO(1000000000000), 12 | FINNEY(1000000000000000), 13 | ETHER(1000000000000000000) 14 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ethereum/common/model/EtherAmount.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ethereum.common.model 2 | 3 | import java.math.BigDecimal 4 | 5 | /** 6 | * Created by williamgriffiths on 22/04/2017. 7 | */ 8 | data class EtherAmount(val amount: BigDecimal, val denomination: Denomination) { 9 | 10 | fun to(targetDenomination: Denomination): EtherAmount { 11 | 12 | val amountInWei: BigDecimal = amount.multiply(BigDecimal(this.denomination.numberOfWei)) 13 | var amountInTargetDenomination: BigDecimal = amountInWei.divide(BigDecimal(targetDenomination.numberOfWei)) 14 | 15 | if (isWholeNumber(amountInTargetDenomination)) { 16 | amountInTargetDenomination = amountInTargetDenomination.setScale(0) 17 | } 18 | 19 | return EtherAmount(amountInTargetDenomination, targetDenomination) 20 | } 21 | 22 | fun isWholeNumber(number: BigDecimal): Boolean { 23 | return number.remainder(BigDecimal.ONE).compareTo(BigDecimal.ZERO) == 0 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ethereum/icap/BaseConverter.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ethereum.icap 2 | 3 | import java.math.BigInteger 4 | 5 | /** 6 | * Created by Will on 01/03/2017. 7 | */ 8 | class BaseConverter { 9 | 10 | fun base16ToBase36(base16: String): String { 11 | val base16BigInt = BigInteger(base16, 16) 12 | return base16BigInt.toString(36).toLowerCase() 13 | } 14 | 15 | fun base36ToBase16(base36: String): String { 16 | val base36BigInt = BigInteger(base36, 36) 17 | return base36BigInt.toString(16).toLowerCase() 18 | } 19 | 20 | fun base36ToInteger(base36Value: String): String { 21 | var integerString = "" 22 | for (char: Char in base36Value) { 23 | integerString = integerString + BigInteger(char.toString(), 36) 24 | } 25 | return integerString 26 | } 27 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ethereum/icap/IbanChecksumUtils.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ethereum.icap 2 | 3 | import java.math.BigInteger 4 | 5 | /** 6 | * Created by Will on 11/03/2017. 7 | * 8 | * Guides: https://usersite.datalab.eu/printclass.aspx?type=wiki&id=91772 9 | * http://ethereum.stackexchange.com/questions/12016/checksum-calculation-for-icap-address 10 | * 11 | * TODO: Consider adding validation 12 | */ 13 | class IbanChecksumUtils(private val baseConverter: BaseConverter) { 14 | 15 | private val ICAP_XE_PREFIX = "XE" 16 | private val IBAN_MUTIPLIER = "00" 17 | private val IBAN_CHECK_MOD_VALUE = "97" 18 | 19 | fun createChecksum(base36Value: String): Int { 20 | if (base36Value.isNullOrBlank()) { 21 | return -1 22 | } 23 | 24 | val valueWithXePrefix = base36Value + ICAP_XE_PREFIX 25 | val valueAsInt = baseConverter.base36ToInteger(valueWithXePrefix) 26 | val valueTimes100 = valueAsInt + IBAN_MUTIPLIER 27 | val valueMod97 = BigInteger(valueTimes100).mod(BigInteger(IBAN_CHECK_MOD_VALUE)) 28 | val checksum = 98 - valueMod97.toInt() 29 | return checksum 30 | } 31 | 32 | fun verifyChecksum(base36Value: String, checksum: Int): Boolean { 33 | if (checksum < 1 || checksum > 97) { 34 | return false 35 | } 36 | 37 | val checksumString = convertChecksumToDoubleDigitString(checksum) 38 | val valueWithChecksum = baseConverter.base36ToInteger(base36Value + ICAP_XE_PREFIX) + checksumString 39 | val bigIntMod = BigInteger(valueWithChecksum).mod(BigInteger(IBAN_CHECK_MOD_VALUE)) 40 | return bigIntMod.toString(10) == "1" 41 | } 42 | 43 | fun convertChecksumToDoubleDigitString(checksum: Int): String { 44 | var checksumString = checksum.toString() 45 | if (checksum < 10) { 46 | checksumString = "0" + checksumString 47 | } 48 | return checksumString 49 | } 50 | 51 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ethereum/icap/IbanGenerator.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ethereum.icap 2 | 3 | /** 4 | * Created by Will on 12/03/2017. 5 | */ 6 | class IbanGenerator(private val baseConverter: BaseConverter, 7 | private val ibanChecksumUtils: IbanChecksumUtils) { 8 | 9 | // TODO: Consider adding validation 10 | fun createIbanFromHexAddress(address: String): String { 11 | val strippedAddress = stripLeading0x(address) 12 | val base36Address = baseConverter.base16ToBase36(strippedAddress) 13 | val checkSum = ibanChecksumUtils.createChecksum(base36Address) 14 | val doubleDigitCheckSumString = ibanChecksumUtils.convertChecksumToDoubleDigitString(checkSum) 15 | val iban = "iban:XE" + doubleDigitCheckSumString + base36Address 16 | return iban.toLowerCase() 17 | } 18 | 19 | private fun stripLeading0x(address: String): String = 20 | when (address.substring(0, 2)) { 21 | "0x" -> address.substring(2) 22 | else -> address 23 | } 24 | 25 | // TODO: Extend functionality, perhaps with a builder to add params other than just amount. 26 | fun createIbanFromHexWithAmount(address: String, amount: Double): String { 27 | var iban = createIbanFromHexAddress(address) 28 | iban = iban + "?amount=" + amount 29 | return iban 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ethereum/icap/IbanParam.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ethereum.icap 2 | 3 | import java.util.* 4 | 5 | enum class IbanParam(paramString: String) { 6 | AMOUNT("amount"), 7 | LABEL("label"), 8 | UKNOWN("unknown"); 9 | 10 | private object Static { 11 | val params: MutableMap = HashMap() 12 | } 13 | 14 | init { 15 | Static.params.put(paramString, this) 16 | } 17 | 18 | companion object { 19 | fun fromString(paramString: String): IbanParam { 20 | return Static.params.get(paramString) ?: UKNOWN 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ethereum/node/DomainNode.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ethereum.node 2 | 3 | import org.ethereum.geth.Geth 4 | import org.ethereum.geth.Node 5 | import org.ethereum.geth.NodeConfig 6 | 7 | /** 8 | * Created by williamgriffiths on 18/04/2017. 9 | */ 10 | class DomainNode(nodeFilePath: String) { 11 | 12 | val node: Node 13 | 14 | init { 15 | val nodeConfig = NodeConfig() 16 | 17 | //EthereumNetworkId: 1 = MainNet, 2 = Morden, 3 = Ropsten, 4 = Rinkeby (Note: Kovan requires the Parity client) 18 | nodeConfig.ethereumNetworkID = 1 19 | nodeConfig.ethereumDatabaseCache = 16 20 | 21 | node = Geth.newNode(nodeFilePath, nodeConfig) 22 | node.start() 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ethereum/node/NewBlockHeaderAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ethereum.node 2 | 3 | import com.github.willjgriff.ethereumwallet.ethereum.node.model.DomainBlockHeader 4 | import io.reactivex.Observable 5 | import org.ethereum.geth.Context 6 | import org.ethereum.geth.EthereumClient 7 | import org.ethereum.geth.Header 8 | import org.ethereum.geth.NewHeadHandler 9 | 10 | /** 11 | * Created by williamgriffiths on 26/04/2017. 12 | */ 13 | class NewBlockHeaderAdapter(private val ethereumClient: EthereumClient, 14 | private val context: Context) { 15 | 16 | // TODO: Find out what this number means. It may be MB of cache for lightchaindata. 17 | private val SOME_RANDOM_SAMPLING_SIZE = 16L 18 | val newBlockHeaderObservable: Observable by lazy { getNewBlockHeader() } 19 | 20 | private fun getNewBlockHeader(): Observable { 21 | return Observable 22 | .create { 23 | ethereumClient.subscribeNewHead(context, object : NewHeadHandler { 24 | override fun onNewHead(header: Header) { 25 | it.onNext(DomainBlockHeader.fromHeader(header)) 26 | } 27 | 28 | override fun onError(error: String) { 29 | it.onError(Throwable(error)) 30 | } 31 | 32 | }, SOME_RANDOM_SAMPLING_SIZE) 33 | } 34 | .share() 35 | } 36 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ethereum/node/NodeDetails.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ethereum.node 2 | 3 | import com.github.willjgriff.ethereumwallet.ethereum.node.model.DomainBlockHeader 4 | import com.github.willjgriff.ethereumwallet.utils.androidIoSchedule 5 | import com.github.willjgriff.ethereumwallet.utils.replayEmissions 6 | import io.reactivex.Observable 7 | import java.util.concurrent.TimeUnit 8 | 9 | /** 10 | * Created by williamgriffiths on 15/04/2017. 11 | */ 12 | class NodeDetails(private val nodeDetailsAdapter: NodeDetailsAdapter, 13 | private val newBlockHeaderAdapter: NewBlockHeaderAdapter) { 14 | 15 | private val EMISSION_INTERVAL_SECONDS = 1L 16 | val cachedBlockHeaderObservable: Observable by lazy { getBlockHeaderObservable() } 17 | 18 | private fun getBlockHeaderObservable(): Observable { 19 | return newBlockHeaderAdapter 20 | .newBlockHeaderObservable 21 | .throttleFirst(EMISSION_INTERVAL_SECONDS, TimeUnit.SECONDS) 22 | .replayEmissions(10) 23 | .androidIoSchedule() 24 | } 25 | 26 | fun getNodeInfoString() = nodeDetailsAdapter.getNodeInfo() 27 | 28 | fun getNumberOfPeers(): Observable = getFuncOnIntervalObservable { nodeDetailsAdapter.getNumberOfPeers() } 29 | 30 | fun getNodePeerInfoStrings(): Observable> = getFuncOnIntervalObservable { nodeDetailsAdapter.getPeersInfo() } 31 | 32 | fun getSyncProgressString(): Observable = getFuncOnIntervalObservable { nodeDetailsAdapter.getSyncProgressString() } 33 | 34 | private fun getFuncOnIntervalObservable(function: () -> T) = Observable 35 | .interval(EMISSION_INTERVAL_SECONDS, TimeUnit.SECONDS) 36 | .startWith(0) 37 | .map { function.invoke() } 38 | .distinctUntilChanged() 39 | .androidIoSchedule() 40 | 41 | } 42 | 43 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ethereum/node/NodeDetailsAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ethereum.node 2 | 3 | import org.ethereum.geth.Context 4 | import org.ethereum.geth.EthereumClient 5 | import org.ethereum.geth.Node 6 | 7 | /** 8 | * Created by Will on 16/03/2017. 9 | * 10 | * This is a likely candidate for being replaced in the future, eg for Parity or similar. 11 | * This should not return objects from the Ethereum library but just domain objects. 12 | */ 13 | class NodeDetailsAdapter(private val node: Node, 14 | private val ethereumClient: EthereumClient, 15 | private val context: Context) { 16 | 17 | fun getNodeInfo(): String = node.getNodeInfoString() 18 | 19 | fun getNumberOfPeers(): Long = node.peersInfo.size() 20 | 21 | fun getPeersInfo(): List = node.getPeersInfoStrings() 22 | 23 | fun getSyncProgressString(): String = ethereumClient.getSyncProgressString(context) 24 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ethereum/node/NodeDetailsExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ethereum.node 2 | 3 | import org.ethereum.geth.Context 4 | import org.ethereum.geth.EthereumClient 5 | import org.ethereum.geth.Node 6 | 7 | /** 8 | * Created by williamgriffiths on 18/04/2017. 9 | */ 10 | fun EthereumClient.getSyncProgressString(context: Context): String { 11 | syncProgress(context)?.let { 12 | return "\nCurrent Block: ${it.currentBlock}\n" + 13 | "Starting Block: ${it.startingBlock}\n" + 14 | "Highest Block: ${it.highestBlock}\n" + 15 | "Known States: ${it.knownStates}\n" + 16 | "Pulled Stats: ${it.pulledStates}" 17 | } 18 | return "\nSYNC PROGRESS NULL" 19 | } 20 | 21 | fun Node.getNodeInfoString(): String = 22 | "Name: ${nodeInfo.name}\n" + 23 | "Protocols: ${nodeInfo.protocols}\n" + 24 | "Ip: ${nodeInfo.ip}\n" + 25 | "Discovery Port: ${nodeInfo.discoveryPort}\n" + 26 | "Listener Address: ${nodeInfo.listenerAddress}\n" + 27 | "Listener Port: ${nodeInfo.listenerPort}\n" + 28 | "Id: ${nodeInfo.id}\n" + 29 | "Enode: ${nodeInfo.enode}\n" 30 | 31 | fun Node.getPeersInfoStrings(): List = 32 | 0L.rangeTo(peersInfo.size() - 1) 33 | .map { peersInfo.get(it) } 34 | .map { 35 | "Name: ${it.name}\n" + 36 | // "ID: ${it.id}\n" + 37 | "Remote Address: ${it.remoteAddress}\n" + 38 | "Local Address:${it.localAddress}\n" + 39 | "Caps: ${it.caps}\n" 40 | } 41 | .toList() -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ethereum/node/model/DomainBlockHeader.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ethereum.node.model 2 | 3 | import org.ethereum.geth.Header 4 | 5 | /** 6 | * Created by williamgriffiths on 16/04/2017. 7 | */ 8 | data class DomainBlockHeader(val hashHex: String, 9 | val time: Long, 10 | val blockNumber: Long) { 11 | 12 | companion object Factory { 13 | fun fromHeader(header: Header): DomainBlockHeader = 14 | DomainBlockHeader(header.hash.hex, header.time, header.number) 15 | } 16 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ethereum/transaction/SendTransactionGenerator.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ethereum.transaction 2 | 3 | import com.github.willjgriff.ethereumwallet.ethereum.common.model.Denomination 4 | import com.github.willjgriff.ethereumwallet.ethereum.common.model.EtherAmount 5 | import com.github.willjgriff.ethereumwallet.ethereum.icap.BaseConverter 6 | import com.github.willjgriff.ethereumwallet.ethereum.icap.IbanParam 7 | import com.github.willjgriff.ethereumwallet.ethereum.transaction.model.SendTransaction 8 | import java.math.BigDecimal 9 | 10 | /** 11 | * Created by Will on 12/03/2017. 12 | */ 13 | class SendTransactionGenerator(private val baseConverter: BaseConverter) { 14 | 15 | private val IBAN_PREFIX_LOWERCASE = "iban:" 16 | private val IBAN_PREFIX_XE_LOWERCASE = "xe" 17 | private val IBAN_PREFIX_LENGTH_INCLUDING_CHECKSUM = 9 18 | private val IBAN_MAX_LENGTH = 35 19 | private val IBAN_PARAMS_START_DELIMITER = "?" 20 | private val IBAN_PARAM_DELIMITER = "&" 21 | private val IBAN_PARAM_KEY_VALUE_DELIMITER = "=" 22 | private val HEX_PREFIX = "0x" 23 | 24 | fun getSendPaymentFromIban(iban: String): SendTransaction { 25 | return if (validEthereumIban(iban)) { 26 | val hexAddress = getHexAddressFromIban(iban) 27 | val params = getParamsFromIban(iban) 28 | 29 | val amountAsBigDecimal = BigDecimal(params[IbanParam.AMOUNT] ?: "0") 30 | val etherAmount = EtherAmount(amountAsBigDecimal, Denomination.WEI) 31 | 32 | SendTransaction(hexAddress, 33 | etherAmount, 34 | params[IbanParam.LABEL] ?: "") 35 | } else { 36 | SendTransaction() 37 | } 38 | } 39 | 40 | private fun validEthereumIban(iban: String): Boolean = iban.length >= IBAN_MAX_LENGTH + IBAN_PREFIX_LOWERCASE.length 41 | && iban.substring(0, 7).toLowerCase() == IBAN_PREFIX_LOWERCASE + IBAN_PREFIX_XE_LOWERCASE 42 | 43 | // TODO: Add some extra checks, eg there could be "=" in the params 44 | private fun getParamsFromIban(iban: String): HashMap { 45 | return iban 46 | .substringAfter(IBAN_PARAMS_START_DELIMITER, "") 47 | .split(IBAN_PARAM_DELIMITER) 48 | .filter { it.contains(IBAN_PARAM_KEY_VALUE_DELIMITER) } 49 | .map { it.split(IBAN_PARAM_KEY_VALUE_DELIMITER, limit = 2) } 50 | .fold(HashMap()) { acc, next -> 51 | acc.put(IbanParam.fromString(next.get(0)), next.get(1)) 52 | acc 53 | } 54 | } 55 | 56 | private fun getHexAddressFromIban(iban: String): String { 57 | val base36Value = iban 58 | .substring(IBAN_PREFIX_LENGTH_INCLUDING_CHECKSUM) 59 | .substringBefore(IBAN_PARAMS_START_DELIMITER) 60 | return HEX_PREFIX + baseConverter.base36ToBase16(base36Value) 61 | } 62 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ethereum/transaction/TransactionAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ethereum.transaction 2 | 3 | import com.github.willjgriff.ethereumwallet.ethereum.address.asList 4 | import com.github.willjgriff.ethereumwallet.ethereum.address.getGethAccountFromAddress 5 | import com.github.willjgriff.ethereumwallet.ethereum.address.model.DomainAddress 6 | import com.github.willjgriff.ethereumwallet.ethereum.common.model.Denomination 7 | import com.github.willjgriff.ethereumwallet.ethereum.transaction.model.SendTransaction 8 | import org.ethereum.geth.* 9 | import timber.log.Timber 10 | 11 | /** 12 | * Created by williamgriffiths on 17/04/2017. 13 | */ 14 | class TransactionAdapter(private val keyStore: KeyStore, 15 | private val ethereumClient: EthereumClient, 16 | private val context: Context) { 17 | 18 | val STANDARD_TRANSACTION_GAS_LIMIT = 21000L 19 | val MAIN_NET_CHAIN_ID_NUMBER = 1L 20 | 21 | fun submitTransaction(sendTransaction: SendTransaction, fromAddress: DomainAddress, password: String): Boolean { 22 | val etherAmountInWei = sendTransaction.etherAmount.to(Denomination.WEI) 23 | val etherAmountAsBigInt = BigInt(etherAmountInWei.amount.toLong()) 24 | 25 | return try { 26 | val unsignedTransaction = getUnsignedTransaction(etherAmountAsBigInt, fromAddress, sendTransaction.toAddress) 27 | val signedTransaction = signTransaction(fromAddress, password, unsignedTransaction) 28 | ethereumClient.sendTransaction(context, signedTransaction) 29 | true 30 | } catch (exception: Exception) { 31 | Timber.e(exception, "Error submitting tx") 32 | false 33 | } 34 | } 35 | 36 | private fun signTransaction(fromAddress: DomainAddress, password: String, unsignedTransaction: Transaction): Transaction { 37 | val gethAccount = keyStore.accounts.asList().getGethAccountFromAddress(fromAddress) 38 | val mainNetChainIdentifier = BigInt(MAIN_NET_CHAIN_ID_NUMBER) 39 | return keyStore.signTxPassphrase(gethAccount, password, unsignedTransaction, mainNetChainIdentifier) 40 | } 41 | 42 | private fun getUnsignedTransaction(amount: BigInt, fromAddress: DomainAddress, toAddressString: String): Transaction { 43 | val gethAccount = keyStore.accounts.asList().getGethAccountFromAddress(fromAddress) 44 | val nonce = ethereumClient.getPendingNonceAt(context, gethAccount.address) // The nonce increases by one for each transaction 45 | val toAddress = Address(toAddressString) 46 | val gasLimit = BigInt(STANDARD_TRANSACTION_GAS_LIMIT) // More than is required to execute the transaction on the Blockchain, 2100 from MyEtherWallet 47 | val gasPrice = ethereumClient.suggestGasPrice(context) // The cost in Ether for each unit of Gas, in Wei, average is 20000000000 at time of writing. 48 | 49 | // Transaction(nonce uint64, to common.Address, amount, gasLimit, gasPrice *big.Int, data []byte) 50 | return Transaction(nonce, toAddress, amount, gasLimit, gasPrice, null) 51 | } 52 | 53 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ethereum/transaction/TransactionManager.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ethereum.transaction 2 | 3 | import com.github.willjgriff.ethereumwallet.ethereum.address.AddressManager 4 | import com.github.willjgriff.ethereumwallet.ethereum.transaction.model.SendTransaction 5 | 6 | /** 7 | * Created by williamgriffiths on 16/04/2017. 8 | */ 9 | class TransactionManager(private val addressManager: AddressManager, 10 | private val transactionAdapter: TransactionAdapter) { 11 | 12 | fun executeTransaction(sendTransaction: SendTransaction, password: String): Boolean { 13 | 14 | return transactionAdapter.submitTransaction(sendTransaction, addressManager.getActiveAddress(), password) 15 | 16 | // Store transaction somewhere, or always get them from the network / etherscan.io 17 | 18 | } 19 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ethereum/transaction/model/SendTransaction.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ethereum.transaction.model 2 | 3 | import com.github.willjgriff.ethereumwallet.ethereum.common.model.Denomination 4 | import com.github.willjgriff.ethereumwallet.ethereum.common.model.EtherAmount 5 | import java.math.BigDecimal 6 | 7 | /** 8 | * Created by Will on 12/03/2017. 9 | */ 10 | data class SendTransaction(val toAddress: String = "", 11 | val etherAmount: EtherAmount = EtherAmount(BigDecimal("0"), Denomination.WEI), 12 | val label: String = "") -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ethereum/transactions/TransactionsManager.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ethereum.transactions 2 | 3 | import com.github.willjgriff.ethereumwallet.ethereum.address.AddressManager 4 | import com.github.willjgriff.ethereumwallet.ethereum.node.NewBlockHeaderAdapter 5 | import com.github.willjgriff.ethereumwallet.ethereum.transactions.model.DomainTransaction 6 | import com.github.willjgriff.ethereumwallet.ethereum.transactions.storage.TransactionsStorage 7 | import com.github.willjgriff.ethereumwallet.utils.androidIoSchedule 8 | import com.github.willjgriff.ethereumwallet.utils.resettablelazy.ResettableLazyManager 9 | import com.github.willjgriff.ethereumwallet.utils.resettablelazy.resettableLazy 10 | import io.reactivex.Observable 11 | 12 | /** 13 | * Created by williamgriffiths on 19/04/2017. 14 | */ 15 | class TransactionsManager(private val transactionsStorage: TransactionsStorage, 16 | private val transactionsAdapter: TransactionsAdapter, 17 | private val newBlockHeaderAdapter: NewBlockHeaderAdapter, 18 | private val addressManager: AddressManager) { 19 | 20 | private val NUMBER_OF_BLOCKS_TO_SEARCH = 500L 21 | val blocksSearchedLogger = BlocksSearchedLogger(transactionsStorage) 22 | 23 | private val resettableLazyManager = ResettableLazyManager() 24 | val transactionsObservable: Observable by resettableLazy(resettableLazyManager) { 25 | getTransactions() 26 | } 27 | 28 | /** 29 | * Note that [DomainTransaction]'s are emitted in the order they are found, not in order of when they were made. 30 | * The caller must order the transactions themselves if necessary, eg by time. 31 | * 32 | * TODO: This should continue until either the transaction block to search to has been reached or the page 33 | * max has been reached. Also, we should probably only search to the block when the address was created. 34 | */ 35 | private fun getTransactions(): Observable { 36 | val storedTransactions = Observable.fromIterable(transactionsStorage.getStoredTransactions()) 37 | val unstoredPastTransactions = getPastBlocksTransactions() 38 | val unstoredFutureTransactions = getCurrentBlockTransactions() 39 | 40 | return storedTransactions 41 | .mergeWith(unstoredPastTransactions) 42 | .mergeWith(unstoredFutureTransactions) 43 | .androidIoSchedule() 44 | .replay() 45 | .autoConnect() 46 | } 47 | 48 | private fun getCurrentBlockTransactions(): Observable { 49 | val address = addressManager.getActiveAddress() 50 | 51 | return newBlockHeaderAdapter 52 | .newBlockHeaderObservable 53 | .flatMap { 54 | transactionsAdapter.getTransactionsForAddressInBlock(address, it.blockNumber, 55 | { blocksSearchedLogger.blockSearched(it) }) 56 | } 57 | .doOnNext { transactionsStorage.storeTransaction(it) } 58 | } 59 | 60 | private fun getPastBlocksTransactions(): Observable { 61 | val currentBlock = transactionsAdapter.getCurrentBlock() 62 | 63 | return currentBlock 64 | .toObservable() 65 | .flatMap { searchAndStoreTransactions(it) } 66 | } 67 | 68 | private fun searchAndStoreTransactions(currentBlock: Long): Observable { 69 | val address = addressManager.getActiveAddress() 70 | val blocksToSearch = blocksSearchedLogger.getBlocksToSearchFromTopBlock(currentBlock, NUMBER_OF_BLOCKS_TO_SEARCH) 71 | 72 | return Observable 73 | .fromIterable(blocksToSearch) 74 | .flatMap { 75 | transactionsAdapter.getTransactionsInBlockRange(address, it.upperBlock, it.rangeExclusive(), 76 | { blocksSearchedLogger.blockSearched(it) } 77 | ) 78 | } 79 | .doOnNext { transactionsStorage.storeTransaction(it) } 80 | } 81 | 82 | fun clearAndRestart() { 83 | transactionsStorage.deleteStoredData() 84 | blocksSearchedLogger.resetBlockRangesSearched() 85 | resettableLazyManager.reset() 86 | } 87 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ethereum/transactions/model/BlockRange.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ethereum.transactions.model 2 | 3 | /** 4 | * Created by williamgriffiths on 21/04/2017. 5 | */ 6 | data class BlockRange(val upperBlock: Long, var lowerBlock: Long) { 7 | 8 | fun rangeExclusive() = upperBlock - lowerBlock + 1 9 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ethereum/transactions/model/DomainBlock.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ethereum.transactions.model 2 | 3 | import org.ethereum.geth.Block 4 | 5 | /** 6 | * Created by williamgriffiths on 25/04/2017. 7 | */ 8 | class DomainBlock(val block: Block?) -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ethereum/transactions/model/DomainTransaction.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ethereum.transactions.model 2 | 3 | import com.github.willjgriff.ethereumwallet.ethereum.address.model.DomainAddress 4 | import java.math.BigInteger 5 | 6 | /** 7 | * Created by williamgriffiths on 19/04/2017. 8 | */ 9 | class DomainTransaction(val fromAddress: DomainAddress, 10 | val toAddress: DomainAddress, 11 | val value: BigInteger, 12 | val time: Long) -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ethereum/transactions/storage/SharedPrefsTransactionsStorage.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ethereum.transactions.storage 2 | 3 | import android.content.Context 4 | import android.preference.PreferenceManager 5 | import com.github.willjgriff.ethereumwallet.data.SharedPreferencesManager 6 | import com.github.willjgriff.ethereumwallet.ethereum.transactions.model.BlockRange 7 | import com.github.willjgriff.ethereumwallet.ethereum.transactions.model.DomainTransaction 8 | import com.google.gson.Gson 9 | import com.google.gson.GsonBuilder 10 | import com.google.gson.reflect.TypeToken 11 | 12 | /** 13 | * Created by williamgriffiths on 24/04/2017. 14 | * 15 | * TODO: This needs to be thread safe. Accessing SharedPreferences from two threads 16 | * at the same time throws a ConcurrentModificationException 17 | */ 18 | class SharedPrefsTransactionsStorage(context: Context): TransactionsStorage { 19 | 20 | private val TRANSACTIONS_KEY: String = "com.github.willjgriff.ethereumwallet.ethereum.transactions.storage.SharedPrefsTransactionsStorage;TRANSACTIONS_KEY" 21 | private val BLOCKS_SEARCHED_KEY = "com.github.willjgriff.ethereumwallet.ethereum.transactions.storage.SharedPrefsTransactionsStorage;BLOCKS_SEARCHED_KEY" 22 | 23 | private val gson: Gson = GsonBuilder().create() 24 | private val sharedPreferencesManager = SharedPreferencesManager(PreferenceManager.getDefaultSharedPreferences(context), gson) 25 | private val listDomainTransactionsType: TypeToken> = object: TypeToken>() {} 26 | private val transactions: MutableList = sharedPreferencesManager 27 | .readComplexObjectFromPreferences(TRANSACTIONS_KEY, listDomainTransactionsType)?.toMutableList() ?: mutableListOf() 28 | 29 | override fun storeTransaction(transaction: DomainTransaction) { 30 | transactions.add(transaction) 31 | sharedPreferencesManager.writeObjectToPreferences(TRANSACTIONS_KEY, transactions) 32 | } 33 | 34 | override fun getStoredTransactions(): List = transactions 35 | 36 | override fun storeBlocksSearched(blocksSearched: List) { 37 | // I think copying the list prevents a ConcurrentModificationException with Gson. 38 | sharedPreferencesManager.writeObjectToPreferences(BLOCKS_SEARCHED_KEY, ArrayList(blocksSearched)) 39 | } 40 | 41 | override fun getBlocksSearched(): List { 42 | val listBlockRangesType: TypeToken> = object : TypeToken>() {} 43 | return sharedPreferencesManager 44 | .readComplexObjectFromPreferences(BLOCKS_SEARCHED_KEY, listBlockRangesType) 45 | ?: listOf() 46 | } 47 | 48 | override fun deleteStoredData() { 49 | transactions.clear() 50 | sharedPreferencesManager.removeObjectFromPrefs(TRANSACTIONS_KEY) 51 | sharedPreferencesManager.removeObjectFromPrefs(BLOCKS_SEARCHED_KEY) 52 | } 53 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ethereum/transactions/storage/TransactionsStorage.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ethereum.transactions.storage 2 | 3 | import com.github.willjgriff.ethereumwallet.ethereum.transactions.model.BlockRange 4 | import com.github.willjgriff.ethereumwallet.ethereum.transactions.model.DomainTransaction 5 | 6 | /** 7 | * Created by williamgriffiths on 24/04/2017. 8 | * 9 | * TODO: We may want to extend this to save transactions and blocks searched for different addresses 10 | */ 11 | interface TransactionsStorage { 12 | 13 | fun storeTransaction(transaction: DomainTransaction) 14 | 15 | fun getStoredTransactions(): List 16 | 17 | fun storeBlocksSearched(blocksSearched: List) 18 | 19 | fun getBlocksSearched(): List 20 | 21 | fun deleteStoredData() 22 | 23 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/mvp/BaseMvpAlertDialog.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.mvp 2 | 3 | import android.content.Context 4 | import android.support.v7.app.AlertDialog 5 | 6 | /** 7 | * Created by williamgriffiths on 03/05/2017. 8 | */ 9 | abstract class BaseMvpAlertDialog>(context: Context) : AlertDialog(context) { 10 | 11 | protected abstract val mvpView: VIEW 12 | 13 | protected abstract val presenter: PRESENTER 14 | 15 | override fun onAttachedToWindow() { 16 | super.onAttachedToWindow() 17 | presenter.bindView(mvpView) 18 | presenter.viewReady() 19 | } 20 | 21 | override fun onDetachedFromWindow() { 22 | super.onDetachedFromWindow() 23 | presenter.unbindView() 24 | } 25 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/mvp/BaseMvpControllerKotlin.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.mvp 2 | 3 | import android.view.View 4 | import com.bluelinelabs.conductor.rxlifecycle2.RxController 5 | 6 | /** 7 | * Created by williamgriffiths on 03/05/2017. 8 | */ 9 | abstract class BaseMvpControllerKotlin> : RxController() { 10 | 11 | protected abstract val mvpView: VIEW 12 | 13 | protected abstract val presenter: PRESENTER 14 | 15 | override fun onAttach(view: View) { 16 | super.onAttach(view) 17 | presenter.bindView(mvpView) 18 | presenter.viewReady() 19 | } 20 | 21 | override fun onDetach(view: View) { 22 | super.onDetach(view) 23 | presenter.unbindView() 24 | } 25 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/mvp/BaseMvpPresenterKotlin.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.mvp 2 | 3 | import io.reactivex.disposables.CompositeDisposable 4 | import io.reactivex.disposables.Disposable 5 | 6 | /** 7 | * Created by williamgriffiths on 03/05/2017. 8 | */ 9 | abstract class BaseMvpPresenterKotlin : MvpPresenter { 10 | 11 | var view: VIEW? = null 12 | private set 13 | private val compositeDisposable: CompositeDisposable = CompositeDisposable() 14 | 15 | override fun bindView(mvpView: VIEW) { 16 | view = mvpView 17 | } 18 | 19 | override fun unbindView() { 20 | compositeDisposable.clear() 21 | releaseViewReferences() 22 | /** 23 | * This is only necessary if we retain the Presenter beyond the life of 24 | * the View, we will do it anyway in case there's something I've 25 | * overlooked or we wish to retain the Presenters in the future 26 | */ 27 | view = null 28 | } 29 | 30 | /** 31 | * If we wish to retain this Presenter beyond the life of the Controller or View that 32 | * it is associated too, override this method and set to null any View references / 33 | * runtime dependencies that can no longer be used to prevent potential memory leaks. 34 | */ 35 | open fun releaseViewReferences() { 36 | 37 | } 38 | 39 | fun addDisposable(disposable: Disposable) { 40 | compositeDisposable.add(disposable) 41 | } 42 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/mvp/MvpPresenter.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.mvp 2 | 3 | /** 4 | * Created by williamgriffiths on 04/05/2017. 5 | */ 6 | interface MvpPresenter { 7 | 8 | fun bindView(mvpView: VIEW) 9 | 10 | fun unbindView() 11 | 12 | /** 13 | * When implementing this method, asynchronous operations should be converted to observables 14 | * and their disposables should be added with [BaseMvpPresenterKotlin.addDisposable]. 15 | * This will ensure callbacks are only executed between [BaseMvpPresenterKotlin.bindView] 16 | * and [BaseMvpPresenterKotlin.unbindView], preventing View access when it isn't available. 17 | */ 18 | fun viewReady() 19 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ui/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ui 2 | 3 | import android.os.Bundle 4 | import android.support.v7.app.AppCompatActivity 5 | import com.bluelinelabs.conductor.Conductor 6 | import com.bluelinelabs.conductor.Controller 7 | import com.bluelinelabs.conductor.Router 8 | import com.bluelinelabs.conductor.RouterTransaction 9 | import com.bluelinelabs.conductor.changehandler.FadeChangeHandler 10 | import com.github.willjgriff.ethereumwallet.R 11 | import com.github.willjgriff.ethereumwallet.di.AppInjector 12 | import com.github.willjgriff.ethereumwallet.ethereum.address.AddressManager 13 | import com.github.willjgriff.ethereumwallet.ui.navigation.NavigationController 14 | import com.github.willjgriff.ethereumwallet.ui.screens.createaccount.PreNavigationCreateAccountController 15 | import kotlinx.android.synthetic.main.activity_main.* 16 | import javax.inject.Inject 17 | 18 | /** 19 | * Created by williamgriffiths on 04/05/2017. 20 | */ 21 | class MainActivity : AppCompatActivity() { 22 | 23 | @Inject lateinit var addressManager: AddressManager 24 | private lateinit var router: Router 25 | 26 | init { 27 | AppInjector.appComponent.inject(this) 28 | } 29 | 30 | override fun onCreate(savedInstanceState: Bundle?) { 31 | super.onCreate(savedInstanceState) 32 | setContentView(R.layout.activity_main) 33 | 34 | router = Conductor.attachRouter(this, activity_main_controller_container, savedInstanceState) 35 | if (!router.hasRootController()) { 36 | checkForAccountAndSetRoot() 37 | } 38 | } 39 | 40 | private fun checkForAccountAndSetRoot() { 41 | if (addressManager.hasAddress()) { 42 | setConductorRoot(NavigationController()) 43 | } else { 44 | setConductorRoot(PreNavigationCreateAccountController()) 45 | } 46 | } 47 | 48 | private fun setConductorRoot(controller: Controller) { 49 | router.setRoot(RouterTransaction.with(controller) 50 | .pushChangeHandler(FadeChangeHandler()) 51 | .popChangeHandler(FadeChangeHandler())) 52 | } 53 | 54 | override fun onBackPressed() { 55 | if (!router.handleBack()) { 56 | super.onBackPressed() 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ui/error/ErrorDisplayer.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ui.error 2 | 3 | import android.content.Context 4 | import android.support.design.widget.Snackbar 5 | import android.view.View 6 | import com.github.willjgriff.ethereumwallet.R 7 | import com.github.willjgriff.ethereumwallet.utils.isConnected 8 | import java.io.IOException 9 | 10 | /** 11 | * Created by williamgriffiths on 04/05/2017. 12 | * 13 | * TODO: This should be injected into the base mvp classes. 14 | */ 15 | object ErrorDisplayer { 16 | 17 | fun displayError(view: View, throwable: Throwable) { 18 | val errorText = getErrorMessage(view.context, throwable) 19 | Snackbar.make(view, errorText, Snackbar.LENGTH_LONG).show() 20 | } 21 | 22 | private fun getErrorMessage(context: Context, throwable: Throwable): String { 23 | if (internetDisconnected(context)) { 24 | return context.getString(R.string.error_internet_disconnected) 25 | } else if (isNetworkError(throwable)) { 26 | return context.getString(R.string.error_network) 27 | } else { 28 | return context.getString(R.string.error_unknown) 29 | } 30 | } 31 | 32 | private fun internetDisconnected(context: Context): Boolean { 33 | return !context.isConnected() 34 | } 35 | 36 | private fun isNetworkError(throwable: Throwable): Boolean { 37 | return throwable is IOException 38 | } 39 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ui/navigation/NavigationControllerFactory.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ui.navigation 2 | 3 | import android.view.MenuItem 4 | import com.bluelinelabs.conductor.Controller 5 | import com.github.willjgriff.ethereumwallet.R 6 | import com.github.willjgriff.ethereumwallet.ui.screens.nodedetails.NodeDetailsController 7 | import com.github.willjgriff.ethereumwallet.ui.screens.receive.ReceiveController 8 | import com.github.willjgriff.ethereumwallet.ui.screens.send.SendController 9 | import com.github.willjgriff.ethereumwallet.ui.screens.settings.SettingsController 10 | import com.github.willjgriff.ethereumwallet.ui.screens.transactions.TransactionsController 11 | 12 | /** 13 | * Created by williamgriffiths on 04/05/2017. 14 | */ 15 | class NavigationControllerFactory { 16 | 17 | fun getController(item: MenuItem): Controller? { 18 | when (item.itemId) { 19 | R.id.navigation_transactions -> return TransactionsController() 20 | R.id.navigation_send -> return SendController() 21 | R.id.navigation_receive -> return ReceiveController() 22 | R.id.navigation_status -> return NodeDetailsController() 23 | R.id.navigation_settings -> return SettingsController() 24 | else -> return null 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ui/navigation/NavigationToolbarListener.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ui.navigation 2 | 3 | /** 4 | * Created by williamgriffiths on 04/05/2017. 5 | */ 6 | interface NavigationToolbarListener { 7 | 8 | fun setToolbarTitle(toolbarTitle: CharSequence) 9 | 10 | fun setBalance(balance: String) 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ui/screens/createaccount/CreateAccountCompletedListener.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ui.screens.createaccount 2 | 3 | /** 4 | * Created by williamgriffiths on 03/05/2017. 5 | */ 6 | interface CreateAccountCompletedListener { 7 | 8 | fun onAccountCreated() 9 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ui/screens/createaccount/CreateAccountController.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ui.screens.createaccount 2 | 3 | import android.view.LayoutInflater 4 | import android.view.View 5 | import android.view.ViewGroup 6 | import com.github.willjgriff.ethereumwallet.R 7 | import com.github.willjgriff.ethereumwallet.mvp.BaseMvpControllerKotlin 8 | import com.github.willjgriff.ethereumwallet.ui.screens.createaccount.di.injectPresenter 9 | import com.github.willjgriff.ethereumwallet.ui.screens.createaccount.mvp.CreateAccountPresenter 10 | import com.github.willjgriff.ethereumwallet.ui.screens.createaccount.mvp.CreateAccountView 11 | import com.jakewharton.rxbinding2.view.RxView 12 | import kotlinx.android.synthetic.main.controller_create_account.view.* 13 | import javax.inject.Inject 14 | 15 | /** 16 | * Created by williamgriffiths on 03/05/2017. 17 | */ 18 | class CreateAccountController : BaseMvpControllerKotlin(), CreateAccountView { 19 | 20 | override val mvpView: CreateAccountView 21 | get() = this 22 | @Inject lateinit override var presenter: CreateAccountPresenter 23 | 24 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup): View { 25 | injectPresenter() 26 | val view = inflater.inflate(R.layout.controller_create_account, container, false) 27 | setPresenterObservables(view) 28 | return view 29 | } 30 | 31 | private fun setPresenterObservables(view: View) { 32 | val passwordField = view.controller_create_account_password_text_input 33 | 34 | val passwordChanged = passwordField.textChangedObservable 35 | val passwordValid = passwordField.textValidObservable 36 | val submitClicked = RxView.clicks(view.controller_create_account_create_button).share() 37 | passwordField.setCheckValidationTrigger(submitClicked) 38 | 39 | presenter.apply { 40 | this.passwordChanged = passwordChanged 41 | this.passwordValid = passwordValid 42 | this.submitClicked = submitClicked 43 | } 44 | } 45 | 46 | override fun openWallet() { 47 | if (targetController is CreateAccountCompletedListener) { 48 | (targetController as CreateAccountCompletedListener).onAccountCreated() 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ui/screens/createaccount/PreNavigationCreateAccountController.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ui.screens.createaccount 2 | 3 | import android.support.v7.widget.Toolbar 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import com.bluelinelabs.conductor.Controller 8 | import com.bluelinelabs.conductor.RouterTransaction 9 | import com.bluelinelabs.conductor.changehandler.FadeChangeHandler 10 | import com.github.willjgriff.ethereumwallet.R 11 | import com.github.willjgriff.ethereumwallet.ui.navigation.NavigationController 12 | import kotlinx.android.synthetic.main.controller_create_account_pre_navigation.view.* 13 | 14 | /** 15 | * Created by williamgriffiths on 03/05/2017. 16 | */ 17 | class PreNavigationCreateAccountController : Controller(), CreateAccountCompletedListener { 18 | 19 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup): View { 20 | val view = inflater.inflate(R.layout.controller_create_account_pre_navigation, container, false) 21 | 22 | addCreateAccountController(view) 23 | (view.controller_create_account_pre_nav_toolbar as Toolbar).setTitle(R.string.controller_create_account_title) 24 | 25 | return view 26 | } 27 | 28 | private fun addCreateAccountController(view: View) { 29 | val createAccountController = CreateAccountController() 30 | createAccountController.targetController = this 31 | getChildRouter(view.controller_create_account_pre_nav_container) 32 | .pushController(RouterTransaction.with(createAccountController)) 33 | } 34 | 35 | override fun onAccountCreated() { 36 | router.setRoot(RouterTransaction.with(NavigationController()) 37 | .pushChangeHandler(FadeChangeHandler()) 38 | .popChangeHandler(FadeChangeHandler())) 39 | } 40 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ui/screens/createaccount/SettingsCreateAccountController.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ui.screens.createaccount 2 | 3 | import android.view.LayoutInflater 4 | import android.view.View 5 | import android.view.ViewGroup 6 | import com.bluelinelabs.conductor.Controller 7 | import com.bluelinelabs.conductor.RouterTransaction 8 | import com.github.willjgriff.ethereumwallet.R 9 | import kotlinx.android.synthetic.main.controller_create_account_settings.view.* 10 | 11 | /** 12 | * Created by williamgriffiths on 03/05/2017. 13 | */ 14 | class SettingsCreateAccountController : Controller(), CreateAccountCompletedListener { 15 | 16 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup): View { 17 | val view = inflater.inflate(R.layout.controller_create_account_settings, container, false) 18 | addCreateAccountController(view) 19 | setToolbarTitle() 20 | return view 21 | } 22 | 23 | private fun addCreateAccountController(view: View) { 24 | val createAccountController = CreateAccountController() 25 | createAccountController.targetController = this 26 | getChildRouter(view.controller_create_account_settings_container).pushController(RouterTransaction.with(createAccountController)) 27 | } 28 | 29 | private fun setToolbarTitle() { 30 | if (targetController is SettingsToolbarListener) { 31 | val settingsTitle = applicationContext?.getString(R.string.controller_create_account_title) ?: "" 32 | (targetController as SettingsToolbarListener).setToolbarTitle(settingsTitle) 33 | } 34 | } 35 | 36 | override fun onAccountCreated() { 37 | router.popCurrentController() 38 | } 39 | 40 | interface SettingsToolbarListener { 41 | fun setToolbarTitle(title: CharSequence) 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ui/screens/createaccount/di/CreateAccountComponent.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ui.screens.createaccount.di 2 | 3 | import com.github.willjgriff.ethereumwallet.di.AppComponent 4 | import com.github.willjgriff.ethereumwallet.di.AppInjector 5 | import com.github.willjgriff.ethereumwallet.di.ControllerScope 6 | import com.github.willjgriff.ethereumwallet.ui.screens.createaccount.CreateAccountController 7 | import dagger.Component 8 | 9 | /** 10 | * Created by williamgriffiths on 03/05/2017. 11 | */ 12 | @Component(dependencies = arrayOf(AppComponent::class)) 13 | @ControllerScope 14 | interface CreateAccountComponent { 15 | 16 | fun inject(createAccountController: CreateAccountController) 17 | } 18 | 19 | fun CreateAccountController.injectPresenter() { 20 | DaggerCreateAccountComponent 21 | .builder() 22 | .appComponent(AppInjector.appComponent) 23 | .build() 24 | .inject(this) 25 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ui/screens/createaccount/mvp/CreateAccountPresenter.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ui.screens.createaccount.mvp 2 | 3 | import com.github.willjgriff.ethereumwallet.ethereum.address.AddressManager 4 | import com.github.willjgriff.ethereumwallet.mvp.BaseMvpPresenterKotlin 5 | import io.reactivex.Observable 6 | import io.reactivex.functions.BiFunction 7 | import javax.inject.Inject 8 | 9 | /** 10 | * Created by williamgriffiths on 03/05/2017. 11 | */ 12 | class CreateAccountPresenter @Inject constructor(private val addressManager: AddressManager) : BaseMvpPresenterKotlin() { 13 | 14 | lateinit var passwordChanged: Observable 15 | lateinit var passwordValid: Observable 16 | lateinit var submitClicked: Observable 17 | 18 | override fun viewReady() { 19 | setupSubmitObservable() 20 | } 21 | 22 | private fun setupSubmitObservable() { 23 | // For adding a confirm passwordChanged field. 24 | // Observable validCombined = Observable 25 | // .combineLatest(mValidMessage, mValidPhone, mValidEmail, 26 | // (messageValid, phoneValid, emailValid) -> messageValid && phoneValid && emailValid) 27 | // .distinctUntilChanged(); 28 | 29 | submitClicked 30 | .withLatestFrom(passwordValid, BiFunction { _, validPassword -> validPassword }) 31 | .filter { it == true } 32 | .flatMap { passwordChanged } 33 | // This is to prevent spamming and creating many accounts 34 | .first("") 35 | .subscribe { password -> setupWallet(password) } 36 | } 37 | 38 | private fun setupWallet(password: String) { 39 | addressManager.createActiveAddress(password) 40 | view?.openWallet() 41 | } 42 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ui/screens/createaccount/mvp/CreateAccountView.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ui.screens.createaccount.mvp 2 | 3 | /** 4 | * Created by williamgriffiths on 03/05/2017. 5 | */ 6 | interface CreateAccountView { 7 | 8 | fun openWallet() 9 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ui/screens/nodedetails/di/NodeDetailsComponent.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ui.screens.nodedetails.di 2 | 3 | import com.github.willjgriff.ethereumwallet.ui.screens.nodedetails.NodeDetailsController 4 | import com.github.willjgriff.ethereumwallet.di.AppComponent 5 | import com.github.willjgriff.ethereumwallet.di.ControllerScope 6 | import dagger.Component 7 | 8 | /** 9 | * Created by Will on 21/03/2017. 10 | */ 11 | @Component(dependencies = arrayOf(AppComponent::class)) 12 | @ControllerScope 13 | interface NodeDetailsComponent { 14 | 15 | fun inject(nodeDetailsController: NodeDetailsController) 16 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ui/screens/nodedetails/list/NodeDetailsHeadersAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ui.screens.nodedetails.list 2 | 3 | import android.support.v7.widget.RecyclerView 4 | import android.view.View 5 | import android.view.ViewGroup 6 | import android.widget.TextView 7 | import com.github.willjgriff.ethereumwallet.R 8 | import com.github.willjgriff.ethereumwallet.ui.utils.inflate 9 | import kotlinx.android.synthetic.main.view_node_details_header_item.view.* 10 | 11 | /** 12 | * Created by williamgriffiths on 11/04/2017. 13 | */ 14 | class NodeDetailsHeadersAdapter(private val adapterListener: NodeStatusHeadersAdapterListener) 15 | : RecyclerView.Adapter() { 16 | 17 | private val headers: MutableList = mutableListOf() 18 | 19 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NodeStatusHeaderViewHolder = 20 | NodeStatusHeaderViewHolder(parent.inflate(R.layout.view_node_details_header_item)) 21 | 22 | override fun getItemCount(): Int = 23 | headers.size 24 | 25 | override fun onBindViewHolder(holder: NodeStatusHeaderViewHolder, position: Int) = 26 | holder.bind(headers[position]) 27 | 28 | fun addHeaderHash(headerHash: String) { 29 | headers.add(headerHash) 30 | notifyItemInserted(headers.size - 1) 31 | adapterListener.headerInserted(headers.size - 1) 32 | } 33 | 34 | class NodeStatusHeaderViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { 35 | val headerHash: TextView by lazy { itemView.view_node_status_header_item_hash } 36 | fun bind(headerHash: String) { 37 | this.headerHash.text = headerHash 38 | } 39 | } 40 | 41 | interface NodeStatusHeadersAdapterListener { 42 | fun headerInserted(position: Int) 43 | } 44 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ui/screens/nodedetails/list/NodeDetailsPeersAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ui.screens.nodedetails.list 2 | 3 | import android.support.v7.widget.RecyclerView 4 | import android.view.View 5 | import android.view.ViewGroup 6 | import android.widget.TextView 7 | import com.github.willjgriff.ethereumwallet.R 8 | import com.github.willjgriff.ethereumwallet.ui.utils.inflate 9 | import kotlinx.android.synthetic.main.view_node_status_peer_item.view.* 10 | 11 | /** 12 | * Created by williamgriffiths on 11/04/2017. 13 | */ 14 | class NodeDetailsPeersAdapter : RecyclerView.Adapter() { 15 | 16 | var peers: List = listOf() 17 | set(value) { 18 | field = value 19 | notifyDataSetChanged() 20 | } 21 | 22 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NodeStatusPeerViewHolder = 23 | NodeStatusPeerViewHolder(parent.inflate(R.layout.view_node_status_peer_item)) 24 | 25 | override fun getItemCount(): Int = 26 | peers.size 27 | 28 | override fun onBindViewHolder(holder: NodeStatusPeerViewHolder, position: Int) = 29 | holder.bind(peers[position]) 30 | 31 | class NodeStatusPeerViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { 32 | val peerDetails: TextView by lazy { itemView.view_node_status_peer_item_details } 33 | fun bind(peerString: String) { 34 | peerDetails.text = peerString 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ui/screens/nodedetails/mvp/NodeDetailsPresenter.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ui.screens.nodedetails.mvp 2 | 3 | import com.github.willjgriff.ethereumwallet.ethereum.node.NodeDetails 4 | import com.github.willjgriff.ethereumwallet.mvp.BaseMvpPresenterKotlin 5 | import timber.log.Timber 6 | import javax.inject.Inject 7 | 8 | /** 9 | * Created by Will on 20/03/2017. 10 | */ 11 | class NodeDetailsPresenter @Inject constructor(private val nodeDetails: NodeDetails) : BaseMvpPresenterKotlin() { 12 | 13 | override fun viewReady() { 14 | addDisposable(nodeDetails.cachedBlockHeaderObservable 15 | .subscribe({ (hashHex, _) -> view?.newHeaderHash(hashHex) }, 16 | { throwable -> Timber.e(throwable) })) 17 | 18 | addDisposable(nodeDetails.getNumberOfPeers() 19 | .subscribe({ view?.updateNumberOfPeers(it) }, 20 | { throwable -> Timber.e(throwable) })) 21 | 22 | addDisposable(nodeDetails.getSyncProgressString() 23 | .subscribe({ view?.setSyncProgress(it) }, 24 | { throwable -> Timber.e(throwable) })) 25 | 26 | addDisposable(nodeDetails.getNodePeerInfoStrings() 27 | .subscribe({ view?.setPeerStrings(it) }, 28 | { throwable -> Timber.e(throwable) })) 29 | 30 | view?.setNodeDetails(nodeDetails.getNodeInfoString()) 31 | } 32 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ui/screens/nodedetails/mvp/NodeDetailsView.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ui.screens.nodedetails.mvp 2 | 3 | /** 4 | * Created by Will on 20/03/2017. 5 | */ 6 | interface NodeDetailsView { 7 | 8 | fun newHeaderHash(headerHash: String) 9 | 10 | fun updateNumberOfPeers(numberOfPeers: Long) 11 | 12 | fun setNodeDetails(nodeInfoString: String) 13 | 14 | fun setSyncProgress(syncProgressString: String) 15 | 16 | fun setPeerStrings(peers: List) 17 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ui/screens/receive/ReceiveController.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ui.screens.receive 2 | 3 | import android.view.LayoutInflater 4 | import android.view.View 5 | import android.view.ViewGroup 6 | import com.github.willjgriff.ethereumwallet.R 7 | import com.github.willjgriff.ethereumwallet.mvp.BaseMvpControllerKotlin 8 | import com.github.willjgriff.ethereumwallet.ui.navigation.NavigationToolbarListener 9 | import com.github.willjgriff.ethereumwallet.ui.screens.receive.di.injectNewReceivePresenter 10 | import com.github.willjgriff.ethereumwallet.ui.screens.receive.mvp.ReceivePresenter 11 | import com.github.willjgriff.ethereumwallet.ui.screens.receive.mvp.ReceiveView 12 | import com.github.willjgriff.ethereumwallet.ui.utils.inflate 13 | import kotlinx.android.synthetic.main.controller_receive.view.* 14 | import javax.inject.Inject 15 | 16 | /** 17 | * Created by williamgriffiths on 18/04/2017. 18 | */ 19 | class ReceiveController : BaseMvpControllerKotlin(), ReceiveView { 20 | 21 | override val mvpView: ReceiveView 22 | get() = this 23 | @Inject lateinit override var presenter: ReceivePresenter 24 | 25 | lateinit var navigationToolbarListener: NavigationToolbarListener 26 | 27 | init { 28 | injectNewReceivePresenter() 29 | } 30 | 31 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup): View { 32 | val view = container.inflate(R.layout.controller_receive) 33 | setNavigationToolbarListener() 34 | setupToolbarTitle() 35 | return view 36 | } 37 | 38 | private fun setNavigationToolbarListener() { 39 | if (targetController is NavigationToolbarListener) { 40 | navigationToolbarListener = targetController as NavigationToolbarListener 41 | } 42 | } 43 | 44 | private fun setupToolbarTitle() { 45 | navigationToolbarListener.setToolbarTitle(applicationContext?.getString(R.string.controller_receive_title) ?: "") 46 | } 47 | 48 | override fun setReceiveAddress(address: String) { 49 | view?.controller_receive_ethereum_address?.text = address 50 | } 51 | 52 | override fun setPendingBalance(pendingBalance: String) { 53 | val ethBalance = applicationContext?.getString(R.string.controller_receive_eth_balance, pendingBalance) 54 | view?.controller_receive_pending_balance?.text = ethBalance 55 | } 56 | 57 | override fun setConfirmedBalance(confirmedBalance: String) { 58 | val ethBalance = applicationContext?.getString(R.string.controller_receive_eth_balance, confirmedBalance) 59 | view?.controller_receive_confirmed_balance?.text = ethBalance 60 | navigationToolbarListener.setBalance(ethBalance ?: "") 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ui/screens/receive/di/ReceiveComponent.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ui.screens.receive.di 2 | 3 | import com.github.willjgriff.ethereumwallet.di.AppComponent 4 | import com.github.willjgriff.ethereumwallet.di.AppInjector 5 | import com.github.willjgriff.ethereumwallet.di.ControllerScope 6 | import com.github.willjgriff.ethereumwallet.ui.screens.receive.ReceiveController 7 | import dagger.Component 8 | 9 | /** 10 | * Created by williamgriffiths on 18/04/2017. 11 | */ 12 | @Component(dependencies = arrayOf(AppComponent::class)) 13 | @ControllerScope 14 | interface ReceiveComponent { 15 | 16 | fun inject(receiveController: ReceiveController) 17 | } 18 | 19 | fun ReceiveController.injectNewReceivePresenter() { 20 | DaggerReceiveComponent.builder() 21 | .appComponent(AppInjector.appComponent) 22 | .build() 23 | .inject(this) 24 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ui/screens/receive/mvp/ReceivePresenter.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ui.screens.receive.mvp 2 | 3 | import com.github.willjgriff.ethereumwallet.ethereum.address.AddressManager 4 | import com.github.willjgriff.ethereumwallet.ethereum.address.balance.AddressBalance 5 | import com.github.willjgriff.ethereumwallet.mvp.BaseMvpPresenterKotlin 6 | import javax.inject.Inject 7 | 8 | /** 9 | * Created by williamgriffiths on 18/04/2017. 10 | */ 11 | class ReceivePresenter @Inject constructor(private val addressManager: AddressManager, private val addressBalance: AddressBalance) 12 | : BaseMvpPresenterKotlin() { 13 | 14 | override fun viewReady() { 15 | view?.setReceiveAddress(addressManager.getActiveAddress().hex) 16 | 17 | addDisposable(addressBalance.getPendingBalanceAtActiveAddress() 18 | .subscribe { view?.setPendingBalance(it) }) 19 | 20 | addDisposable(addressBalance.getBalanceAtActiveAddress() 21 | .subscribe { view?.setConfirmedBalance(it) }) 22 | } 23 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ui/screens/receive/mvp/ReceiveView.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ui.screens.receive.mvp 2 | 3 | /** 4 | * Created by williamgriffiths on 18/04/2017. 5 | */ 6 | interface ReceiveView { 7 | 8 | fun setReceiveAddress(address: String) 9 | fun setPendingBalance(pendingBalance: String) 10 | fun setConfirmedBalance(confirmedBalance: String) 11 | 12 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ui/screens/send/SendController.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ui.screens.send 2 | 3 | import android.support.v7.app.AlertDialog 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import com.github.willjgriff.ethereumwallet.R 8 | import com.github.willjgriff.ethereumwallet.mvp.BaseMvpControllerKotlin 9 | import com.github.willjgriff.ethereumwallet.ui.navigation.NavigationToolbarListener 10 | import com.github.willjgriff.ethereumwallet.ui.screens.send.di.injectPresenter 11 | import com.github.willjgriff.ethereumwallet.ui.screens.send.mvp.SendPresenter 12 | import com.github.willjgriff.ethereumwallet.ui.screens.send.mvp.SendView 13 | import com.jakewharton.rxbinding2.view.RxView 14 | import kotlinx.android.synthetic.main.controller_send.view.* 15 | import javax.inject.Inject 16 | 17 | /** 18 | * Created by Will on 29/01/2017. 19 | */ 20 | class SendController : BaseMvpControllerKotlin(), 21 | SendView { 22 | 23 | override val mvpView: SendView get() = this 24 | @Inject lateinit override var presenter: SendPresenter 25 | 26 | init { 27 | injectPresenter() 28 | } 29 | 30 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup): View { 31 | val view = inflater.inflate(R.layout.controller_send, container, false) 32 | setupToolbarTitle() 33 | setupObservables(view) 34 | return view 35 | } 36 | 37 | private fun setupToolbarTitle() { 38 | if (targetController is NavigationToolbarListener) { 39 | (targetController as NavigationToolbarListener) 40 | .setToolbarTitle(applicationContext!!.getString(R.string.controller_send_title)) 41 | } 42 | } 43 | 44 | private fun setupObservables(view: View) { 45 | val recipientAddress = view.controller_send_recipient_address 46 | val sendAmount = view.controller_send_amount 47 | val accountPassword = view.controller_send_password 48 | val sendEtherClicked = RxView.clicks(view.controller_send_send_ether).share() 49 | 50 | recipientAddress.setCheckValidationTrigger(sendEtherClicked) 51 | sendAmount.setCheckValidationTrigger(sendEtherClicked) 52 | accountPassword.setCheckValidationTrigger(sendEtherClicked) 53 | 54 | presenter.apply { 55 | recipientChanged = recipientAddress.textChangedObservable 56 | recipientValid = recipientAddress.textValidObservable 57 | amountChanged = sendAmount.textChangedObservable 58 | amountValid = sendAmount.textValidObservable 59 | passwordChanged = accountPassword.textChangedObservable 60 | passwordValid = accountPassword.textValidObservable 61 | sendClicked = sendEtherClicked 62 | } 63 | } 64 | 65 | override fun displayTransactionSubmitted() { 66 | activity?.let { 67 | AlertDialog.Builder(it) 68 | .setTitle(applicationContext?.getString(R.string.controller_send_success_dialog_title)) 69 | .setMessage(applicationContext?.getString(R.string.controller_send_success_dialog_message)) 70 | .setPositiveButton(applicationContext?.getString(R.string.common_ok), { dialog, _ -> dialog.dismiss() }) 71 | .show() 72 | } 73 | } 74 | 75 | override fun displayErrorSubmittingTx() { 76 | activity?.let { 77 | AlertDialog.Builder(it) 78 | .setTitle(applicationContext?.getString(R.string.controller_send_error_dialog_title)) 79 | .setMessage(applicationContext?.getString(R.string.controller_send_error_dialog_message)) 80 | .setPositiveButton(applicationContext?.getString(R.string.common_ok), { dialog, _ -> dialog.dismiss() }) 81 | .show() 82 | } 83 | } 84 | 85 | override fun setBalance(balance: String) { 86 | view?.controller_send_current_balance?.text = balance 87 | } 88 | 89 | override fun setPendingBalance(pendingBalance: String) { 90 | view?.controller_send_pending_balance?.text = pendingBalance 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ui/screens/send/di/SendComponent.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ui.screens.send.di 2 | 3 | import com.github.willjgriff.ethereumwallet.di.AppComponent 4 | import com.github.willjgriff.ethereumwallet.di.AppInjector 5 | import com.github.willjgriff.ethereumwallet.di.ControllerScope 6 | import com.github.willjgriff.ethereumwallet.ui.screens.send.SendController 7 | import dagger.Component 8 | 9 | /** 10 | * Created by williamgriffiths on 03/05/2017. 11 | */ 12 | @Component(dependencies = arrayOf(AppComponent::class)) 13 | @ControllerScope 14 | interface SendComponent { 15 | 16 | fun inject(sendController: SendController) 17 | } 18 | 19 | fun SendController.injectPresenter() { 20 | DaggerSendComponent.builder() 21 | .appComponent(AppInjector.appComponent) 22 | .build() 23 | .inject(this) 24 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ui/screens/send/model/WholeTransaction.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ui.screens.send.model 2 | 3 | import com.github.willjgriff.ethereumwallet.ethereum.transaction.model.SendTransaction 4 | 5 | /** 6 | * Created by williamgriffiths on 05/05/2017. 7 | */ 8 | data class WholeTransaction(val sendTransaction: SendTransaction, val password: String) -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ui/screens/send/mvp/SendPresenter.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ui.screens.send.mvp 2 | 3 | import com.github.willjgriff.ethereumwallet.ethereum.address.balance.AddressBalance 4 | import com.github.willjgriff.ethereumwallet.ethereum.common.model.Denomination 5 | import com.github.willjgriff.ethereumwallet.ethereum.common.model.EtherAmount 6 | import com.github.willjgriff.ethereumwallet.ethereum.transaction.TransactionManager 7 | import com.github.willjgriff.ethereumwallet.ethereum.transaction.model.SendTransaction 8 | import com.github.willjgriff.ethereumwallet.mvp.BaseMvpPresenterKotlin 9 | import com.github.willjgriff.ethereumwallet.ui.screens.send.model.WholeTransaction 10 | import io.reactivex.Observable 11 | import io.reactivex.functions.BiFunction 12 | import io.reactivex.functions.Function3 13 | import java.math.BigDecimal 14 | import java.util.concurrent.TimeUnit 15 | import javax.inject.Inject 16 | 17 | /** 18 | * Created by williamgriffiths on 03/05/2017. 19 | */ 20 | class SendPresenter @Inject constructor(private val transactionManager: TransactionManager, 21 | private val addressBalance: AddressBalance) 22 | : BaseMvpPresenterKotlin() { 23 | 24 | private var SEND_SPAM_PREVENTION_DELAY_SECONDS = 2L 25 | 26 | lateinit var recipientChanged: Observable 27 | lateinit var recipientValid: Observable 28 | lateinit var amountChanged: Observable 29 | lateinit var amountValid: Observable 30 | lateinit var passwordChanged: Observable 31 | lateinit var passwordValid: Observable 32 | lateinit var sendClicked: Observable 33 | 34 | override fun viewReady() { 35 | setBalance() 36 | setupSendClicked() 37 | } 38 | 39 | private fun setBalance() { 40 | addDisposable(addressBalance.getBalanceAtActiveAddress() 41 | .subscribe { view?.setBalance(it) }) 42 | 43 | addDisposable(addressBalance.getPendingBalanceAtActiveAddress() 44 | .subscribe { view?.setPendingBalance(it) }) 45 | } 46 | 47 | private fun setupSendClicked() { 48 | sendClicked.subscribe { _ -> view.toString() } 49 | 50 | val allInputValid = Observable.combineLatest(recipientValid, amountValid, passwordValid, 51 | Function3 { recipientValid, amountValid, passwordValid -> recipientValid && amountValid && passwordValid }) 52 | .distinctUntilChanged() 53 | 54 | sendClicked 55 | .withLatestFrom(allInputValid, BiFunction { _, validInput -> validInput }) 56 | .filter { it == true } 57 | // This is to prevent spamming 58 | .throttleFirst(SEND_SPAM_PREVENTION_DELAY_SECONDS, TimeUnit.SECONDS) 59 | .flatMap { packageTransactionFromInput() } 60 | .subscribe { submitTransaction(it) } 61 | } 62 | 63 | fun packageTransactionFromInput(): Observable? { 64 | return Observable.zip(recipientChanged, mapAmountToEtherAmount(), 65 | BiFunction { recipient, amount -> SendTransaction(recipient, amount) }) 66 | .zipWith(passwordChanged, BiFunction(::WholeTransaction)) 67 | } 68 | 69 | fun mapAmountToEtherAmount(): Observable = amountChanged 70 | .map { EtherAmount(BigDecimal(it), Denomination.ETHER) } 71 | 72 | private fun submitTransaction(it: WholeTransaction) { 73 | val txSubmittedSuccessfully = transactionManager.executeTransaction(it.sendTransaction, it.password) 74 | if (txSubmittedSuccessfully) { 75 | view?.displayTransactionSubmitted() 76 | } else { 77 | view?.displayErrorSubmittingTx() 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ui/screens/send/mvp/SendView.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ui.screens.send.mvp 2 | 3 | /** 4 | * Created by williamgriffiths on 03/05/2017. 5 | */ 6 | interface SendView { 7 | fun displayTransactionSubmitted() 8 | fun displayErrorSubmittingTx() 9 | fun setBalance(balance: String) 10 | fun setPendingBalance(pendingBalance: String) 11 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ui/screens/settings/ChangeAddressController.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ui.screens.settings 2 | 3 | import android.support.v7.widget.LinearLayoutManager 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import com.github.willjgriff.ethereumwallet.R 8 | import com.github.willjgriff.ethereumwallet.ethereum.address.model.DomainAddress 9 | import com.github.willjgriff.ethereumwallet.mvp.BaseMvpControllerKotlin 10 | import com.github.willjgriff.ethereumwallet.ui.screens.settings.di.injectPresenter 11 | import com.github.willjgriff.ethereumwallet.ui.screens.settings.list.ChangeAddressAdapter 12 | import com.github.willjgriff.ethereumwallet.ui.screens.settings.list.ChangeAddressItemViewHolder 13 | import com.github.willjgriff.ethereumwallet.ui.screens.settings.mvp.ChangeAddressPresenter 14 | import com.github.willjgriff.ethereumwallet.ui.screens.settings.mvp.ChangeAddressView 15 | import kotlinx.android.synthetic.main.controller_settings_change_address.view.* 16 | import javax.inject.Inject 17 | 18 | /** 19 | * Created by williamgriffiths on 04/05/2017. 20 | */ 21 | 22 | class ChangeAddressController : BaseMvpControllerKotlin(), 23 | ChangeAddressView, ChangeAddressItemViewHolder.ChangeAddressItemListener { 24 | 25 | override val mvpView: ChangeAddressView 26 | get() = this 27 | @Inject override lateinit var presenter: ChangeAddressPresenter 28 | 29 | private var addressesAdapter: ChangeAddressAdapter = ChangeAddressAdapter(this) 30 | 31 | init { 32 | injectPresenter() 33 | } 34 | 35 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup): View { 36 | val view = inflater.inflate(R.layout.controller_settings_change_address, container, false) 37 | setupAddressesList(view) 38 | return view 39 | } 40 | 41 | private fun setupAddressesList(view: View) { 42 | view.controller_settings_change_address_recycler_view.apply { 43 | layoutManager = LinearLayoutManager(applicationContext) 44 | adapter = addressesAdapter 45 | } 46 | } 47 | 48 | override fun setAddresses(allAccounts: List) { 49 | addressesAdapter.accounts = allAccounts 50 | } 51 | 52 | override fun closeScreen() { 53 | router.popCurrentController() 54 | } 55 | 56 | override fun addressItemClicked(domainAddress: DomainAddress) { 57 | presenter.onAddressItemClicked(domainAddress) 58 | } 59 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ui/screens/settings/DeleteAddressAlertDialog.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ui.screens.settings 2 | 3 | import android.content.Context 4 | import android.content.DialogInterface 5 | import android.os.Bundle 6 | import com.github.willjgriff.ethereumwallet.R 7 | import com.github.willjgriff.ethereumwallet.mvp.BaseMvpAlertDialog 8 | import com.github.willjgriff.ethereumwallet.ui.screens.settings.di.injectPresenter 9 | import com.github.willjgriff.ethereumwallet.ui.screens.settings.mvp.DeleteAddressPresenter 10 | import com.github.willjgriff.ethereumwallet.ui.screens.settings.mvp.DeleteAddressView 11 | import com.github.willjgriff.ethereumwallet.ui.utils.inflate 12 | import com.github.willjgriff.ethereumwallet.ui.widget.validatedtextinput.ValidatedTextInputLayout 13 | import com.jakewharton.rxbinding2.view.RxView 14 | import javax.inject.Inject 15 | 16 | /** 17 | * Created by williamgriffiths on 04/05/2017. 18 | */ 19 | class DeleteAddressAlertDialog(context: Context, private val mDialogListener: SettingsDeleteAlertDialogListener) 20 | : BaseMvpAlertDialog(context), DeleteAddressView { 21 | 22 | override val mvpView: DeleteAddressView 23 | get() = this 24 | @Inject override lateinit var presenter: DeleteAddressPresenter 25 | 26 | private var mValidatedTextInputLayout: ValidatedTextInputLayout = 27 | context.inflate(R.layout.view_controller_settings_delete_validated_input) as ValidatedTextInputLayout 28 | 29 | init { 30 | injectPresenter() 31 | setupAppearance() 32 | } 33 | 34 | private fun setupAppearance() { 35 | setButton(DialogInterface.BUTTON_POSITIVE, context.getString(R.string.controller_settings_delete_address)) { _, _ -> } 36 | setButton(DialogInterface.BUTTON_NEGATIVE, context.getString(R.string.controller_settings_delete_address_cancel)) { _, _ -> } 37 | setTitle(R.string.controller_settings_delete_address_dialog_title) 38 | setMessage(context.getString(R.string.controller_settings_delete_address_dialog_message)) 39 | setView(mValidatedTextInputLayout) 40 | } 41 | 42 | override fun onCreate(savedInstanceState: Bundle?) { 43 | super.onCreate(savedInstanceState) 44 | 45 | setupObservablesOnPresenter() 46 | } 47 | 48 | private fun setupObservablesOnPresenter() { 49 | val deleteClick = RxView.clicks(getButton(DialogInterface.BUTTON_POSITIVE)).share() 50 | val cancelClick = RxView.clicks(getButton(DialogInterface.BUTTON_NEGATIVE)).share() 51 | mValidatedTextInputLayout.setCheckValidationTrigger(deleteClick) 52 | val passwordValid = mValidatedTextInputLayout.textValidObservable 53 | val passwordChanged = mValidatedTextInputLayout.textChangedObservable 54 | 55 | presenter.apply { 56 | this.deleteClick = deleteClick 57 | this.cancelClick = cancelClick 58 | this.passwordValid = passwordValid 59 | this.passwordChanged = passwordChanged 60 | } 61 | } 62 | 63 | override fun closeDialog() { 64 | dismiss() 65 | } 66 | 67 | override fun incorrectPasswordEntered() { 68 | mDialogListener.showIncorrectPasswordDialog() 69 | } 70 | 71 | override fun addressDeleted() { 72 | mDialogListener.addressSuccessfullyDeleted() 73 | } 74 | 75 | interface SettingsDeleteAlertDialogListener { 76 | fun showIncorrectPasswordDialog() 77 | fun addressSuccessfullyDeleted() 78 | } 79 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ui/screens/settings/di/SettingsComponent.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ui.screens.settings.di 2 | 3 | import com.github.willjgriff.ethereumwallet.di.AppComponent 4 | import com.github.willjgriff.ethereumwallet.di.AppInjector 5 | import com.github.willjgriff.ethereumwallet.di.ControllerScope 6 | import com.github.willjgriff.ethereumwallet.ui.screens.settings.ChangeAddressController 7 | import com.github.willjgriff.ethereumwallet.ui.screens.settings.DeleteAddressAlertDialog 8 | import com.github.willjgriff.ethereumwallet.ui.screens.settings.SettingsController 9 | import dagger.Component 10 | 11 | /** 12 | * Created by williamgriffiths on 04/05/2017. 13 | */ 14 | @ControllerScope 15 | @Component(dependencies = arrayOf(AppComponent::class)) 16 | interface SettingsComponent { 17 | 18 | fun inject(settingsController: SettingsController) 19 | 20 | fun inject(alertDialog: DeleteAddressAlertDialog) 21 | 22 | fun inject(changeAddressController: ChangeAddressController) 23 | } 24 | 25 | fun SettingsController.injectPresenter() { 26 | getComponent().inject(this) 27 | } 28 | 29 | fun DeleteAddressAlertDialog.injectPresenter() { 30 | getComponent().inject(this) 31 | } 32 | 33 | fun ChangeAddressController.injectPresenter() { 34 | getComponent().inject(this) 35 | } 36 | 37 | private fun getComponent(): SettingsComponent { 38 | return DaggerSettingsComponent.builder() 39 | .appComponent(AppInjector.appComponent) 40 | .build() 41 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ui/screens/settings/list/ChangeAddressAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ui.screens.settings.list 2 | 3 | import android.support.v7.widget.RecyclerView 4 | import android.view.ViewGroup 5 | import com.github.willjgriff.ethereumwallet.R 6 | import com.github.willjgriff.ethereumwallet.ethereum.address.model.DomainAddress 7 | import com.github.willjgriff.ethereumwallet.ui.screens.settings.list.ChangeAddressAdapter.ChangeAddressType.HEADER 8 | import com.github.willjgriff.ethereumwallet.ui.screens.settings.list.ChangeAddressAdapter.ChangeAddressType.ITEM 9 | import com.github.willjgriff.ethereumwallet.ui.screens.settings.list.ChangeAddressItemViewHolder.ChangeAddressItemListener 10 | import com.github.willjgriff.ethereumwallet.ui.utils.inflate 11 | 12 | /** 13 | * Created by williamgriffiths on 04/05/2017. 14 | */ 15 | class ChangeAddressAdapter(private val changeAddressItemListener: ChangeAddressItemListener) 16 | : RecyclerView.Adapter() { 17 | 18 | private val HEADER_OFFSET = 1 19 | 20 | var accounts: List = listOf() 21 | set(value) { 22 | field = value 23 | notifyDataSetChanged() 24 | } 25 | 26 | internal enum class ChangeAddressType { 27 | HEADER, 28 | ITEM 29 | } 30 | 31 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder? { 32 | when (ChangeAddressType.values()[viewType]) { 33 | HEADER -> return ChangeAddressHeaderViewHolder(parent.inflate(R.layout.view_change_address_header)) 34 | ITEM -> return ChangeAddressItemViewHolder(parent.inflate(R.layout.view_change_address_item), changeAddressItemListener) 35 | } 36 | } 37 | 38 | override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { 39 | if (holder is ChangeAddressItemViewHolder) { 40 | holder.bind(accounts[position - HEADER_OFFSET]) 41 | } 42 | } 43 | 44 | override fun getItemCount(): Int { 45 | return accounts.size + HEADER_OFFSET 46 | } 47 | 48 | override fun getItemViewType(position: Int): Int = 49 | when (position) { 50 | 0 -> HEADER.ordinal 51 | else -> ITEM.ordinal 52 | } 53 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ui/screens/settings/list/ChangeAddressHeaderViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ui.screens.settings.list 2 | 3 | import android.support.v7.widget.RecyclerView 4 | import android.view.View 5 | 6 | /** 7 | * Created by williamgriffiths on 04/05/2017. 8 | */ 9 | class ChangeAddressHeaderViewHolder(view: View) : RecyclerView.ViewHolder(view) 10 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ui/screens/settings/list/ChangeAddressItemViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ui.screens.settings.list 2 | 3 | import android.support.v7.widget.RecyclerView 4 | import android.view.View 5 | import android.widget.TextView 6 | import com.github.willjgriff.ethereumwallet.ethereum.address.model.DomainAddress 7 | import kotlinx.android.synthetic.main.view_change_address_item.view.* 8 | 9 | /** 10 | * Created by williamgriffiths on 04/05/2017. 11 | */ 12 | class ChangeAddressItemViewHolder(itemView: View, 13 | private val changeAddressItemListener: ChangeAddressItemViewHolder.ChangeAddressItemListener) 14 | : RecyclerView.ViewHolder(itemView) { 15 | 16 | private var address: TextView = itemView.view_change_address_item_address 17 | 18 | fun bind(domainAddress: DomainAddress) { 19 | address.text = domainAddress.hex 20 | itemView.setOnClickListener { _ -> changeAddressItemListener.addressItemClicked(domainAddress) } 21 | } 22 | 23 | interface ChangeAddressItemListener { 24 | fun addressItemClicked(domainAddress: DomainAddress) 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ui/screens/settings/mvp/ChangeAddressPresenter.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ui.screens.settings.mvp 2 | 3 | import com.github.willjgriff.ethereumwallet.ethereum.address.AddressManager 4 | import com.github.willjgriff.ethereumwallet.ethereum.address.model.DomainAddress 5 | import com.github.willjgriff.ethereumwallet.mvp.BaseMvpPresenterKotlin 6 | import javax.inject.Inject 7 | 8 | /** 9 | * Created by williamgriffiths on 04/05/2017. 10 | */ 11 | class ChangeAddressPresenter @Inject constructor(private val addressManager: AddressManager) 12 | : BaseMvpPresenterKotlin() { 13 | 14 | override fun viewReady() { 15 | view?.setAddresses(addressManager.getAllAddresses()) 16 | } 17 | 18 | fun onAddressItemClicked(domainAddress: DomainAddress) { 19 | addressManager.setActiveAccount(domainAddress) 20 | view?.closeScreen() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ui/screens/settings/mvp/ChangeAddressView.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ui.screens.settings.mvp 2 | 3 | import com.github.willjgriff.ethereumwallet.ethereum.address.model.DomainAddress 4 | 5 | /** 6 | * Created by williamgriffiths on 04/05/2017. 7 | */ 8 | interface ChangeAddressView { 9 | 10 | fun setAddresses(allAccounts: List) 11 | 12 | fun closeScreen() 13 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ui/screens/settings/mvp/DeleteAddressPresenter.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ui.screens.settings.mvp 2 | 3 | import com.github.willjgriff.ethereumwallet.ethereum.address.AddressManager 4 | import com.github.willjgriff.ethereumwallet.mvp.BaseMvpPresenterKotlin 5 | import io.reactivex.Observable 6 | import io.reactivex.functions.BiFunction 7 | import javax.inject.Inject 8 | 9 | /** 10 | * Created by Will on 04/05/2017. 11 | * 12 | * 13 | * Note this uses AutoFactory which means it can't be retained beyond the life of 14 | * the Controller / AlertDialog. Therefore we don't have to release references to 15 | * View containing objects or dispose of Observable Subscriptions. 16 | */ 17 | class DeleteAddressPresenter @Inject 18 | constructor(private val addressManager: AddressManager) 19 | : BaseMvpPresenterKotlin() { 20 | 21 | lateinit var deleteClick: Observable 22 | lateinit var cancelClick: Observable 23 | lateinit var passwordValid: Observable 24 | lateinit var passwordChanged: Observable 25 | 26 | override fun viewReady() { 27 | setupDeleteButton(deleteClick, passwordValid, passwordChanged) 28 | cancelClick.subscribe { _ -> view?.closeDialog() } 29 | } 30 | 31 | private fun setupDeleteButton(deleteButton: Observable, passwordValid: Observable, passwordChanged: Observable) { 32 | val deleteObservable = deleteButton 33 | .withLatestFrom(passwordValid, BiFunction { _, passwordIsValid -> passwordIsValid }) 34 | .filter { passwordIsValid -> passwordIsValid } 35 | .flatMap { _ -> passwordChanged } 36 | .map { validPassword -> addressManager.deleteActiveAddress(validPassword) } 37 | .share() 38 | 39 | // Show incorrect password error if the password is incorrect 40 | deleteObservable 41 | .filter({ deleteAccount -> !deleteAccount }) 42 | .subscribe { _ -> incorrectPasswordEntered() } 43 | 44 | // Delete active account if the password is correct 45 | deleteObservable 46 | .filter({ deleteAccount -> deleteAccount }) 47 | .subscribe { _ -> deleteAccount() } 48 | } 49 | 50 | private fun incorrectPasswordEntered() { 51 | view?.incorrectPasswordEntered() 52 | view?.closeDialog() 53 | } 54 | 55 | private fun deleteAccount() { 56 | view?.addressDeleted() 57 | view?.closeDialog() 58 | } 59 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ui/screens/settings/mvp/DeleteAddressView.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ui.screens.settings.mvp 2 | 3 | /** 4 | * Created by williamgriffiths on 04/05/2017. 5 | */ 6 | interface DeleteAddressView { 7 | 8 | fun closeDialog() 9 | 10 | fun incorrectPasswordEntered() 11 | 12 | fun addressDeleted() 13 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ui/screens/settings/mvp/SettingsPresenter.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ui.screens.settings.mvp 2 | 3 | import com.github.willjgriff.ethereumwallet.ethereum.address.AddressManager 4 | import com.github.willjgriff.ethereumwallet.mvp.BaseMvpPresenterKotlin 5 | import io.reactivex.Observable 6 | import javax.inject.Inject 7 | 8 | /** 9 | * Created by williamgriffiths on 04/05/2017. 10 | */ 11 | class SettingsPresenter @Inject constructor(private val addressManager: AddressManager) 12 | : BaseMvpPresenterKotlin() { 13 | 14 | override fun viewReady() { 15 | getAndSetActiveAddress() 16 | } 17 | 18 | fun setObservables(newAccountClick: Observable, changeAddressClick: Observable, deleteAddressClick: Observable) { 19 | newAccountClick.subscribe { _ -> view?.openCreateAccountScreen() } 20 | changeAddressClick.subscribe { _ -> view?.openChangeAddressScreen() } 21 | deleteAddressClick.subscribe { _ -> view?.showDeleteAddressDialog() } 22 | } 23 | 24 | private fun getAndSetActiveAddress() { 25 | val address: String 26 | if (addressManager.getActiveAddress().hex != "NO_ADDRESS_HEX") { 27 | address = addressManager.getActiveAddress().hex 28 | view?.setActiveAddress(address) 29 | } else { 30 | view?.setAddressDeleted() 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ui/screens/settings/mvp/SettingsView.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ui.screens.settings.mvp 2 | 3 | /** 4 | * Created by williamgriffiths on 04/05/2017. 5 | */ 6 | interface SettingsView { 7 | 8 | fun openCreateAccountScreen() 9 | 10 | fun showDeleteAddressDialog() 11 | 12 | fun setActiveAddress(address: String) 13 | 14 | fun setAddressDeleted() 15 | 16 | fun openChangeAddressScreen() 17 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ui/screens/transactions/SelectBlockRangeAlertDialog.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ui.screens.transactions 2 | 3 | import android.content.Context 4 | import android.content.DialogInterface 5 | import com.github.willjgriff.ethereumwallet.R 6 | import com.github.willjgriff.ethereumwallet.mvp.BaseMvpAlertDialog 7 | import com.github.willjgriff.ethereumwallet.ui.screens.transactions.di.injectPresenter 8 | import com.github.willjgriff.ethereumwallet.ui.screens.transactions.mvp.SelectBlockRangePresenter 9 | import com.github.willjgriff.ethereumwallet.ui.screens.transactions.mvp.SelectBlockRangeView 10 | import com.github.willjgriff.ethereumwallet.ui.utils.inflate 11 | import javax.inject.Inject 12 | 13 | /** 14 | * Created by williamgriffiths on 03/05/2017. 15 | */ 16 | class SelectBlockRangeAlertDialog(context: Context) : BaseMvpAlertDialog(context), 17 | SelectBlockRangeView { 18 | 19 | override val mvpView: SelectBlockRangeView 20 | get() = this 21 | @Inject lateinit override var presenter: SelectBlockRangePresenter 22 | 23 | init { 24 | injectPresenter() 25 | setupAppearance() 26 | } 27 | 28 | private fun setupAppearance() { 29 | setButton(DialogInterface.BUTTON_POSITIVE, context.getString(R.string.common_ok)) { dialog, _ -> dialog.dismiss()} 30 | setTitle("Select a block range") 31 | 32 | val rangeFieldsView = context.inflate(R.layout.view_controller_transactions_search_range_dialog) 33 | 34 | setView(rangeFieldsView) 35 | } 36 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ui/screens/transactions/TransactionsController.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ui.screens.transactions 2 | 3 | import android.support.v7.widget.LinearLayoutManager 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import com.github.willjgriff.ethereumwallet.R 8 | import com.github.willjgriff.ethereumwallet.ethereum.transactions.model.DomainTransaction 9 | import com.github.willjgriff.ethereumwallet.mvp.BaseMvpControllerKotlin 10 | import com.github.willjgriff.ethereumwallet.ui.navigation.NavigationToolbarListener 11 | import com.github.willjgriff.ethereumwallet.ui.screens.transactions.adapters.TransactionsAdapter 12 | import com.github.willjgriff.ethereumwallet.ui.screens.transactions.di.injectPresenter 13 | import com.github.willjgriff.ethereumwallet.ui.screens.transactions.mvp.TransactionsPresenter 14 | import com.github.willjgriff.ethereumwallet.ui.screens.transactions.mvp.TransactionsView 15 | import com.github.willjgriff.ethereumwallet.ui.utils.inflate 16 | import com.github.willjgriff.ethereumwallet.ui.utils.listdecorator.EvenPaddingDecorator 17 | import com.jakewharton.rxbinding2.view.RxView 18 | import kotlinx.android.synthetic.main.controller_transactions.view.* 19 | import javax.inject.Inject 20 | 21 | /** 22 | * Created by williamgriffiths on 11/04/2017. 23 | */ 24 | class TransactionsController : BaseMvpControllerKotlin(), 25 | TransactionsView { 26 | 27 | override val mvpView: TransactionsView 28 | get() = this 29 | @Inject lateinit override var presenter: TransactionsPresenter 30 | 31 | private val TRANSACTIONS_LIST_ITEM_TOP_BOTTOM_PADDING_DP = 16 32 | private val TRANSACTIONS_LIST_ITEM_LEFT_RIGHT_PADDING_DP = 8 33 | private val transactionsAdapter: TransactionsAdapter = TransactionsAdapter() 34 | 35 | init { 36 | injectPresenter() 37 | } 38 | 39 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup): View { 40 | val view = container.inflate(R.layout.controller_transactions) 41 | setNavigationToolbarListener() 42 | bindViews(view) 43 | setupTransactionsList(view) 44 | return view 45 | } 46 | 47 | private fun bindViews(view: View) { 48 | val clearTxsButton = view.controller_transactions_clear_transactions 49 | val selectSearchRange = view.controller_transactions_search_in_range 50 | presenter.setObservables(RxView.clicks(clearTxsButton), RxView.clicks(selectSearchRange)) 51 | } 52 | 53 | private fun setNavigationToolbarListener() { 54 | val navigationToolbarListener = targetController 55 | if (navigationToolbarListener is NavigationToolbarListener) { 56 | navigationToolbarListener.setToolbarTitle(applicationContext?.getString(R.string.controller_transactions_title) ?: "") 57 | } 58 | } 59 | 60 | private fun setupTransactionsList(view: View) { 61 | view.controller_transactions_recycler_view.apply { 62 | layoutManager = LinearLayoutManager(applicationContext) 63 | adapter = transactionsAdapter 64 | addItemDecoration(EvenPaddingDecorator(TRANSACTIONS_LIST_ITEM_TOP_BOTTOM_PADDING_DP, TRANSACTIONS_LIST_ITEM_LEFT_RIGHT_PADDING_DP)) 65 | } 66 | } 67 | 68 | override fun addTransaction(transaction: DomainTransaction) { 69 | transactionsAdapter.addTransaction(transaction) 70 | } 71 | 72 | override fun clearTransactions() { 73 | transactionsAdapter.transactions = mutableListOf() 74 | } 75 | 76 | override fun displayRangeDialog() { 77 | SelectBlockRangeAlertDialog(activity!!).show() 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ui/screens/transactions/adapters/TransactionsAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ui.screens.transactions.adapters 2 | 3 | import android.support.v7.widget.RecyclerView 4 | import android.view.ViewGroup 5 | import com.github.willjgriff.ethereumwallet.R 6 | import com.github.willjgriff.ethereumwallet.ethereum.transactions.model.DomainTransaction 7 | import com.github.willjgriff.ethereumwallet.ui.screens.transactions.viewholders.TransactionViewHolder 8 | import com.github.willjgriff.ethereumwallet.ui.utils.inflate 9 | 10 | /** 11 | * Created by williamgriffiths on 19/04/2017. 12 | */ 13 | class TransactionsAdapter : RecyclerView.Adapter() { 14 | 15 | var transactions: MutableList = mutableListOf() 16 | set(value) { 17 | field = value 18 | notifyDataSetChanged() 19 | } 20 | 21 | override fun onBindViewHolder(holder: TransactionViewHolder, position: Int) = 22 | holder.bind(transactions[position]) 23 | 24 | override fun getItemCount(): Int = 25 | transactions.size 26 | 27 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TransactionViewHolder = 28 | TransactionViewHolder(parent.inflate(R.layout.view_transaction_item)) 29 | 30 | fun addTransaction(transaction: DomainTransaction) { 31 | transactions.add(transaction) 32 | transactions.sortByDescending { it.time } 33 | notifyDataSetChanged() 34 | } 35 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ui/screens/transactions/di/TransactionsComponent.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ui.screens.transactions.di 2 | 3 | import com.github.willjgriff.ethereumwallet.di.AppComponent 4 | import com.github.willjgriff.ethereumwallet.di.AppInjector 5 | import com.github.willjgriff.ethereumwallet.di.ControllerScope 6 | import com.github.willjgriff.ethereumwallet.ui.screens.transactions.SelectBlockRangeAlertDialog 7 | import com.github.willjgriff.ethereumwallet.ui.screens.transactions.TransactionsController 8 | import dagger.Component 9 | 10 | /** 11 | * Created by williamgriffiths on 11/04/2017. 12 | */ 13 | @Component(dependencies = arrayOf(AppComponent::class)) 14 | @ControllerScope 15 | interface TransactionsComponent { 16 | 17 | fun inject(transactionsController: TransactionsController) 18 | 19 | fun inject(selectBlockRangeAlertDialog: SelectBlockRangeAlertDialog) 20 | } 21 | 22 | fun TransactionsController.injectPresenter() = 23 | transactionsComponent().inject(this) 24 | 25 | fun SelectBlockRangeAlertDialog.injectPresenter() = 26 | transactionsComponent().inject(this) 27 | 28 | private fun transactionsComponent(): TransactionsComponent { 29 | return DaggerTransactionsComponent.builder() 30 | .appComponent(AppInjector.appComponent) 31 | .build() 32 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ui/screens/transactions/mvp/SelectBlockRangePresenter.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ui.screens.transactions.mvp 2 | 3 | import com.github.willjgriff.ethereumwallet.mvp.BaseMvpPresenterKotlin 4 | import javax.inject.Inject 5 | 6 | /** 7 | * Created by williamgriffiths on 03/05/2017. 8 | */ 9 | class SelectBlockRangePresenter @Inject constructor() : BaseMvpPresenterKotlin() { 10 | 11 | override fun viewReady() { 12 | 13 | } 14 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ui/screens/transactions/mvp/SelectBlockRangeView.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ui.screens.transactions.mvp 2 | 3 | /** 4 | * Created by williamgriffiths on 03/05/2017. 5 | */ 6 | interface SelectBlockRangeView { 7 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ui/screens/transactions/mvp/TransactionsPresenter.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ui.screens.transactions.mvp 2 | 3 | import com.github.willjgriff.ethereumwallet.ethereum.transactions.TransactionsManager 4 | import com.github.willjgriff.ethereumwallet.mvp.BaseMvpPresenterKotlin 5 | import io.reactivex.Observable 6 | import io.reactivex.disposables.Disposable 7 | import javax.inject.Inject 8 | 9 | /** 10 | * Created by williamgriffiths on 11/04/2017. 11 | */ 12 | class TransactionsPresenter @Inject constructor(private val transactionsManager: TransactionsManager) : BaseMvpPresenterKotlin() { 13 | 14 | private lateinit var transactionsDisposable: Disposable 15 | 16 | fun setObservables(clearTransactions: Observable, selectSearchRange: Observable) { 17 | setupClearTransactions(clearTransactions) 18 | setupSelectSearchRange(selectSearchRange) 19 | } 20 | 21 | private fun setupSelectSearchRange(selectSearchRange: Observable) { 22 | addDisposable(selectSearchRange.subscribe { view?.displayRangeDialog() }) 23 | } 24 | 25 | override fun viewReady() { 26 | subscribeToTransactions() 27 | } 28 | 29 | private fun subscribeToTransactions() { 30 | transactionsDisposable = transactionsManager 31 | .transactionsObservable.subscribe { view?.addTransaction(it) } 32 | addDisposable(transactionsDisposable) 33 | } 34 | 35 | private fun setupClearTransactions(clearTransactions: Observable) { 36 | clearTransactions.subscribe { 37 | view?.clearTransactions() 38 | transactionsManager.clearAndRestart() 39 | transactionsDisposable.dispose() 40 | subscribeToTransactions() 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ui/screens/transactions/mvp/TransactionsView.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ui.screens.transactions.mvp 2 | 3 | import com.github.willjgriff.ethereumwallet.ethereum.transactions.model.DomainTransaction 4 | 5 | /** 6 | * Created by williamgriffiths on 11/04/2017. 7 | */ 8 | interface TransactionsView { 9 | 10 | fun addTransaction(transaction: DomainTransaction) 11 | fun clearTransactions() 12 | fun displayRangeDialog() 13 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ui/screens/transactions/viewholders/TransactionViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ui.screens.transactions.viewholders 2 | 3 | import android.support.v7.widget.RecyclerView 4 | import android.view.View 5 | import android.widget.TextView 6 | import com.github.willjgriff.ethereumwallet.ethereum.common.fromWeiTo 7 | import com.github.willjgriff.ethereumwallet.ethereum.common.model.Denomination 8 | import com.github.willjgriff.ethereumwallet.ethereum.transactions.model.DomainTransaction 9 | import kotlinx.android.synthetic.main.view_transaction_item.view.* 10 | import java.text.DateFormat 11 | import java.util.* 12 | 13 | /** 14 | * Created by williamgriffiths on 19/04/2017. 15 | */ 16 | class TransactionViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { 17 | 18 | val EPOCH_MULTIPLIER = 1000 19 | val toAddress: TextView = itemView.view_transaction_item_to_address 20 | val value: TextView = itemView.view_transaction_item_value 21 | val time: TextView = itemView.view_transaction_item_time 22 | 23 | fun bind(transaction: DomainTransaction) { 24 | toAddress.text = transaction.toAddress.hex 25 | 26 | val etherValue = transaction.value.fromWeiTo(Denomination.ETHER) 27 | value.text = String.format(Locale.getDefault(), etherValue.toString()) 28 | 29 | val dateFromLong = Date(transaction.time * EPOCH_MULTIPLIER) 30 | val formattedDateString = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, Locale.UK).format(dateFromLong) 31 | time.text = formattedDateString 32 | } 33 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ui/utils/InflaterExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ui.utils 2 | 3 | import android.content.Context 4 | import android.support.annotation.LayoutRes 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import android.widget.FrameLayout 9 | 10 | /** 11 | * Created by williamgriffiths on 23/03/2017. 12 | */ 13 | 14 | fun ViewGroup.inflate(@LayoutRes layoutId: Int, attachToRoot: Boolean = false): View { 15 | return LayoutInflater.from(context).inflate(layoutId, this, attachToRoot) 16 | } 17 | 18 | fun Context.inflate(@LayoutRes layoutId: Int, viewGroup: ViewGroup = FrameLayout(this), attachToRoot: Boolean = false): View { 19 | return LayoutInflater.from(this).inflate(layoutId, viewGroup, attachToRoot) 20 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ui/utils/SizeConverterExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ui.utils 2 | 3 | import android.content.Context 4 | 5 | /** 6 | * Created by williamgriffiths on 04/05/2017. 7 | */ 8 | fun Float.convertDpToPixel(context: Context): Float { 9 | val resources = context.resources 10 | val metrics = resources.displayMetrics 11 | return this * (metrics.densityDpi / 160f) 12 | } 13 | 14 | fun Float.convertPixelsToDp(context: Context): Float { 15 | val resources = context.resources 16 | val metrics = resources.displayMetrics 17 | return this / (metrics.densityDpi / 160f) 18 | } 19 | 20 | fun Int.convertDpToPixel(context: Context): Int { 21 | val resources = context.resources 22 | val metrics = resources.displayMetrics 23 | return this * (metrics.densityDpi / 160) 24 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ui/utils/ViewExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ui.utils 2 | 3 | import android.content.Context 4 | import android.view.View 5 | import android.view.inputmethod.InputMethodManager 6 | 7 | /** 8 | * Created by williamgriffiths on 04/05/2017. 9 | */ 10 | fun View.hideSoftKeyboard() { 11 | val inputMethodManager = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager 12 | inputMethodManager.hideSoftInputFromWindow(windowToken, 0) 13 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ui/utils/listdecorator/EvenPaddingDecorator.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ui.utils.listdecorator 2 | 3 | import android.graphics.Rect 4 | import android.support.v7.widget.RecyclerView 5 | import android.view.View 6 | import com.github.willjgriff.ethereumwallet.ui.utils.convertDpToPixel 7 | 8 | /** 9 | * Created by williamgriffiths on 20/04/2017. 10 | */ 11 | class EvenPaddingDecorator(private val topBottomPadding: Int, 12 | private val leftRightPadding: Int) 13 | : RecyclerView.ItemDecoration() { 14 | 15 | override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) { 16 | super.getItemOffsets(outRect, view, parent, state) 17 | 18 | val topBottomPixelPadding = topBottomPadding.convertDpToPixel(view.context) 19 | val leftRightPixelPadding = leftRightPadding.convertDpToPixel(view.context) 20 | val itemPosition = parent.getChildAdapterPosition(view) 21 | 22 | if (itemPosition == 0) { 23 | outRect.top = topBottomPixelPadding 24 | } 25 | 26 | outRect.apply { 27 | bottom = topBottomPixelPadding 28 | left = leftRightPixelPadding 29 | right = leftRightPixelPadding 30 | } 31 | 32 | } 33 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ui/widget/validatedtextinput/RxTextInputValidation.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ui.widget.validatedtextinput 2 | 3 | import com.github.willjgriff.ethereumwallet.ui.widget.validatedtextinput.validators.Validator 4 | import io.reactivex.Observable 5 | 6 | /** 7 | * Created by williamgriffiths on 04/05/2017. 8 | */ 9 | class RxTextInputValidation(private val validTextInputListener: RxTextInputValidation.ValidTextInputListener, 10 | private val validators: List, 11 | textChangedObservable: Observable) { 12 | 13 | lateinit var textValidObservable: Observable 14 | private set 15 | 16 | val textChangedObservable: Observable = textChangedObservable 17 | .map { it.toString() } 18 | .distinctUntilChanged() 19 | 20 | init { 21 | // TODO: Debug these Observables and check they behave as expected (they may be firing to often) 22 | setupTextValidObservable() 23 | } 24 | 25 | fun setupTextValidObservable() { 26 | textValidObservable = textChangedObservable 27 | .flatMap { isTextValid(it) } 28 | .map { validText -> validText } 29 | .distinctUntilChanged() 30 | 31 | // Hide the error when valid text is emitted 32 | textValidObservable 33 | .filter { isValid -> isValid } 34 | .subscribe { _ -> validTextInputListener.hideError() } 35 | 36 | // Skip until the field has a valid result 37 | val hotValidText = textValidObservable 38 | .publish() 39 | .autoConnect() 40 | val skipUntilValid = hotValidText 41 | .skipUntil(hotValidText.filter { isValid -> isValid }) 42 | 43 | // Show the error when invalid text is emitted 44 | skipUntilValid 45 | .filter { isValid -> !isValid } 46 | .subscribe { _ -> validTextInputListener.showError() } 47 | } 48 | 49 | private fun isTextValid(inputText: String): Observable { 50 | return Observable.fromIterable(validators) 51 | .all { validator -> validator.isValid(inputText) } 52 | .toObservable() 53 | } 54 | 55 | fun setValidateTrigger(validateTrigger: Observable) { 56 | validateTrigger 57 | .flatMap { _ -> textValidObservable } 58 | .filter { isValid -> !isValid } 59 | .subscribe { _ -> validTextInputListener.showError() } 60 | } 61 | 62 | interface ValidTextInputListener { 63 | fun showError() 64 | fun hideError() 65 | } 66 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ui/widget/validatedtextinput/ValidatedTextInputLayout.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ui.widget.validatedtextinput 2 | 3 | import android.content.Context 4 | import android.support.design.widget.TextInputLayout 5 | import android.text.InputType 6 | import android.util.AttributeSet 7 | import com.github.willjgriff.ethereumwallet.R 8 | import com.github.willjgriff.ethereumwallet.ui.utils.inflate 9 | import com.github.willjgriff.ethereumwallet.ui.widget.validatedtextinput.validators.Validator 10 | import com.jakewharton.rxbinding2.widget.RxTextView 11 | import io.reactivex.Observable 12 | import java.util.* 13 | 14 | /** 15 | * Created by williamgriffiths on 04/05/2017. 16 | */ 17 | class ValidatedTextInputLayout : TextInputLayout, RxTextInputValidation.ValidTextInputListener { 18 | 19 | private var validators: List = ArrayList() 20 | private var errorMessage: CharSequence = "" 21 | private lateinit var rxTextInputValidation: RxTextInputValidation 22 | 23 | val textChangedObservable: Observable 24 | get() = rxTextInputValidation.textChangedObservable 25 | 26 | val textValidObservable: Observable 27 | get() = rxTextInputValidation.textValidObservable 28 | 29 | val text: String 30 | get() = editText!!.text.toString() 31 | 32 | init { 33 | context.inflate(R.layout.view_validated_text_input_layout, this, true) 34 | } 35 | 36 | constructor(context: Context) : super(context) { 37 | setupObservables() 38 | } 39 | 40 | constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { 41 | setupAttributes(attrs) 42 | setupObservables() 43 | } 44 | 45 | constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { 46 | setupAttributes(attrs) 47 | setupObservables() 48 | } 49 | 50 | private fun setupObservables() { 51 | val textChanged = RxTextView.textChanges(editText!!) 52 | rxTextInputValidation = RxTextInputValidation(this, validators, textChanged) 53 | } 54 | 55 | private fun setupAttributes(attrs: AttributeSet) { 56 | val typedArray = context.obtainStyledAttributes(attrs, R.styleable.ValidatedTextInputLayout) 57 | try { 58 | val inputType = typedArray.getInteger(R.styleable.ValidatedTextInputLayout_android_inputType, InputType.TYPE_CLASS_TEXT) 59 | editText!!.inputType = inputType 60 | 61 | val validatorsFlag = typedArray.getInteger(R.styleable.ValidatedTextInputLayout_validator, 0) 62 | validators = ValidatorFactory().getValidators(validatorsFlag) 63 | 64 | errorMessage = typedArray.getText(R.styleable.ValidatedTextInputLayout_error_text) 65 | 66 | } finally { 67 | typedArray.recycle() 68 | } 69 | } 70 | 71 | fun setCheckValidationTrigger(validateTrigger: Observable) { 72 | rxTextInputValidation.setValidateTrigger(validateTrigger) 73 | } 74 | 75 | override fun showError() { 76 | error = errorMessage 77 | } 78 | 79 | override fun hideError() { 80 | isErrorEnabled = false 81 | } 82 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ui/widget/validatedtextinput/ValidatorFactory.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ui.widget.validatedtextinput 2 | 3 | import com.github.willjgriff.ethereumwallet.ui.widget.validatedtextinput.validators.NotEmptyValidator 4 | import com.github.willjgriff.ethereumwallet.ui.widget.validatedtextinput.validators.Validator 5 | import java.util.* 6 | 7 | /** 8 | * Created by williamgriffiths on 04/05/2017. 9 | * 10 | * If you want to understand how this works, read: 11 | * https://medium.com/@JakobUlbrich/flag-attributes-in-android-how-to-use-them-ac4ec8aee7d1#.ivwy7y15b 12 | */ 13 | class ValidatorFactory { 14 | 15 | private val NOT_EMPTY = 1 16 | 17 | fun getValidators(flagSet: Int): List { 18 | val validators = ArrayList() 19 | 20 | if (containsFlag(flagSet, NOT_EMPTY)) { 21 | validators.add(NotEmptyValidator()) 22 | } 23 | 24 | return validators 25 | } 26 | 27 | private fun containsFlag(flagSet: Int, flag: Int): Boolean { 28 | return flagSet or flag == flagSet 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ui/widget/validatedtextinput/validators/NotEmptyValidator.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ui.widget.validatedtextinput.validators 2 | 3 | /** 4 | * Created by williamgriffiths on 04/05/2017. 5 | */ 6 | class NotEmptyValidator : Validator { 7 | 8 | override fun isValid(text: String): Boolean { 9 | return text.isNotEmpty() 10 | } 11 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/ui/widget/validatedtextinput/validators/Validator.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.ui.widget.validatedtextinput.validators 2 | 3 | /** 4 | * Created by williamgriffiths on 04/05/2017. 5 | */ 6 | interface Validator { 7 | 8 | fun isValid(text: String): Boolean 9 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/utils/ErrorExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.utils 2 | 3 | import android.content.Context 4 | import android.net.ConnectivityManager 5 | 6 | /** 7 | * Created by williamgriffiths on 06/05/2017. 8 | */ 9 | fun Context.isConnected(): Boolean { 10 | val connectivityManager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager 11 | val info = connectivityManager.activeNetworkInfo 12 | return info != null && info.isConnected 13 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/utils/ObservableExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.utils 2 | 3 | import io.reactivex.Observable 4 | import io.reactivex.android.schedulers.AndroidSchedulers 5 | import io.reactivex.schedulers.Schedulers 6 | 7 | fun Observable.androidIoSchedule(): Observable = 8 | subscribeOn(Schedulers.io()) 9 | .observeOn(AndroidSchedulers.mainThread()) 10 | 11 | fun Observable.replayEmissions(numberOfItems: Int): Observable = 12 | replay(numberOfItems).autoConnect() -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/utils/resettablelazy/Funcs.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.utils.resettablelazy 2 | 3 | /** 4 | * Created by williamgriffiths on 28/04/2017. 5 | */ 6 | fun resettableLazy(manager: ResettableLazyManager, init: ()->PROPTYPE): ResettableLazy { 7 | return ResettableLazy(manager, init) 8 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/utils/resettablelazy/ResettableLazy.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.utils.resettablelazy 2 | 3 | import kotlin.reflect.KProperty 4 | 5 | /** 6 | * Created by williamgriffiths on 28/04/2017. 7 | */ 8 | class ResettableLazy(val manager: ResettableLazyManager, val init: ()->PROPTYPE): ResettableLazyManager.Resettable { 9 | @Volatile var lazyHolder = makeInitBlock() 10 | 11 | operator fun getValue(thisRef: Any?, property: KProperty<*>): PROPTYPE { 12 | return lazyHolder.value 13 | } 14 | 15 | override fun reset() { 16 | lazyHolder = makeInitBlock() 17 | } 18 | 19 | fun makeInitBlock(): Lazy { 20 | return lazy { 21 | manager.register(this) 22 | init() 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/github/willjgriff/ethereumwallet/utils/resettablelazy/ResettableLazyManager.kt: -------------------------------------------------------------------------------- 1 | package com.github.willjgriff.ethereumwallet.utils.resettablelazy 2 | 3 | import java.util.* 4 | 5 | /** 6 | * Created by williamgriffiths on 28/04/2017. 7 | */ 8 | class ResettableLazyManager { 9 | // we synchronize to make sure the timing of a reset() call and new inits do not collide 10 | val managedDelegates = LinkedList() 11 | 12 | fun register(managed: Resettable) { 13 | synchronized (managedDelegates) { 14 | managedDelegates.add(managed) 15 | } 16 | } 17 | 18 | fun reset() { 19 | synchronized (managedDelegates) { 20 | managedDelegates.forEach { it.reset() } 21 | managedDelegates.clear() 22 | } 23 | } 24 | 25 | interface Resettable { 26 | fun reset() 27 | } 28 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_node_status_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_receive_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_send_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_settings_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_transactions_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/selector_navigation_icon_color.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/layout/controller_create_account.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 31 | 32 |