├── .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 |
44 |
45 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/controller_create_account_pre_navigation.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
12 |
13 |
17 |
18 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/controller_create_account_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/controller_list.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
11 |
12 |
16 |
17 |
18 |
19 |
25 |
26 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/controller_navigation.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
43 |
44 |
45 |
46 |
52 |
53 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/controller_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
16 |
17 |
28 |
29 |
45 |
46 |
58 |
59 |
69 |
70 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/controller_settings_change_address.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/controller_transactions.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
14 |
15 |
21 |
22 |
28 |
29 |
30 |
31 |
36 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/toolbar.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/view_change_address_header.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/view_change_address_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/view_controller_settings_delete_validated_input.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/view_controller_transactions_search_range_dialog.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
21 |
22 |
33 |
34 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/view_node_details_header_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/view_node_status_peer_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/view_transaction_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
16 |
17 |
21 |
22 |
27 |
28 |
34 |
35 |
43 |
44 |
45 |
46 |
51 |
52 |
57 |
58 |
66 |
67 |
68 |
69 |
70 |
71 |
75 |
76 |
81 |
82 |
90 |
91 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/view_validated_text_input_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_bottom_navigation.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/willjgriff/android-ethereum-wallet/3e8b894606df7fd5595078971ca0883cbf78badf/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/willjgriff/android-ethereum-wallet/3e8b894606df7fd5595078971ca0883cbf78badf/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/willjgriff/android-ethereum-wallet/3e8b894606df7fd5595078971ca0883cbf78badf/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/willjgriff/android-ethereum-wallet/3e8b894606df7fd5595078971ca0883cbf78badf/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/willjgriff/android-ethereum-wallet/3e8b894606df7fd5595078971ca0883cbf78badf/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/willjgriff/android-ethereum-wallet/3e8b894606df7fd5595078971ca0883cbf78badf/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/willjgriff/android-ethereum-wallet/3e8b894606df7fd5595078971ca0883cbf78badf/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/willjgriff/android-ethereum-wallet/3e8b894606df7fd5595078971ca0883cbf78badf/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/willjgriff/android-ethereum-wallet/3e8b894606df7fd5595078971ca0883cbf78badf/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/willjgriff/android-ethereum-wallet/3e8b894606df7fd5595078971ca0883cbf78badf/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap/ethereum_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/willjgriff/android-ethereum-wallet/3e8b894606df7fd5595078971ca0883cbf78badf/app/src/main/res/mipmap/ethereum_logo.png
--------------------------------------------------------------------------------
/app/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | // Note the next flag value should be 4
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | #3F51B5
5 | #303F9F
6 | #FF4081
7 | #42FF9800
8 |
9 |
10 | #212121
11 | #757575
12 | @color/white
13 | #FFFFFF
14 | #BDBDBD
15 |
16 |
17 | @color/white
18 | #f7f7f7
19 | #f4f4f4
20 | #818181
21 |
22 |
23 | #FFFFFF
24 | #000000
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 12sp
4 | 14sp
5 | 16sp
6 | 20sp
7 | 24sp
8 | 34sp
9 |
10 | 14sp
11 |
12 | 4dp
13 | 8dp
14 | 16dp
15 | 24dp
16 | 36dp
17 | 48dp
18 | 56dp
19 | 64dp
20 | 72dp
21 |
22 | 4dp
23 | 8dp
24 |
25 |
26 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/values/text_styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |
7 |
11 |
12 |
16 |
17 |
21 |
22 |
25 |
26 |
29 |
30 |
--------------------------------------------------------------------------------
/app/src/test/kotlin/com/github/willjgriff/ethereumwallet/ethereum/account/AddressManagerTest.kt:
--------------------------------------------------------------------------------
1 | package com.github.willjgriff.ethereumwallet.ethereum.account
2 |
3 | import com.github.willjgriff.ethereumwallet.ethereum.address.model.DomainAddress
4 | import com.github.willjgriff.ethereumwallet.ethereum.address.ActiveAddress
5 | import com.github.willjgriff.ethereumwallet.ethereum.address.AddressAdapter
6 | import com.github.willjgriff.ethereumwallet.ethereum.address.AddressManager
7 | import com.nhaarman.mockito_kotlin.any
8 | import com.nhaarman.mockito_kotlin.doReturn
9 | import com.nhaarman.mockito_kotlin.mock
10 | import com.nhaarman.mockito_kotlin.verify
11 | import org.amshove.kluent.shouldEqual
12 | import org.junit.Before
13 | import org.junit.Test
14 |
15 | /**
16 | * Created by Will on 07/02/2017.
17 | */
18 | class AddressManagerTest {
19 |
20 | private lateinit var mSubject: AddressManager;
21 |
22 | private var MOCK_HEX_ADDRESS = "0x349j8w983"
23 | private var mockAddress: DomainAddress = mock {
24 | on { hex } doReturn MOCK_HEX_ADDRESS
25 | }
26 | private var mockAccounts: List = listOf(mockAddress)
27 | private var mMockAddressAdapter: AddressAdapter = mock {
28 | on { getAddresses() } doReturn mockAccounts
29 | on { newAddress(any()) } doReturn mockAddress
30 | }
31 | private var mMockActiveAddress: ActiveAddress = mock {
32 | on { getHex() } doReturn MOCK_HEX_ADDRESS
33 | }
34 |
35 | @Before
36 | fun setupEthereumAccountManagerKotlinTest() {
37 | mSubject = AddressManager(mMockAddressAdapter, mMockActiveAddress)
38 | }
39 |
40 | @Test
41 | fun createAccount_callsNewAccountOnAccountManager() {
42 | val MOCK_PASSWORD = "password"
43 |
44 | mSubject.createActiveAddress(MOCK_PASSWORD)
45 |
46 | verify(mMockAddressAdapter).newAddress(MOCK_PASSWORD)
47 | verify(mMockActiveAddress).setHex(any())
48 | }
49 |
50 | @Test
51 | fun getActiveAccount_returnsExpectedAccount() {
52 | val actualAddress = mSubject.getActiveAddress()
53 |
54 | mockAddress shouldEqual actualAddress
55 | }
56 | }
--------------------------------------------------------------------------------
/app/src/test/kotlin/com/github/willjgriff/ethereumwallet/ethereum/common/model/EtherAmountTest.kt:
--------------------------------------------------------------------------------
1 | package com.github.willjgriff.ethereumwallet.ethereum.common.model
2 |
3 | import com.github.willjgriff.ethereumwallet.ethereum.common.model.Denomination.*
4 | import org.amshove.kluent.shouldEqual
5 | import org.junit.Test
6 | import java.math.BigDecimal
7 |
8 | /**
9 | * Created by williamgriffiths on 05/05/2017.
10 | */
11 | class EtherAmountTest {
12 |
13 | @Test
14 | fun convertWeiToWei() {
15 | val expectedWeiAmount = EtherAmount(BigDecimal("123"), WEI)
16 |
17 | val actualWeiAmount = expectedWeiAmount.to(WEI)
18 |
19 | actualWeiAmount shouldEqual expectedWeiAmount
20 | }
21 |
22 | @Test
23 | fun convertKweiToKwei() {
24 | val expectedKweiAmount = EtherAmount(BigDecimal("123"), KWEI)
25 |
26 | val actualKweiAmount = expectedKweiAmount.to(KWEI)
27 |
28 | actualKweiAmount shouldEqual expectedKweiAmount
29 | }
30 |
31 | @Test
32 | fun convertWeiToKwei() {
33 | val weiAmount = EtherAmount(BigDecimal("1001"), WEI)
34 | val expectedKweiAmount = EtherAmount(BigDecimal("1.001"), KWEI)
35 |
36 | val actualKweiAmount = weiAmount.to(KWEI)
37 |
38 | actualKweiAmount shouldEqual expectedKweiAmount
39 | }
40 |
41 | @Test
42 | fun convertKweiToWei() {
43 | val kweiAmount = EtherAmount(BigDecimal("1001"), KWEI)
44 | val expectedWeiAmount = EtherAmount(BigDecimal("1001000"), WEI)
45 |
46 | val actualWeiAmount = kweiAmount.to(WEI)
47 |
48 | actualWeiAmount shouldEqual expectedWeiAmount
49 | }
50 |
51 | @Test
52 | fun convertWeiToEther() {
53 | val weiAmount = EtherAmount(BigDecimal("1000000000"), WEI)
54 | val expectedEtherAmount = EtherAmount(BigDecimal("0.000000001"), ETHER)
55 |
56 | val actualEtherAmount = weiAmount.to(ETHER)
57 |
58 | actualEtherAmount shouldEqual expectedEtherAmount
59 | }
60 |
61 | @Test
62 | fun convertEtherToWei() {
63 | val etherAmount = EtherAmount(BigDecimal("0.0001234"), ETHER)
64 | val expectedWeiAmount = EtherAmount(BigDecimal("123400000000000"), WEI)
65 |
66 | val weiAmount = etherAmount.to(WEI)
67 |
68 | weiAmount shouldEqual expectedWeiAmount
69 | }
70 | }
--------------------------------------------------------------------------------
/app/src/test/kotlin/com/github/willjgriff/ethereumwallet/ethereum/common/model/UnitConverterExtensionsTest.kt:
--------------------------------------------------------------------------------
1 | package com.github.willjgriff.ethereumwallet.ethereum.common.model
2 |
3 | import com.github.willjgriff.ethereumwallet.ethereum.common.fromDenominationToWei
4 | import com.github.willjgriff.ethereumwallet.ethereum.common.fromWeiTo
5 | import org.amshove.kluent.shouldEqual
6 | import org.junit.Test
7 | import java.math.BigDecimal
8 | import java.math.BigInteger
9 |
10 | /**
11 | * Created by williamgriffiths on 05/05/2017.
12 | */
13 | class UnitConverterExtensionsTest {
14 |
15 | @Test
16 | fun convertWeiStringToEther() {
17 | val weiAmount = BigInteger("1000000000")
18 |
19 | val etherAmount = weiAmount.fromWeiTo(Denomination.ETHER)
20 |
21 | etherAmount shouldEqual BigDecimal("0.000000001")
22 | }
23 |
24 | @Test
25 | fun convertEtherStringToWei() {
26 | val etherAmount = BigDecimal("0.0001234")
27 |
28 | val weiAmount = etherAmount.fromDenominationToWei(Denomination.ETHER)
29 |
30 | weiAmount shouldEqual BigInteger("123400000000000")
31 | }
32 |
33 | @Test
34 | fun convertEtherStringToWei2() {
35 | val etherValue = BigDecimal("0.0000123")
36 |
37 | val weiAmount = etherValue.fromDenominationToWei(Denomination.ETHER)
38 |
39 | weiAmount shouldEqual BigInteger("12300000000000")
40 | }
41 |
42 | @Test
43 | fun convertWeiToEtherString2() {
44 | val weiAmount = BigInteger("12300000000000")
45 |
46 | val etherString = weiAmount.fromWeiTo(Denomination.ETHER)
47 |
48 | etherString shouldEqual BigDecimal("0.0000123")
49 | }
50 | }
--------------------------------------------------------------------------------
/app/src/test/kotlin/com/github/willjgriff/ethereumwallet/ethereum/icap/BaseConverterTest.kt:
--------------------------------------------------------------------------------
1 | package com.github.willjgriff.ethereumwallet.ethereum.icap
2 |
3 | import com.github.willjgriff.ethereumwallet.ethereum.icap.BaseConverter
4 | import org.amshove.kluent.shouldEqual
5 | import org.junit.Test
6 |
7 | /**
8 | * Created by Will on 01/03/2017.
9 | */
10 | class BaseConverterTest {
11 |
12 | private val subject = BaseConverter()
13 |
14 | @Test
15 | fun base16ToBase36_returnsExpectedValue() {
16 | val expectedNumber = "P2J0C65CFU410SM2IXXO687WQO2HMJV".toLowerCase()
17 | val actualNumber = subject.base16ToBase36("D69F2FF2893C73B5eF4959a2ce85Ab1B1d35CE6B")
18 | actualNumber shouldEqual expectedNumber
19 | }
20 |
21 | @Test
22 | fun base16ToBase36_returnsExpectedValue2() {
23 | val expectedNumber = "38O073KYGTWWZN0F2WZ0R8PX5ZPPZS".toLowerCase()
24 | val actualNumber = subject.base16ToBase36("c5496aee77c1ba1f0854206a26dda82a81d6d8")
25 | actualNumber shouldEqual expectedNumber
26 | }
27 |
28 | @Test
29 | fun base36ToBase16_returnsExpectedValue() {
30 | val expectedNumber = "D69F2FF2893C73B5eF4959a2ce85Ab1B1d35CE6B".toLowerCase()
31 | val actualNumber = subject.base36ToBase16("P2J0C65CFU410SM2IXXO687WQO2HMJV")
32 | actualNumber shouldEqual expectedNumber
33 | }
34 |
35 | @Test
36 | fun base36ToBase16_returnsExpectedValue2() {
37 | val expectedNumber = "c5496aee77c1ba1f0854206a26dda82a81d6d8".toLowerCase()
38 | val actualNumber = subject.base36ToBase16("38O073KYGTWWZN0F2WZ0R8PX5ZPPZS")
39 | actualNumber shouldEqual expectedNumber
40 | }
41 |
42 | @Test
43 | fun base36ToInteger_returnsExpectedIntegerString() {
44 | val expectedInteger = "25219012651215304102822218333324687322624217221931"
45 | val actualInteger = subject.base36ToInteger("P2J0C65CFU410SM2IXXO687WQO2HMJV")
46 | actualInteger shouldEqual expectedInteger
47 | }
48 |
49 | @Test
50 | fun base36ToInteger_returnsExpectedIntegerString2() {
51 | val expectedInteger = "38240732034162932323523015232350278253353525253528"
52 | val actualInteger = subject.base36ToInteger("38O073KYGTWWZN0F2WZ0R8PX5ZPPZS")
53 | actualInteger shouldEqual expectedInteger
54 | }
55 |
56 | }
--------------------------------------------------------------------------------
/app/src/test/kotlin/com/github/willjgriff/ethereumwallet/ethereum/icap/IbanChecksumUtilsTest.kt:
--------------------------------------------------------------------------------
1 | package com.github.willjgriff.ethereumwallet.ethereum.icap
2 |
3 | import com.github.willjgriff.ethereumwallet.ethereum.icap.BaseConverter
4 | import com.github.willjgriff.ethereumwallet.ethereum.icap.IbanChecksumUtils
5 | import com.nhaarman.mockito_kotlin.mock
6 | import com.nhaarman.mockito_kotlin.whenever
7 | import org.amshove.kluent.shouldBe
8 | import org.amshove.kluent.shouldEqual
9 | import org.junit.Test
10 |
11 | /**
12 | * Created by Will on 11/03/2017.
13 | */
14 | class IbanChecksumUtilsTest {
15 |
16 | private val mockBaseConverter = mock()
17 | private val subject = IbanChecksumUtils(mockBaseConverter)
18 |
19 | @Test
20 | fun createChecksum_returnsCorrectChecksum() {
21 | val expectedChecksum = 8
22 | setupMockBaseConverterForTest1()
23 | val actualChecksum = subject.createChecksum("P2J0C65CFU410SM2IXXO687WQO2HMJV")
24 | actualChecksum shouldEqual expectedChecksum
25 | }
26 |
27 | @Test
28 | fun createChecksum_returnsCorrectChecksum2() {
29 | val expectedChecksum = 73
30 | setupMockBaseConverterForTest2()
31 | val actualChecksum = subject.createChecksum("38O073KYGTWWZN0F2WZ0R8PX5ZPPZS")
32 | actualChecksum shouldEqual expectedChecksum
33 | }
34 |
35 | @Test
36 | fun verifyChecksum_withCorrectInputReturnsTrue() {
37 | setupMockBaseConverterForTest1()
38 | val addressIsValid = subject.verifyChecksum("P2J0C65CFU410SM2IXXO687WQO2HMJV", 8)
39 | addressIsValid shouldBe true
40 | }
41 |
42 | @Test
43 | fun verifyChecksum_withCorrectInputReturnsTrue2() {
44 | setupMockBaseConverterForTest2()
45 | val addressIsValid = subject.verifyChecksum("38O073KYGTWWZN0F2WZ0R8PX5ZPPZS", 73)
46 | addressIsValid shouldBe true
47 | }
48 |
49 | private fun setupMockBaseConverterForTest1() {
50 | whenever(mockBaseConverter.base36ToInteger("P2J0C65CFU410SM2IXXO687WQO2HMJVXE"))
51 | .then { "252190126512153041028222183333246873226242172219313314" }
52 | }
53 |
54 | private fun setupMockBaseConverterForTest2() {
55 | whenever(mockBaseConverter.base36ToInteger("38O073KYGTWWZN0F2WZ0R8PX5ZPPZSXE"))
56 | .then { "382407320341629323235230152323502782533535252535283314" }
57 | }
58 |
59 | @Test
60 | fun verifyChecksum_withIncorrectInputReturnsFalse() {
61 | whenever(mockBaseConverter.base36ToInteger("P2J0C65CFU410SM2IXXO687WQO2HMJWXE"))
62 | .then { "252190126512153041028222183333246873226242172219323314" }
63 | val addressIsValid = subject.verifyChecksum("P2J0C65CFU410SM2IXXO687WQO2HMJW", 8)
64 | addressIsValid shouldBe false
65 | }
66 |
67 | @Test
68 | fun verifyChecksum_withCorrectInputReturnsFalse2() {
69 | whenever(mockBaseConverter.base36ToInteger("48O073KYGTWWZN0F2WZ0R8PX5ZPPZSXE"))
70 | .then { "482407320341629323235230152323502782533535252535283314" }
71 | val addressIsValid = subject.verifyChecksum("48O073KYGTWWZN0F2WZ0R8PX5ZPPZS", 73)
72 | addressIsValid shouldBe false
73 | }
74 |
75 | @Test
76 | fun convertCheckSumToDoubleDigitString_convertsToString() {
77 | val expectedChecksum = "73"
78 | val actualChecksum = subject.convertChecksumToDoubleDigitString(73)
79 | actualChecksum shouldEqual expectedChecksum
80 | }
81 |
82 | @Test
83 | fun convertCheckSumToDoubleDigitString_addsLeading0() {
84 | val expectedChecksum = "08"
85 | val actualChecksum = subject.convertChecksumToDoubleDigitString(8)
86 | actualChecksum shouldEqual expectedChecksum
87 | }
88 | }
--------------------------------------------------------------------------------
/app/src/test/kotlin/com/github/willjgriff/ethereumwallet/ethereum/icap/IbanGeneratorTest.kt:
--------------------------------------------------------------------------------
1 | package com.github.willjgriff.ethereumwallet.ethereum.icap
2 |
3 | import com.github.willjgriff.ethereumwallet.ethereum.icap.BaseConverter
4 | import com.github.willjgriff.ethereumwallet.ethereum.icap.IbanChecksumUtils
5 | import com.github.willjgriff.ethereumwallet.ethereum.icap.IbanGenerator
6 | import com.nhaarman.mockito_kotlin.mock
7 | import com.nhaarman.mockito_kotlin.whenever
8 | import org.amshove.kluent.shouldEqual
9 | import org.junit.Test
10 |
11 | /**
12 | * Created by Will on 12/03/2017.
13 | */
14 | class IbanGeneratorTest {
15 |
16 | private val mockBaseConverter = mock()
17 | private val mockIbanChecksumUtils = mock()
18 | private val subject = IbanGenerator(mockBaseConverter, mockIbanChecksumUtils)
19 |
20 | @Test
21 | fun createIbanFromAddress_returnsValidIban() {
22 | val expectedIban = "iban:XE08P2J0C65CFU410SM2IXXO687WQO2HMJV".toLowerCase()
23 | setupMocksForTest1()
24 | val actualIban = subject.createIbanFromHexAddress("0xD69F2FF2893C73B5eF4959a2ce85Ab1B1d35CE6B")
25 | actualIban shouldEqual expectedIban
26 | }
27 |
28 | // TODO: Test some transactions with leading zeros in the address.
29 | @Test
30 | fun createIbanFromAddress_returnsValidIban2() {
31 | val expectedIban = "iban:XE7338O073KYGTWWZN0F2WZ0R8PX5ZPPZS".toLowerCase()
32 | whenever(mockBaseConverter.base16ToBase36("00c5496aee77c1ba1f0854206a26dda82a81d6d8"))
33 | .then { "38o073kygtwwzn0f2wz0r8px5zppzs" }
34 | whenever(mockIbanChecksumUtils.createChecksum("38o073kygtwwzn0f2wz0r8px5zppzs"))
35 | .then { 73 }
36 | whenever(mockIbanChecksumUtils.convertChecksumToDoubleDigitString(73))
37 | .then { "73" }
38 |
39 | val actualIban = subject.createIbanFromHexAddress("0x00c5496aee77c1ba1f0854206a26dda82a81d6d8")
40 |
41 | actualIban shouldEqual expectedIban
42 | }
43 |
44 | @Test
45 | fun createIbanWithAmount_returnsValidIbanWithAmount() {
46 | val expectedIban = "iban:XE08P2J0C65CFU410SM2IXXO687WQO2HMJV?amount=0.023".toLowerCase()
47 | setupMocksForTest1()
48 | val actualIban = subject.createIbanFromHexWithAmount("0xD69F2FF2893C73B5eF4959a2ce85Ab1B1d35CE6B", 0.023)
49 | actualIban shouldEqual expectedIban
50 | }
51 |
52 | private fun setupMocksForTest1() {
53 | whenever(mockBaseConverter.base16ToBase36("D69F2FF2893C73B5eF4959a2ce85Ab1B1d35CE6B"))
54 | .then { "p2j0c65cfu410sm2ixxo687wqo2hmjv" }
55 | whenever(mockIbanChecksumUtils.createChecksum("p2j0c65cfu410sm2ixxo687wqo2hmjv"))
56 | .then { 8 }
57 | whenever(mockIbanChecksumUtils.convertChecksumToDoubleDigitString(8))
58 | .then { "08" }
59 | }
60 | }
--------------------------------------------------------------------------------
/app/src/test/kotlin/com/github/willjgriff/ethereumwallet/ethereum/payment/SendDomainTransactionGeneratorTest.kt:
--------------------------------------------------------------------------------
1 | package com.github.willjgriff.ethereumwallet.ethereum.payment
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.transaction.SendTransactionGenerator
7 | import com.github.willjgriff.ethereumwallet.ethereum.transaction.model.SendTransaction
8 | import com.nhaarman.mockito_kotlin.mock
9 | import com.nhaarman.mockito_kotlin.whenever
10 | import org.amshove.kluent.shouldEqual
11 | import org.junit.Before
12 | import org.junit.Test
13 | import java.math.BigDecimal
14 |
15 | /**
16 | * Created by Will on 12/03/2017.
17 | */
18 | class SendDomainTransactionGeneratorTest {
19 |
20 | private val mockBaseConverter: BaseConverter = mock()
21 | private val subject = SendTransactionGenerator(mockBaseConverter)
22 |
23 | @Before
24 | fun setupSendPaymentGeneratorTest() {
25 | whenever(mockBaseConverter.base36ToBase16("P2J0C65CFU410SM2IXXO687WQO2HMJV"))
26 | .then { "d69f2ff2893c73b5ef4959a2ce85ab1b1d35ce6b" }
27 | }
28 |
29 | @Test
30 | fun getSendPaymentFromIban_returnsDefaultSendPaymentWithIncorrectFormat() {
31 | val expectedPayment = SendTransaction()
32 | val actualPayment = subject.getSendPaymentFromIban("iba:XE08P2J0C65CFU410SM2IXXO687WQO2HMJV?amount=0.023")
33 | actualPayment shouldEqual expectedPayment
34 | }
35 |
36 | @Test
37 | fun getSendPaymentFromIban_returnsDefaultSendPaymentWithTooShortIban() {
38 | val expectedPayment = SendTransaction()
39 | val actualPayment = subject.getSendPaymentFromIban("iban:XE08P2J0C65CFU410SM2IXXO687WQO2HMJ")
40 | actualPayment shouldEqual expectedPayment
41 | }
42 |
43 | @Test
44 | fun getSendPaymentFromIban_returnsSendPaymentWithDefaultAmountAndLabel() {
45 | val expectedPayment = SendTransaction("0xD69F2FF2893C73B5eF4959a2ce85Ab1B1d35CE6B".toLowerCase())
46 | val actualPayment = subject.getSendPaymentFromIban("iban:XE08P2J0C65CFU410SM2IXXO687WQO2HMJV")
47 | actualPayment shouldEqual expectedPayment
48 | }
49 |
50 | @Test
51 | fun getSendPaymentFromIban_returnsSendPaymentWithCorrectAmount() {
52 | val etherAmount = EtherAmount(BigDecimal("23"), Denomination.ETHER)
53 | val expectedPayment = SendTransaction("0xD69F2FF2893C73B5eF4959a2ce85Ab1B1d35CE6B".toLowerCase(), etherAmount)
54 | val actualPayment = subject.getSendPaymentFromIban("iban:XE08P2J0C65CFU410SM2IXXO687WQO2HMJV?amount=23")
55 | actualPayment shouldEqual expectedPayment
56 | }
57 |
58 | @Test
59 | fun getSendPaymentFromIban_returnsSendPaymentWithCorrectAmountAndLabel() {
60 | val etherAmount = EtherAmount(BigDecimal("23"), Denomination.ETHER)
61 | val expectedPayment = SendTransaction("0xD69F2FF2893C73B5eF4959a2ce85Ab1B1d35CE6B".toLowerCase(), etherAmount, "quepasa")
62 | val actualPayment = subject.getSendPaymentFromIban("iban:XE08P2J0C65CFU410SM2IXXO687WQO2HMJV?amount=23&label=quepasa")
63 | actualPayment shouldEqual expectedPayment
64 | }
65 | }
--------------------------------------------------------------------------------
/app/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker:
--------------------------------------------------------------------------------
1 | mock-maker-inline
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | ext.kotlin_version = '1.1.1'
5 |
6 | repositories {
7 | jcenter()
8 | }
9 | dependencies {
10 | classpath 'com.android.tools.build:gradle:2.3.1'
11 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
12 |
13 | // NOTE: Do not place your application dependencies here; they belong
14 | // in the individual module build.gradle files
15 | }
16 | }
17 |
18 | allprojects {
19 | repositories {
20 | jcenter()
21 | }
22 | }
23 |
24 | task clean(type: Delete) {
25 | delete rootProject.buildDir
26 | }
27 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/willjgriff/android-ethereum-wallet/3e8b894606df7fd5595078971ca0883cbf78badf/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sun Jan 22 01:31:09 GMT 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.4.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------