From 6a19f6679578ef0fe595e3867f00d4543f079484 Mon Sep 17 00:00:00 2001 From: Ry Jones Date: Fri, 17 Aug 2018 16:43:50 -0700 Subject: [PATCH 1/5] Add Apache 2.0 LICENSE file Signed-off-by: Ry Jones --- LICENSE | 209 +++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 199 insertions(+), 10 deletions(-) diff --git a/LICENSE b/LICENSE index 56b60cf..d645695 100644 --- a/LICENSE +++ b/LICENSE @@ -1,13 +1,202 @@ -Copyright 2016 Soramitsu Co., Ltd. -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ - http://www.apache.org/licenses/LICENSE-2.0 + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. From 90557b1dab29e0e2b6c97435c44546b9f9066559 Mon Sep 17 00:00:00 2001 From: Ry Jones Date: Sat, 28 Sep 2019 19:00:08 -0700 Subject: [PATCH 2/5] Add default SECURITY policy Signed-off-by: Ry Jones --- SECURITY.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..2f4eba8 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,23 @@ +# Hyperledger Security Policy + +## Reporting a Security Bug + +If you think you have discovered a security issue in any of the Hyperledger +projects, we'd love to hear from you. We will take all security bugs +seriously and if confirmed upon investigation we will patch it within a +reasonable amount of time and release a public security bulletin discussing +the impact and credit the discoverer. + +There are two ways to report a security bug. The easiest is to email a +description of the flaw and any related information (e.g. reproduction +steps, version) to +[security at hyperledger dot org](mailto:security@hyperledger.org). + +The other way is to file a confidential security bug in our +[JIRA bug tracking system](https://jira.hyperledger.org). +Be sure to set the “Security Level” to “Security issue”. + +The process by which the Hyperledger Security Team handles security bugs +is documented further in our +[Defect Response](https://wiki.hyperledger.org/display/HYP/Defect+Response) +page on our [wiki](https://wiki.hyperledger.org). From 361994fce9d7f53a2188f4536f82405535e52cae Mon Sep 17 00:00:00 2001 From: mrzizik Date: Sat, 30 Nov 2019 23:15:00 +0300 Subject: [PATCH 3/5] initial Signed-off-by: mrzizik --- {iroha-android-bindings => app}/.gitignore | 0 app/build.gradle | 66 +++++ .../proguard-rules.pro | 0 .../irohaandroid/ExampleInstrumentedTest.kt | 24 ++ app/src/main/AndroidManifest.xml | 32 +++ .../soramitsu/irohaandroid/IrohaSampleApp.kt | 15 ++ .../data/transfer/TransferRepository.kt | 17 ++ .../data/transfer/TransferRepositoryImpl.kt | 102 ++++++++ .../irohaandroid/data/user/UserRepository.kt | 27 ++ .../data/user/UserRepositoryImpl.kt | 153 +++++++++++ .../user/datasource/PrefsUserDatasource.kt | 61 +++++ .../data/user/datasource/UserDatasource.kt | 22 ++ .../soramitsu/irohaandroid/di/AppComponent.kt | 32 +++ .../co/soramitsu/irohaandroid/di/AppModule.kt | 108 ++++++++ .../soramitsu/irohaandroid/di/ViewModelKey.kt | 12 + .../irohaandroid/domain/MainInteractor.kt | 49 ++++ .../irohaandroid/domain/SignupInteractor.kt | 18 ++ .../presentation/base/BaseActivity.kt | 59 +++++ .../presentation/base/BaseFragment.kt | 40 +++ .../presentation/base/BaseViewModel.kt | 37 +++ .../presentation/base/BioEnterDialog.kt | 43 ++++ .../presentation/base/ChooserDialog.kt | 33 +++ .../base/IrohaViewModelFactory.kt | 22 ++ .../presentation/base/LoadingDialog.kt | 15 ++ .../presentation/main/MainActivity.kt | 90 +++++++ .../presentation/main/MainViewModel.kt | 177 +++++++++++++ .../presentation/main/TabsViewpagerAdapter.kt | 22 ++ .../presentation/main/di/MainComponent.kt | 36 +++ .../presentation/main/di/MainModule.kt | 32 +++ .../main/history/HistoryAdapter.kt | 50 ++++ .../main/history/HistoryFragment.kt | 66 +++++ .../main/receive/ReceiveFragment.kt | 49 ++++ .../presentation/main/send/SendFragment.kt | 114 ++++++++ .../presentation/model/TransactionModel.kt | 8 + .../presentation/model/Transfer.kt | 3 + .../irohaandroid/presentation/model/User.kt | 3 + .../model/mapper/TransactionMapper.kt | 33 +++ .../presentation/signup/SignupActivity.kt | 58 +++++ .../presentation/signup/SignupViewModel.kt | 38 +++ .../presentation/signup/di/SignupComponent.kt | 27 ++ .../presentation/signup/di/SignupModule.kt | 30 +++ .../presentation/splash/SplashActivity.kt | 32 +++ .../presentation/splash/SplashViewModel.kt | 24 ++ .../presentation/splash/di/SplashComponent.kt | 22 ++ .../presentation/splash/di/SplashModule.kt | 30 +++ .../co/soramitsu/irohaandroid/util/Const.kt | 11 + .../irohaandroid/util/DateFormatter.kt | 17 ++ .../co/soramitsu/irohaandroid/util/Event.kt | 43 ++++ .../jp/co/soramitsu/irohaandroid/util/Ext.kt | 37 +++ .../irohaandroid/util/IrohaException.kt | 24 ++ .../soramitsu/irohaandroid/util/PrefsUtil.kt | 29 +++ .../irohaandroid/util/QrCodeDecoder.kt | 41 +++ .../irohaandroid/util/QrCodeGenerator.kt | 37 +++ .../irohaandroid/util/RegistrationState.kt | 6 + .../irohaandroid/util/ResourceManager.kt | 11 + .../src/main/res/drawable-hdpi/background.png | Bin .../src/main/res/drawable-hdpi/ic_logo.png | Bin .../main/res/drawable-hdpi/iroha_logo_big.png | Bin .../src/main/res/drawable-hdpi/qr.png | Bin .../src/main/res/drawable-ldpi/background.png | Bin .../src/main/res/drawable-ldpi/ic_logo.png | Bin .../main/res/drawable-ldpi/iroha_logo_big.png | Bin .../src/main/res/drawable-ldpi/qr.png | Bin .../src/main/res/drawable-mdpi/background.png | Bin .../src/main/res/drawable-mdpi/ic_logo.png | Bin .../main/res/drawable-mdpi/iroha_logo_big.png | Bin .../src/main/res/drawable-mdpi/qr.png | Bin .../drawable-v24/ic_launcher_foreground.xml | 0 .../main/res/drawable-xhdpi/background.png | Bin .../src/main/res/drawable-xhdpi/ic_logo.png | Bin .../res/drawable-xhdpi/iroha_logo_big.png | Bin .../src/main/res/drawable-xhdpi/qr.png | Bin .../main/res/drawable-xxhdpi/background.png | Bin .../src/main/res/drawable-xxhdpi/ic_logo.png | Bin .../res/drawable-xxhdpi/iroha_logo_big.png | Bin .../src/main/res/drawable-xxhdpi/qr.png | Bin .../main/res/drawable-xxxhdpi/background.png | Bin .../src/main/res/drawable-xxxhdpi/ic_logo.png | Bin .../res/drawable-xxxhdpi/iroha_logo_big.png | Bin .../src/main/res/drawable-xxxhdpi/qr.png | Bin app/src/main/res/drawable/bg_splash.xml | 18 ++ .../res/drawable/ic_launcher_background.xml | 0 .../src/main/res/drawable/ic_logout.xml | 0 app/src/main/res/layout/activity_main.xml | 119 +++++++++ app/src/main/res/layout/activity_signup.xml | 95 +++++++ .../src/main/res/layout/dialog_enter_bio.xml | 4 +- app/src/main/res/layout/fragment_history.xml | 36 +++ app/src/main/res/layout/fragment_receive.xml | 45 ++++ app/src/main/res/layout/fragment_send.xml | 98 +++++++ app/src/main/res/layout/item_transaction.xml | 53 ++++ .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + app/src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 2963 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 4905 bytes app/src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2060 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 2783 bytes app/src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 4490 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 6895 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 6387 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 10413 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 9128 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 15132 bytes app/src/main/res/navigation/main.xml | 11 + app/src/main/res/values/colors.xml | 11 + app/src/main/res/values/dimens.xml | 16 ++ app/src/main/res/values/strings.xml | 50 ++++ app/src/main/res/values/styles.xml | 16 ++ .../soramitsu/irohaandroid/ExampleUnitTest.kt | 17 ++ build.gradle | 119 ++++++++- gradle.properties | 18 +- gradle/wrapper/gradle-wrapper.jar | Bin 53636 -> 54329 bytes gradle/wrapper/gradle-wrapper.properties | 4 +- gradlew | 72 +++--- gradlew.bat | 14 +- iroha-android-bindings/bintray.gradle | 52 ---- iroha-android-bindings/build.gradle | 50 ---- iroha-android-bindings/install.gradle | 42 --- .../src/main/AndroidManifest.xml | 2 - .../jp/co/soramitsu/iroha/android/Blob.java | 74 ------ .../soramitsu/iroha/android/ByteVector.java | 78 ------ .../jp/co/soramitsu/iroha/android/Hash.java | 87 ------- .../soramitsu/iroha/android/HashVector.java | 74 ------ .../co/soramitsu/iroha/android/Keypair.java | 58 ----- .../soramitsu/iroha/android/ModelCrypto.java | 54 ---- .../iroha/android/ModelProtoQuery.java | 50 ---- .../iroha/android/ModelProtoTransaction.java | 50 ---- .../iroha/android/ModelQueryBuilder.java | 98 ------- .../android/ModelTransactionBuilder.java | 122 --------- .../soramitsu/iroha/android/PrivateKey.java | 50 ---- .../co/soramitsu/iroha/android/PublicKey.java | 50 ---- .../jp/co/soramitsu/iroha/android/Query.java | 74 ------ ...onst_R_boost__forward_traversal_tag_t.java | 26 -- ...ed_model__interface__SignatureSetType.java | 26 -- ...model__proto__Query__QueryVariantType.java | 26 -- ...del__proto__Transaction__CommandsType.java | 26 -- ...model__proto__Transaction__QuorumType.java | 26 -- .../jp/co/soramitsu/iroha/android/Signed.java | 50 ---- .../soramitsu/iroha/android/StringVector.java | 78 ------ .../soramitsu/iroha/android/Transaction.java | 74 ------ .../iroha/android/UnsignedQuery.java | 54 ---- .../soramitsu/iroha/android/UnsignedTx.java | 54 ---- .../jp/co/soramitsu/iroha/android/iroha.java | 16 -- .../co/soramitsu/iroha/android/irohaJNI.java | 165 ------------ .../main/jniLibs/arm64-v8a/libirohajava.so | Bin 21830232 -> 0 bytes .../main/jniLibs/armeabi-v7a/libirohajava.so | Bin 18018012 -> 0 bytes .../src/main/jniLibs/armeabi/libirohajava.so | Bin 18555820 -> 0 bytes .../src/main/jniLibs/x86/libirohajava.so | Bin 19307024 -> 0 bytes .../src/main/jniLibs/x86_64/libirohajava.so | Bin 21329264 -> 0 bytes .../src/main/res/values/strings.xml | 3 - iroha-android-sample/.gitignore | 1 - iroha-android-sample/build.gradle | 121 --------- iroha-android-sample/proguard-rules.pro | 21 -- .../soramitsu/iroha/android/sample/Const.java | 12 - .../iroha/android/sample/MatcherUtils.java | 59 ----- .../sample/NetworkRequestIdlingResources.java | 37 --- .../sample/main/MainIntegrationTest.java | 46 ---- .../iroha/android/sample/main/MainTest.java | 135 ---------- .../RegistrationIntegrationTest.java | 105 -------- .../sample/registration/RegistrationTest.java | 113 -------- .../src/main/AndroidManifest.xml | 62 ----- .../iroha/android/sample/Constants.java | 13 - .../iroha/android/sample/PreferencesUtil.java | 56 ---- .../android/sample/SampleApplication.java | 38 --- .../iroha/android/sample/data/Account.java | 21 -- .../injection/ApplicationComponent.java | 26 -- .../sample/injection/ApplicationModule.java | 49 ---- .../sample/interactor/AddAssetInteractor.java | 89 ------- .../interactor/CompletableInteractor.java | 22 -- .../interactor/CreateAccountInteractor.java | 94 ------- .../interactor/GenerateQRInteractor.java | 79 ------ .../GetAccountBalanceInteractor.java | 73 ------ .../GetAccountDetailsInteractor.java | 81 ------ .../interactor/GetAccountInteractor.java | 82 ------ .../GetAccountTransactionsInteractor.java | 106 -------- .../android/sample/interactor/Interactor.java | 72 ------ .../interactor/SendAssetInteractor.java | 81 ------ .../SetAccountDetailsInteractor.java | 80 ------ .../sample/interactor/SingleInteractor.java | 25 -- .../android/sample/main/AccountDetail.java | 9 - .../android/sample/main/MainActivity.java | 243 ------------------ .../android/sample/main/MainPresenter.java | 94 ------- .../iroha/android/sample/main/MainView.java | 21 -- .../sample/main/history/HistoryFragment.java | 84 ------ .../sample/main/history/HistoryPresenter.java | 123 --------- .../sample/main/history/HistoryView.java | 9 - .../sample/main/history/Transaction.java | 13 - .../main/history/TransactionDiffChecker.java | 50 ---- .../sample/main/history/TransactionVM.java | 11 - .../main/history/TransactionsAdapter.java | 101 -------- .../main/history/TransactionsViewModel.java | 21 -- .../sample/main/receive/ReceiveFragment.java | 58 ----- .../sample/main/receive/ReceivePresenter.java | 37 --- .../sample/main/receive/ReceiveView.java | 10 - .../sample/main/send/SendFragment.java | 121 --------- .../sample/main/send/SendPresenter.java | 80 ------ .../android/sample/main/send/SendView.java | 9 - .../sample/qrscanner/QRScannerActivity.java | 56 ---- .../registration/RegistrationActivity.java | 103 -------- .../registration/RegistrationPresenter.java | 80 ------ .../sample/registration/RegistrationView.java | 13 - .../src/main/proto/empty.proto | 10 - .../proto/iroha/consensus/yac/proto/yac.proto | 34 --- .../proto/iroha/network/proto/loader.proto | 34 --- .../proto/iroha/network/transport/mst.proto | 20 -- .../proto/iroha/ordering/proto/ordering.proto | 14 - .../src/main/proto/iroha/protocol/block.proto | 35 --- .../main/proto/iroha/protocol/commands.proto | 111 -------- .../main/proto/iroha/protocol/endpoint.proto | 40 --- .../main/proto/iroha/protocol/primitive.proto | 103 -------- .../main/proto/iroha/protocol/proposal.proto | 10 - .../main/proto/iroha/protocol/queries.proto | 79 ------ .../main/proto/iroha/protocol/responses.proto | 109 -------- .../src/main/res/layout/activity_main.xml | 142 ---------- .../main/res/layout/activity_registration.xml | 97 ------- .../src/main/res/layout/fragment_history.xml | 24 -- .../src/main/res/layout/fragment_receive.xml | 47 ---- .../src/main/res/layout/fragment_send.xml | 100 ------- .../res/layout/transaction_header_item.xml | 41 --- .../src/main/res/layout/transaction_item.xml | 63 ----- .../src/main/res/values/colors.xml | 29 --- .../src/main/res/values/strings.xml | 29 --- .../src/main/res/values/styles.xml | 30 --- licenses/android-googletv-license | 2 - licenses/android-sdk-license | 3 - licenses/android-sdk-preview-license | 2 - licenses/google-gdk-license | 2 - licenses/intel-android-extra-license | 2 - licenses/mips-android-sysimage-license | 2 - settings.gradle | 3 +- 229 files changed, 2950 insertions(+), 6114 deletions(-) rename {iroha-android-bindings => app}/.gitignore (100%) create mode 100644 app/build.gradle rename {iroha-android-bindings => app}/proguard-rules.pro (100%) create mode 100644 app/src/androidTest/java/jp/co/soramitsu/irohaandroid/ExampleInstrumentedTest.kt create mode 100644 app/src/main/AndroidManifest.xml create mode 100644 app/src/main/java/jp/co/soramitsu/irohaandroid/IrohaSampleApp.kt create mode 100644 app/src/main/java/jp/co/soramitsu/irohaandroid/data/transfer/TransferRepository.kt create mode 100644 app/src/main/java/jp/co/soramitsu/irohaandroid/data/transfer/TransferRepositoryImpl.kt create mode 100644 app/src/main/java/jp/co/soramitsu/irohaandroid/data/user/UserRepository.kt create mode 100644 app/src/main/java/jp/co/soramitsu/irohaandroid/data/user/UserRepositoryImpl.kt create mode 100644 app/src/main/java/jp/co/soramitsu/irohaandroid/data/user/datasource/PrefsUserDatasource.kt create mode 100644 app/src/main/java/jp/co/soramitsu/irohaandroid/data/user/datasource/UserDatasource.kt create mode 100644 app/src/main/java/jp/co/soramitsu/irohaandroid/di/AppComponent.kt create mode 100644 app/src/main/java/jp/co/soramitsu/irohaandroid/di/AppModule.kt create mode 100644 app/src/main/java/jp/co/soramitsu/irohaandroid/di/ViewModelKey.kt create mode 100644 app/src/main/java/jp/co/soramitsu/irohaandroid/domain/MainInteractor.kt create mode 100644 app/src/main/java/jp/co/soramitsu/irohaandroid/domain/SignupInteractor.kt create mode 100644 app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/base/BaseActivity.kt create mode 100644 app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/base/BaseFragment.kt create mode 100644 app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/base/BaseViewModel.kt create mode 100644 app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/base/BioEnterDialog.kt create mode 100644 app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/base/ChooserDialog.kt create mode 100644 app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/base/IrohaViewModelFactory.kt create mode 100644 app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/base/LoadingDialog.kt create mode 100644 app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/main/MainActivity.kt create mode 100644 app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/main/MainViewModel.kt create mode 100644 app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/main/TabsViewpagerAdapter.kt create mode 100644 app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/main/di/MainComponent.kt create mode 100644 app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/main/di/MainModule.kt create mode 100644 app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/main/history/HistoryAdapter.kt create mode 100644 app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/main/history/HistoryFragment.kt create mode 100644 app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/main/receive/ReceiveFragment.kt create mode 100644 app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/main/send/SendFragment.kt create mode 100644 app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/model/TransactionModel.kt create mode 100644 app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/model/Transfer.kt create mode 100644 app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/model/User.kt create mode 100644 app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/model/mapper/TransactionMapper.kt create mode 100644 app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/signup/SignupActivity.kt create mode 100644 app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/signup/SignupViewModel.kt create mode 100644 app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/signup/di/SignupComponent.kt create mode 100644 app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/signup/di/SignupModule.kt create mode 100644 app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/splash/SplashActivity.kt create mode 100644 app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/splash/SplashViewModel.kt create mode 100644 app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/splash/di/SplashComponent.kt create mode 100644 app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/splash/di/SplashModule.kt create mode 100644 app/src/main/java/jp/co/soramitsu/irohaandroid/util/Const.kt create mode 100644 app/src/main/java/jp/co/soramitsu/irohaandroid/util/DateFormatter.kt create mode 100644 app/src/main/java/jp/co/soramitsu/irohaandroid/util/Event.kt create mode 100644 app/src/main/java/jp/co/soramitsu/irohaandroid/util/Ext.kt create mode 100644 app/src/main/java/jp/co/soramitsu/irohaandroid/util/IrohaException.kt create mode 100644 app/src/main/java/jp/co/soramitsu/irohaandroid/util/PrefsUtil.kt create mode 100644 app/src/main/java/jp/co/soramitsu/irohaandroid/util/QrCodeDecoder.kt create mode 100644 app/src/main/java/jp/co/soramitsu/irohaandroid/util/QrCodeGenerator.kt create mode 100644 app/src/main/java/jp/co/soramitsu/irohaandroid/util/RegistrationState.kt create mode 100644 app/src/main/java/jp/co/soramitsu/irohaandroid/util/ResourceManager.kt rename {iroha-android-sample => app}/src/main/res/drawable-hdpi/background.png (100%) rename {iroha-android-sample => app}/src/main/res/drawable-hdpi/ic_logo.png (100%) rename {iroha-android-sample => app}/src/main/res/drawable-hdpi/iroha_logo_big.png (100%) rename {iroha-android-sample => app}/src/main/res/drawable-hdpi/qr.png (100%) rename {iroha-android-sample => app}/src/main/res/drawable-ldpi/background.png (100%) rename {iroha-android-sample => app}/src/main/res/drawable-ldpi/ic_logo.png (100%) rename {iroha-android-sample => app}/src/main/res/drawable-ldpi/iroha_logo_big.png (100%) rename {iroha-android-sample => app}/src/main/res/drawable-ldpi/qr.png (100%) rename {iroha-android-sample => app}/src/main/res/drawable-mdpi/background.png (100%) rename {iroha-android-sample => app}/src/main/res/drawable-mdpi/ic_logo.png (100%) rename {iroha-android-sample => app}/src/main/res/drawable-mdpi/iroha_logo_big.png (100%) rename {iroha-android-sample => app}/src/main/res/drawable-mdpi/qr.png (100%) rename {iroha-android-sample => app}/src/main/res/drawable-v24/ic_launcher_foreground.xml (100%) rename {iroha-android-sample => app}/src/main/res/drawable-xhdpi/background.png (100%) rename {iroha-android-sample => app}/src/main/res/drawable-xhdpi/ic_logo.png (100%) rename {iroha-android-sample => app}/src/main/res/drawable-xhdpi/iroha_logo_big.png (100%) rename {iroha-android-sample => app}/src/main/res/drawable-xhdpi/qr.png (100%) rename {iroha-android-sample => app}/src/main/res/drawable-xxhdpi/background.png (100%) rename {iroha-android-sample => app}/src/main/res/drawable-xxhdpi/ic_logo.png (100%) rename {iroha-android-sample => app}/src/main/res/drawable-xxhdpi/iroha_logo_big.png (100%) rename {iroha-android-sample => app}/src/main/res/drawable-xxhdpi/qr.png (100%) rename {iroha-android-sample => app}/src/main/res/drawable-xxxhdpi/background.png (100%) rename {iroha-android-sample => app}/src/main/res/drawable-xxxhdpi/ic_logo.png (100%) rename {iroha-android-sample => app}/src/main/res/drawable-xxxhdpi/iroha_logo_big.png (100%) rename {iroha-android-sample => app}/src/main/res/drawable-xxxhdpi/qr.png (100%) create mode 100644 app/src/main/res/drawable/bg_splash.xml rename {iroha-android-sample => app}/src/main/res/drawable/ic_launcher_background.xml (100%) rename {iroha-android-sample => app}/src/main/res/drawable/ic_logout.xml (100%) create mode 100644 app/src/main/res/layout/activity_main.xml create mode 100644 app/src/main/res/layout/activity_signup.xml rename iroha-android-sample/src/main/res/layout/dialog_account_details.xml => app/src/main/res/layout/dialog_enter_bio.xml (86%) create mode 100644 app/src/main/res/layout/fragment_history.xml create mode 100644 app/src/main/res/layout/fragment_receive.xml create mode 100644 app/src/main/res/layout/fragment_send.xml create mode 100644 app/src/main/res/layout/item_transaction.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/navigation/main.xml create mode 100644 app/src/main/res/values/colors.xml create mode 100644 app/src/main/res/values/dimens.xml create mode 100644 app/src/main/res/values/strings.xml create mode 100644 app/src/main/res/values/styles.xml create mode 100644 app/src/test/java/jp/co/soramitsu/irohaandroid/ExampleUnitTest.kt delete mode 100644 iroha-android-bindings/bintray.gradle delete mode 100644 iroha-android-bindings/build.gradle delete mode 100644 iroha-android-bindings/install.gradle delete mode 100644 iroha-android-bindings/src/main/AndroidManifest.xml delete mode 100644 iroha-android-bindings/src/main/java/jp/co/soramitsu/iroha/android/Blob.java delete mode 100644 iroha-android-bindings/src/main/java/jp/co/soramitsu/iroha/android/ByteVector.java delete mode 100644 iroha-android-bindings/src/main/java/jp/co/soramitsu/iroha/android/Hash.java delete mode 100644 iroha-android-bindings/src/main/java/jp/co/soramitsu/iroha/android/HashVector.java delete mode 100644 iroha-android-bindings/src/main/java/jp/co/soramitsu/iroha/android/Keypair.java delete mode 100644 iroha-android-bindings/src/main/java/jp/co/soramitsu/iroha/android/ModelCrypto.java delete mode 100644 iroha-android-bindings/src/main/java/jp/co/soramitsu/iroha/android/ModelProtoQuery.java delete mode 100644 iroha-android-bindings/src/main/java/jp/co/soramitsu/iroha/android/ModelProtoTransaction.java delete mode 100644 iroha-android-bindings/src/main/java/jp/co/soramitsu/iroha/android/ModelQueryBuilder.java delete mode 100644 iroha-android-bindings/src/main/java/jp/co/soramitsu/iroha/android/ModelTransactionBuilder.java delete mode 100644 iroha-android-bindings/src/main/java/jp/co/soramitsu/iroha/android/PrivateKey.java delete mode 100644 iroha-android-bindings/src/main/java/jp/co/soramitsu/iroha/android/PublicKey.java delete mode 100644 iroha-android-bindings/src/main/java/jp/co/soramitsu/iroha/android/Query.java delete mode 100644 iroha-android-bindings/src/main/java/jp/co/soramitsu/iroha/android/SWIGTYPE_p_boost__any_rangeT_shared_model__interface__Signature_const_R_boost__forward_traversal_tag_t.java delete mode 100644 iroha-android-bindings/src/main/java/jp/co/soramitsu/iroha/android/SWIGTYPE_p_shared_model__interface__SignatureSetType.java delete mode 100644 iroha-android-bindings/src/main/java/jp/co/soramitsu/iroha/android/SWIGTYPE_p_shared_model__proto__Query__QueryVariantType.java delete mode 100644 iroha-android-bindings/src/main/java/jp/co/soramitsu/iroha/android/SWIGTYPE_p_shared_model__proto__Transaction__CommandsType.java delete mode 100644 iroha-android-bindings/src/main/java/jp/co/soramitsu/iroha/android/SWIGTYPE_p_shared_model__proto__Transaction__QuorumType.java delete mode 100644 iroha-android-bindings/src/main/java/jp/co/soramitsu/iroha/android/Signed.java delete mode 100644 iroha-android-bindings/src/main/java/jp/co/soramitsu/iroha/android/StringVector.java delete mode 100644 iroha-android-bindings/src/main/java/jp/co/soramitsu/iroha/android/Transaction.java delete mode 100644 iroha-android-bindings/src/main/java/jp/co/soramitsu/iroha/android/UnsignedQuery.java delete mode 100644 iroha-android-bindings/src/main/java/jp/co/soramitsu/iroha/android/UnsignedTx.java delete mode 100644 iroha-android-bindings/src/main/java/jp/co/soramitsu/iroha/android/iroha.java delete mode 100644 iroha-android-bindings/src/main/java/jp/co/soramitsu/iroha/android/irohaJNI.java delete mode 100644 iroha-android-bindings/src/main/jniLibs/arm64-v8a/libirohajava.so delete mode 100644 iroha-android-bindings/src/main/jniLibs/armeabi-v7a/libirohajava.so delete mode 100644 iroha-android-bindings/src/main/jniLibs/armeabi/libirohajava.so delete mode 100644 iroha-android-bindings/src/main/jniLibs/x86/libirohajava.so delete mode 100644 iroha-android-bindings/src/main/jniLibs/x86_64/libirohajava.so delete mode 100644 iroha-android-bindings/src/main/res/values/strings.xml delete mode 100644 iroha-android-sample/.gitignore delete mode 100644 iroha-android-sample/build.gradle delete mode 100644 iroha-android-sample/proguard-rules.pro delete mode 100644 iroha-android-sample/src/androidTest/java/jp/co/soramitsu/iroha/android/sample/Const.java delete mode 100644 iroha-android-sample/src/androidTest/java/jp/co/soramitsu/iroha/android/sample/MatcherUtils.java delete mode 100644 iroha-android-sample/src/androidTest/java/jp/co/soramitsu/iroha/android/sample/NetworkRequestIdlingResources.java delete mode 100644 iroha-android-sample/src/androidTest/java/jp/co/soramitsu/iroha/android/sample/main/MainIntegrationTest.java delete mode 100644 iroha-android-sample/src/androidTest/java/jp/co/soramitsu/iroha/android/sample/main/MainTest.java delete mode 100644 iroha-android-sample/src/androidTest/java/jp/co/soramitsu/iroha/android/sample/registration/RegistrationIntegrationTest.java delete mode 100644 iroha-android-sample/src/androidTest/java/jp/co/soramitsu/iroha/android/sample/registration/RegistrationTest.java delete mode 100644 iroha-android-sample/src/main/AndroidManifest.xml delete mode 100644 iroha-android-sample/src/main/java/jp/co/soramitsu/iroha/android/sample/Constants.java delete mode 100644 iroha-android-sample/src/main/java/jp/co/soramitsu/iroha/android/sample/PreferencesUtil.java delete mode 100644 iroha-android-sample/src/main/java/jp/co/soramitsu/iroha/android/sample/SampleApplication.java delete mode 100644 iroha-android-sample/src/main/java/jp/co/soramitsu/iroha/android/sample/data/Account.java delete mode 100644 iroha-android-sample/src/main/java/jp/co/soramitsu/iroha/android/sample/injection/ApplicationComponent.java delete mode 100755 iroha-android-sample/src/main/java/jp/co/soramitsu/iroha/android/sample/injection/ApplicationModule.java delete mode 100644 iroha-android-sample/src/main/java/jp/co/soramitsu/iroha/android/sample/interactor/AddAssetInteractor.java delete mode 100644 iroha-android-sample/src/main/java/jp/co/soramitsu/iroha/android/sample/interactor/CompletableInteractor.java delete mode 100644 iroha-android-sample/src/main/java/jp/co/soramitsu/iroha/android/sample/interactor/CreateAccountInteractor.java delete mode 100644 iroha-android-sample/src/main/java/jp/co/soramitsu/iroha/android/sample/interactor/GenerateQRInteractor.java delete mode 100644 iroha-android-sample/src/main/java/jp/co/soramitsu/iroha/android/sample/interactor/GetAccountBalanceInteractor.java delete mode 100644 iroha-android-sample/src/main/java/jp/co/soramitsu/iroha/android/sample/interactor/GetAccountDetailsInteractor.java delete mode 100644 iroha-android-sample/src/main/java/jp/co/soramitsu/iroha/android/sample/interactor/GetAccountInteractor.java delete mode 100644 iroha-android-sample/src/main/java/jp/co/soramitsu/iroha/android/sample/interactor/GetAccountTransactionsInteractor.java delete mode 100755 iroha-android-sample/src/main/java/jp/co/soramitsu/iroha/android/sample/interactor/Interactor.java delete mode 100644 iroha-android-sample/src/main/java/jp/co/soramitsu/iroha/android/sample/interactor/SendAssetInteractor.java delete mode 100644 iroha-android-sample/src/main/java/jp/co/soramitsu/iroha/android/sample/interactor/SetAccountDetailsInteractor.java delete mode 100644 iroha-android-sample/src/main/java/jp/co/soramitsu/iroha/android/sample/interactor/SingleInteractor.java delete mode 100644 iroha-android-sample/src/main/java/jp/co/soramitsu/iroha/android/sample/main/AccountDetail.java delete mode 100644 iroha-android-sample/src/main/java/jp/co/soramitsu/iroha/android/sample/main/MainActivity.java delete mode 100644 iroha-android-sample/src/main/java/jp/co/soramitsu/iroha/android/sample/main/MainPresenter.java delete mode 100644 iroha-android-sample/src/main/java/jp/co/soramitsu/iroha/android/sample/main/MainView.java delete mode 100644 iroha-android-sample/src/main/java/jp/co/soramitsu/iroha/android/sample/main/history/HistoryFragment.java delete mode 100644 iroha-android-sample/src/main/java/jp/co/soramitsu/iroha/android/sample/main/history/HistoryPresenter.java delete mode 100644 iroha-android-sample/src/main/java/jp/co/soramitsu/iroha/android/sample/main/history/HistoryView.java delete mode 100644 iroha-android-sample/src/main/java/jp/co/soramitsu/iroha/android/sample/main/history/Transaction.java delete mode 100644 iroha-android-sample/src/main/java/jp/co/soramitsu/iroha/android/sample/main/history/TransactionDiffChecker.java delete mode 100644 iroha-android-sample/src/main/java/jp/co/soramitsu/iroha/android/sample/main/history/TransactionVM.java delete mode 100644 iroha-android-sample/src/main/java/jp/co/soramitsu/iroha/android/sample/main/history/TransactionsAdapter.java delete mode 100644 iroha-android-sample/src/main/java/jp/co/soramitsu/iroha/android/sample/main/history/TransactionsViewModel.java delete mode 100644 iroha-android-sample/src/main/java/jp/co/soramitsu/iroha/android/sample/main/receive/ReceiveFragment.java delete mode 100644 iroha-android-sample/src/main/java/jp/co/soramitsu/iroha/android/sample/main/receive/ReceivePresenter.java delete mode 100644 iroha-android-sample/src/main/java/jp/co/soramitsu/iroha/android/sample/main/receive/ReceiveView.java delete mode 100644 iroha-android-sample/src/main/java/jp/co/soramitsu/iroha/android/sample/main/send/SendFragment.java delete mode 100644 iroha-android-sample/src/main/java/jp/co/soramitsu/iroha/android/sample/main/send/SendPresenter.java delete mode 100644 iroha-android-sample/src/main/java/jp/co/soramitsu/iroha/android/sample/main/send/SendView.java delete mode 100644 iroha-android-sample/src/main/java/jp/co/soramitsu/iroha/android/sample/qrscanner/QRScannerActivity.java delete mode 100644 iroha-android-sample/src/main/java/jp/co/soramitsu/iroha/android/sample/registration/RegistrationActivity.java delete mode 100644 iroha-android-sample/src/main/java/jp/co/soramitsu/iroha/android/sample/registration/RegistrationPresenter.java delete mode 100644 iroha-android-sample/src/main/java/jp/co/soramitsu/iroha/android/sample/registration/RegistrationView.java delete mode 100644 iroha-android-sample/src/main/proto/empty.proto delete mode 100644 iroha-android-sample/src/main/proto/iroha/consensus/yac/proto/yac.proto delete mode 100644 iroha-android-sample/src/main/proto/iroha/network/proto/loader.proto delete mode 100644 iroha-android-sample/src/main/proto/iroha/network/transport/mst.proto delete mode 100644 iroha-android-sample/src/main/proto/iroha/ordering/proto/ordering.proto delete mode 100755 iroha-android-sample/src/main/proto/iroha/protocol/block.proto delete mode 100755 iroha-android-sample/src/main/proto/iroha/protocol/commands.proto delete mode 100755 iroha-android-sample/src/main/proto/iroha/protocol/endpoint.proto delete mode 100644 iroha-android-sample/src/main/proto/iroha/protocol/primitive.proto delete mode 100644 iroha-android-sample/src/main/proto/iroha/protocol/proposal.proto delete mode 100644 iroha-android-sample/src/main/proto/iroha/protocol/queries.proto delete mode 100644 iroha-android-sample/src/main/proto/iroha/protocol/responses.proto delete mode 100644 iroha-android-sample/src/main/res/layout/activity_main.xml delete mode 100644 iroha-android-sample/src/main/res/layout/activity_registration.xml delete mode 100644 iroha-android-sample/src/main/res/layout/fragment_history.xml delete mode 100644 iroha-android-sample/src/main/res/layout/fragment_receive.xml delete mode 100644 iroha-android-sample/src/main/res/layout/fragment_send.xml delete mode 100644 iroha-android-sample/src/main/res/layout/transaction_header_item.xml delete mode 100644 iroha-android-sample/src/main/res/layout/transaction_item.xml delete mode 100644 iroha-android-sample/src/main/res/values/colors.xml delete mode 100644 iroha-android-sample/src/main/res/values/strings.xml delete mode 100644 iroha-android-sample/src/main/res/values/styles.xml delete mode 100644 licenses/android-googletv-license delete mode 100644 licenses/android-sdk-license delete mode 100644 licenses/android-sdk-preview-license delete mode 100644 licenses/google-gdk-license delete mode 100644 licenses/intel-android-extra-license delete mode 100644 licenses/mips-android-sysimage-license diff --git a/iroha-android-bindings/.gitignore b/app/.gitignore similarity index 100% rename from iroha-android-bindings/.gitignore rename to app/.gitignore diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..7b9e33f --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,66 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-kapt' +apply plugin: 'kotlin-android-extensions' + +android { + compileSdkVersion 28 + defaultConfig { + applicationId "jp.co.soramitsu.irohaandroid" + minSdkVersion 21 + targetSdkVersion 28 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + packagingOptions { + exclude 'META-INF/DEPENDENCIES' + exclude 'META-INF/LICENSE' + exclude 'META-INF/LICENSE.txt' + exclude 'META-INF/license.txt' + exclude 'META-INF/NOTICE' + exclude 'META-INF/NOTICE.txt' + exclude 'META-INF/notice.txt' + exclude 'META-INF/ASL2.0' + exclude 'META-INF/INDEX.LIST' + exclude 'META-INF/io.netty.versions.properties' + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation kotlinDeps.values() + implementation androidDeps.values() + implementation rxDeps.values() + implementation rxAndroidDeps.values() + implementation rxBindingDeps.values() + implementation irohaDeps.values() + implementation birtDeps.values() + implementation zXingDeps.values() + implementation rxPermissionsDeps.values() + + implementation daggerDeps.values() + implementation 'androidx.legacy:legacy-support-v4:1.0.0' + kapt daggerKapt.values() + + implementation lifecycleDeps.values() + kapt lifecycleKapt.values() + + implementation javaXInject.values() + + testImplementation 'junit:junit:4.12' + androidTestImplementation 'androidx.test.ext:junit:1.1.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' + implementation 'androidx.navigation:navigation-fragment-ktx:2.1.0' + implementation 'androidx.navigation:navigation-ui-ktx:2.1.0' +} diff --git a/iroha-android-bindings/proguard-rules.pro b/app/proguard-rules.pro similarity index 100% rename from iroha-android-bindings/proguard-rules.pro rename to app/proguard-rules.pro diff --git a/app/src/androidTest/java/jp/co/soramitsu/irohaandroid/ExampleInstrumentedTest.kt b/app/src/androidTest/java/jp/co/soramitsu/irohaandroid/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..5222530 --- /dev/null +++ b/app/src/androidTest/java/jp/co/soramitsu/irohaandroid/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package jp.co.soramitsu.irohaandroid + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("jp.co.soramitsu.irohaandroid", appContext.packageName) + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..660e8b8 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/jp/co/soramitsu/irohaandroid/IrohaSampleApp.kt b/app/src/main/java/jp/co/soramitsu/irohaandroid/IrohaSampleApp.kt new file mode 100644 index 0000000..090710b --- /dev/null +++ b/app/src/main/java/jp/co/soramitsu/irohaandroid/IrohaSampleApp.kt @@ -0,0 +1,15 @@ +package jp.co.soramitsu.irohaandroid + +import android.app.Application +import jp.co.soramitsu.irohaandroid.di.AppComponent +import jp.co.soramitsu.irohaandroid.di.DaggerAppComponent + +class IrohaSampleApp : Application() { + + lateinit var appComponent: AppComponent + + override fun onCreate() { + super.onCreate() + appComponent = DaggerAppComponent.builder().context(this).build() + } +} \ No newline at end of file diff --git a/app/src/main/java/jp/co/soramitsu/irohaandroid/data/transfer/TransferRepository.kt b/app/src/main/java/jp/co/soramitsu/irohaandroid/data/transfer/TransferRepository.kt new file mode 100644 index 0000000..9ce8644 --- /dev/null +++ b/app/src/main/java/jp/co/soramitsu/irohaandroid/data/transfer/TransferRepository.kt @@ -0,0 +1,17 @@ +package jp.co.soramitsu.irohaandroid.data.transfer + +import io.reactivex.Completable +import io.reactivex.Single +import jp.co.soramitsu.irohaandroid.presentation.model.TransactionModel +import jp.co.soramitsu.irohaandroid.presentation.model.Transfer + +interface TransferRepository { + + fun getReceiveQrString(amount: String): Single + + fun transfer(dstUsername: String, amount: String): Completable + + fun getTransactionHistory(): Single> + + fun processQr(contents: String): Single +} \ No newline at end of file diff --git a/app/src/main/java/jp/co/soramitsu/irohaandroid/data/transfer/TransferRepositoryImpl.kt b/app/src/main/java/jp/co/soramitsu/irohaandroid/data/transfer/TransferRepositoryImpl.kt new file mode 100644 index 0000000..d11c6f0 --- /dev/null +++ b/app/src/main/java/jp/co/soramitsu/irohaandroid/data/transfer/TransferRepositoryImpl.kt @@ -0,0 +1,102 @@ +package jp.co.soramitsu.irohaandroid.data.transfer + +import com.google.gson.Gson +import io.reactivex.Completable +import io.reactivex.Single +import iroha.protocol.Endpoint +import jp.co.soramitsu.iroha.java.IrohaAPI +import jp.co.soramitsu.iroha.java.Query +import jp.co.soramitsu.iroha.java.Transaction +import jp.co.soramitsu.iroha.java.TransactionStatusObserver +import jp.co.soramitsu.irohaandroid.R +import jp.co.soramitsu.irohaandroid.data.user.datasource.UserDatasource +import jp.co.soramitsu.irohaandroid.presentation.model.TransactionModel +import jp.co.soramitsu.irohaandroid.presentation.model.Transfer +import jp.co.soramitsu.irohaandroid.presentation.model.mapper.mapIrohaTransactionsToTransactions +import jp.co.soramitsu.irohaandroid.util.* + +class TransferRepositoryImpl( + private val irohaAPI: IrohaAPI, + private val gson: Gson, + private val userDatasource: UserDatasource, + private val dateFormatter: DateFormatter, + private val resourceManager: ResourceManager +) : TransferRepository { + + companion object { + private const val PAGE_SIZE = 100 + } + + override fun transfer(dstUsername: String, amount: String): Completable { + return Completable.create { emitter -> + val srcUsername = userDatasource.getUsername() + val keyPair = userDatasource.getKeypair() + + val tx1 = Transaction.builder(srcUsername.accountId()) + .transferAsset( + srcUsername.accountId(), + dstUsername.accountId(), + Const.ASSET_ID, + "", + amount + ) + .sign(keyPair) + .build() + + val observer = TransactionStatusObserver.builder() + .onTransactionFailed { + val exception = when (it.txStatus) { + + Endpoint.TxStatus.STATELESS_VALIDATION_FAILED -> IrohaException.businessError( + R.string.username_not_valid, resourceManager + ) + + Endpoint.TxStatus.STATEFUL_VALIDATION_FAILED -> IrohaException.businessError( + R.string.username_doesnt_exists, resourceManager + ) + + else -> IrohaException("") + } + + emitter.onError(exception) + } + .onError { emitter.onError(IrohaException.unexpectedError(it)) } + .onTransactionCommitted { emitter.onComplete() } + .build() + + irohaAPI.transaction(tx1).blockingSubscribe(observer) + } + } + + override fun getReceiveQrString(amount: String): Single { + val username = userDatasource.getUsername() + return Single.just(gson.toJson(Transfer(amount, username.accountId()))) + } + + override fun getTransactionHistory(): Single> { + return Single.create { emitter -> + val username = userDatasource.getUsername() + val keyPair = userDatasource.getKeypair() + + val query = Query.builder(username.accountId(), Const.QUERY_COUNTER) + .getAccountAssetTransactions(username.accountId(), Const.ASSET_ID, PAGE_SIZE, null) + .buildSigned(keyPair) + + val result = irohaAPI.query(query) + + val transactionsDto = result.transactionsPageResponse.transactionsList + + emitter.onSuccess( + mapIrohaTransactionsToTransactions( + transactionsDto, + dateFormatter, + username + ) + ) + } + } + + override fun processQr(contents: String): Single { + return Single.just(gson.fromJson(contents, Transfer::class.java)) + } +} \ No newline at end of file diff --git a/app/src/main/java/jp/co/soramitsu/irohaandroid/data/user/UserRepository.kt b/app/src/main/java/jp/co/soramitsu/irohaandroid/data/user/UserRepository.kt new file mode 100644 index 0000000..b99bd06 --- /dev/null +++ b/app/src/main/java/jp/co/soramitsu/irohaandroid/data/user/UserRepository.kt @@ -0,0 +1,27 @@ +package jp.co.soramitsu.irohaandroid.data.user + +import io.reactivex.Completable +import io.reactivex.Single +import jp.co.soramitsu.irohaandroid.util.RegistrationState +import java.security.KeyPair + +interface UserRepository { + + fun createAccount(username: String): Completable + + fun getRegistrationState(): Single + + fun saveUsername(username: String): Completable + + fun getUsername(): Single + + fun getUserDetails(): Single + + fun getUserBalance(): Single + + fun getKeyPair(): Single + + fun removeUserData(): Completable + + fun setAccountDetails(details: String): Completable +} \ No newline at end of file diff --git a/app/src/main/java/jp/co/soramitsu/irohaandroid/data/user/UserRepositoryImpl.kt b/app/src/main/java/jp/co/soramitsu/irohaandroid/data/user/UserRepositoryImpl.kt new file mode 100644 index 0000000..f641551 --- /dev/null +++ b/app/src/main/java/jp/co/soramitsu/irohaandroid/data/user/UserRepositoryImpl.kt @@ -0,0 +1,153 @@ +package jp.co.soramitsu.irohaandroid.data.user + +import io.reactivex.Completable +import io.reactivex.Single +import iroha.protocol.Endpoint +import jp.co.soramitsu.crypto.ed25519.Ed25519Sha3 +import jp.co.soramitsu.iroha.java.IrohaAPI +import jp.co.soramitsu.iroha.java.Query +import jp.co.soramitsu.iroha.java.Transaction +import jp.co.soramitsu.iroha.java.TransactionStatusObserver +import jp.co.soramitsu.irohaandroid.R +import jp.co.soramitsu.irohaandroid.data.user.datasource.UserDatasource +import jp.co.soramitsu.irohaandroid.util.* +import org.json.JSONObject +import org.spongycastle.util.encoders.Hex +import java.security.KeyPair +import javax.inject.Inject + +class UserRepositoryImpl @Inject constructor( + private val irohaAPI: IrohaAPI, + private val datasource: UserDatasource, + private val ed25519Sha3: Ed25519Sha3, + private val resourceManager: ResourceManager +): UserRepository { + + companion object { + private const val AMOUNT = "1337" + private const val INITIAL = "initial" + private const val DETAILS_KEY = "details" + } + + override fun createAccount(username: String): Completable { + return Completable.create { emitter -> + val keyPair = ed25519Sha3.generateKeypair() + + val tx1 = Transaction.builder(Const.CREATOR) + .createAccount(username, Const.DOMAIN_ID, keyPair.public) + .addAssetQuantity(Const.ASSET_ID, + AMOUNT + ) + .transferAsset(Const.CREATOR, username.accountId(), Const.ASSET_ID, + INITIAL, + AMOUNT + ) + .sign(Ed25519Sha3.keyPairFromBytes(Hex.decode(Const.PRIV_KEY), Hex.decode(Const.PUB_KEY))) + .build() + + val observer = TransactionStatusObserver.builder() + .onTransactionFailed { + val exception = when (it.txStatus) { + + Endpoint.TxStatus.STATELESS_VALIDATION_FAILED -> IrohaException.businessError( + R.string.username_not_valid, resourceManager) + + Endpoint.TxStatus.STATEFUL_VALIDATION_FAILED -> IrohaException.businessError( + R.string.username_already_exists_error, resourceManager + ) + + else -> IrohaException("") + } + + emitter.onError(exception) + } + .onTransactionCommitted { + datasource.saveUsername(username) + datasource.saveKeypair(keyPair) + datasource.saveRegistrationState(RegistrationState.REGISTERED) + emitter.onComplete() + } + .build() + + irohaAPI.transaction(tx1).blockingSubscribe(observer) + } + } + + override fun getUserDetails(): Single { + return Single.create { + val keyPair = datasource.getKeypair()!! + val username = datasource.getUsername() + + val query = Query.builder(username.accountId(), Const.QUERY_COUNTER) + .getAccountDetail(username.accountId(), null, null) + .buildSigned(keyPair) + + val response = irohaAPI.query(query) + + val detailsJson = JSONObject(response.accountDetailResponse.detail) + + if (detailsJson.has(username.accountId()) && detailsJson.getJSONObject(username.accountId()).has(DETAILS_KEY)) { + it.onSuccess(detailsJson.getJSONObject(username.accountId()).getString(DETAILS_KEY)) + } else { + it.onSuccess("") + } + } + } + + override fun getUserBalance(): Single { + return Single.create { + val keyPair = datasource.getKeypair() + val username = datasource.getUsername() + + val query = Query.builder(username.accountId(), Const.QUERY_COUNTER) + .getAccountAssets(username.accountId()) + .buildSigned(keyPair) + + val response = irohaAPI.query(query) + + val balance = response.accountAssetsResponse.getAccountAssets(0).balance + it.onSuccess(balance) + } + } + + override fun getRegistrationState(): Single { + return Single.just(datasource.getRegistrationState()) + } + + override fun saveUsername(username: String): Completable { + return Completable.fromAction { datasource.saveUsername(username) } + } + + override fun getUsername(): Single { + return Single.just(datasource.getUsername()) + } + + override fun removeUserData(): Completable { + return Completable.fromAction { datasource.removeUserData() } + } + + override fun getKeyPair(): Single { + return Single.just(datasource.getKeypair()) + } + + override fun setAccountDetails(details: String): Completable { + return Completable.create { emitter -> + val username = datasource.getUsername() + val keyPair = datasource.getKeypair() + + val tx1 = Transaction.builder(datasource.getUsername().accountId()) + .setAccountDetail(username.accountId(), DETAILS_KEY, details) + .sign(keyPair) + .build() + + val observer = TransactionStatusObserver.builder() + .onTransactionFailed { + emitter.onError(IrohaException.businessError(R.string.general_error, resourceManager)) + } + .onTransactionCommitted { emitter.onComplete() } + .build() + + irohaAPI.transaction(tx1).blockingSubscribe(observer) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/jp/co/soramitsu/irohaandroid/data/user/datasource/PrefsUserDatasource.kt b/app/src/main/java/jp/co/soramitsu/irohaandroid/data/user/datasource/PrefsUserDatasource.kt new file mode 100644 index 0000000..6ce621b --- /dev/null +++ b/app/src/main/java/jp/co/soramitsu/irohaandroid/data/user/datasource/PrefsUserDatasource.kt @@ -0,0 +1,61 @@ +package jp.co.soramitsu.irohaandroid.data.user.datasource + +import jp.co.soramitsu.crypto.ed25519.Ed25519Sha3 +import jp.co.soramitsu.irohaandroid.util.PrefsUtil +import jp.co.soramitsu.irohaandroid.util.RegistrationState +import org.spongycastle.util.encoders.Hex +import java.security.KeyPair +import javax.inject.Inject + +class PrefsUserDatasource @Inject constructor(private val prefsUtil: PrefsUtil): + UserDatasource { + + companion object { + private const val KEY_REGISTRATION_STATE = "key_registration_state" + private const val KEY_USERNAME = "key_username" + private const val KEY_PRIVATE = "key_private" + private const val KEY_PUBLIC = "key_public" + } + + override fun saveRegistrationState(registrationState: RegistrationState) { + prefsUtil.saveString(KEY_REGISTRATION_STATE, registrationState.toString()) + } + + override fun getRegistrationState(): RegistrationState { + val registrationStateString = prefsUtil.getString(KEY_REGISTRATION_STATE, "") + + return if (registrationStateString.isEmpty()) { + RegistrationState.INITIAL + } else { + RegistrationState.valueOf(registrationStateString) + } + } + + override fun saveUsername(username: String) { + prefsUtil.saveString(KEY_USERNAME, username) + } + + override fun getUsername(): String { + return prefsUtil.getString(KEY_USERNAME, "") + } + + override fun saveKeypair(keyPair: KeyPair) { + prefsUtil.saveString(KEY_PUBLIC, Hex.toHexString(keyPair.public.encoded)) + prefsUtil.saveString(KEY_PRIVATE, Hex.toHexString(keyPair.private.encoded)) + } + + override fun getKeypair(): KeyPair? { + val publicString = prefsUtil.getString(KEY_PUBLIC, "") + val privateString = prefsUtil.getString(KEY_PRIVATE, "") + + return if (publicString.isEmpty() || privateString.isEmpty()) { + null + } else { + Ed25519Sha3.keyPairFromBytes(Hex.decode(privateString), Hex.decode(publicString)) + } + } + + override fun removeUserData() { + prefsUtil.clearData() + } +} \ No newline at end of file diff --git a/app/src/main/java/jp/co/soramitsu/irohaandroid/data/user/datasource/UserDatasource.kt b/app/src/main/java/jp/co/soramitsu/irohaandroid/data/user/datasource/UserDatasource.kt new file mode 100644 index 0000000..258a286 --- /dev/null +++ b/app/src/main/java/jp/co/soramitsu/irohaandroid/data/user/datasource/UserDatasource.kt @@ -0,0 +1,22 @@ +package jp.co.soramitsu.irohaandroid.data.user.datasource + +import jp.co.soramitsu.irohaandroid.util.RegistrationState +import java.security.KeyPair + +interface UserDatasource { + + fun saveRegistrationState(registrationState: RegistrationState) + + fun getRegistrationState(): RegistrationState + + fun saveUsername(username: String) + + fun getUsername(): String + + fun saveKeypair(keyPair: KeyPair) + + fun getKeypair(): KeyPair? + + fun removeUserData() + +} \ No newline at end of file diff --git a/app/src/main/java/jp/co/soramitsu/irohaandroid/di/AppComponent.kt b/app/src/main/java/jp/co/soramitsu/irohaandroid/di/AppComponent.kt new file mode 100644 index 0000000..3067314 --- /dev/null +++ b/app/src/main/java/jp/co/soramitsu/irohaandroid/di/AppComponent.kt @@ -0,0 +1,32 @@ +package jp.co.soramitsu.irohaandroid.di + +import android.content.Context +import dagger.BindsInstance +import dagger.Component +import jp.co.soramitsu.irohaandroid.presentation.main.MainActivity +import jp.co.soramitsu.irohaandroid.presentation.main.di.MainComponent +import jp.co.soramitsu.irohaandroid.presentation.signup.di.SignupComponent +import jp.co.soramitsu.irohaandroid.presentation.splash.SplashActivity +import jp.co.soramitsu.irohaandroid.presentation.splash.di.SplashComponent +import javax.inject.Singleton + +@Singleton +@Component(modules = [AppModule::class]) +interface AppComponent { + + fun mainComponentBuilder(): MainComponent.Builder + + fun signupComponentBuilder(): SignupComponent.Builder + + fun splashComponentBuilder(): SplashComponent.Builder + + @Component.Builder + interface Builder { + + @BindsInstance + fun context(context: Context): Builder + + fun build(): AppComponent + + } +} \ No newline at end of file diff --git a/app/src/main/java/jp/co/soramitsu/irohaandroid/di/AppModule.kt b/app/src/main/java/jp/co/soramitsu/irohaandroid/di/AppModule.kt new file mode 100644 index 0000000..948299c --- /dev/null +++ b/app/src/main/java/jp/co/soramitsu/irohaandroid/di/AppModule.kt @@ -0,0 +1,108 @@ +package jp.co.soramitsu.irohaandroid.di + +import android.content.Context +import android.graphics.Color +import androidx.lifecycle.ViewModelProvider +import com.google.gson.Gson +import dagger.Module +import dagger.Provides +import jp.co.soramitsu.crypto.ed25519.Ed25519Sha3 +import jp.co.soramitsu.iroha.java.IrohaAPI +import jp.co.soramitsu.irohaandroid.R +import jp.co.soramitsu.irohaandroid.data.transfer.TransferRepository +import jp.co.soramitsu.irohaandroid.data.transfer.TransferRepositoryImpl +import jp.co.soramitsu.irohaandroid.data.user.UserRepository +import jp.co.soramitsu.irohaandroid.data.user.UserRepositoryImpl +import jp.co.soramitsu.irohaandroid.data.user.datasource.PrefsUserDatasource +import jp.co.soramitsu.irohaandroid.data.user.datasource.UserDatasource +import jp.co.soramitsu.irohaandroid.presentation.base.IrohaViewModelFactory +import jp.co.soramitsu.irohaandroid.util.PrefsUtil +import jp.co.soramitsu.irohaandroid.util.QrCodeGenerator +import jp.co.soramitsu.irohaandroid.util.DateFormatter +import jp.co.soramitsu.irohaandroid.util.ResourceManager +import jp.co.soramitsu.irohaandroid.util.QrCodeDecoder +import javax.inject.Singleton + +@Module +class AppModule { + + @Provides + fun provideViewModelFactory(factory: IrohaViewModelFactory): ViewModelProvider.Factory = factory + + + @Provides + @Singleton + fun provideIrohaApi(): IrohaAPI { + return IrohaAPI("192.168.1.6", 50051) + } + + @Provides + @Singleton + fun provideEd25519Sha3(): Ed25519Sha3 { + return Ed25519Sha3() + } + + @Provides + @Singleton + fun provideGson(): Gson { + return Gson() + } + + @Provides + @Singleton + fun providePrefsUtil(context: Context): PrefsUtil { + return PrefsUtil(context) + } + + @Provides + @Singleton + fun provideUserDatasource(prefsUtil: PrefsUtil): UserDatasource { + return PrefsUserDatasource(prefsUtil) + } + + @Provides + @Singleton + fun provideQrCodeGenerator(context: Context): QrCodeGenerator { + return QrCodeGenerator(Color.BLACK, context.resources.getColor(R.color.zxing_transparent)) + } + + @Provides + @Singleton + fun provideDateFormatter() = DateFormatter() + + @Provides + @Singleton + fun provideUserRepository( + irohaAPI: IrohaAPI, + userDatasource: UserDatasource, + ed25519Sha3: Ed25519Sha3, + resourceManager: ResourceManager + ): UserRepository { + return UserRepositoryImpl( + irohaAPI, + userDatasource, + ed25519Sha3, + resourceManager + ) + } + + @Singleton + @Provides + fun provideQrCodeDecoder(context: Context) = QrCodeDecoder(context.contentResolver) + + @Singleton + @Provides + fun provideResourceManager(context: Context) = ResourceManager(context) + + @Provides + @Singleton + fun provideTransferRepository( + irohaAPI: IrohaAPI, + gson: Gson, + userDatasource: UserDatasource, + dateFormatter: DateFormatter, + resourceManager: ResourceManager + ): TransferRepository { + return TransferRepositoryImpl(irohaAPI, gson, userDatasource, dateFormatter, resourceManager) + } +} \ No newline at end of file diff --git a/app/src/main/java/jp/co/soramitsu/irohaandroid/di/ViewModelKey.kt b/app/src/main/java/jp/co/soramitsu/irohaandroid/di/ViewModelKey.kt new file mode 100644 index 0000000..f7e2ff7 --- /dev/null +++ b/app/src/main/java/jp/co/soramitsu/irohaandroid/di/ViewModelKey.kt @@ -0,0 +1,12 @@ +package jp.co.soramitsu.irohaandroid.di + +import androidx.lifecycle.ViewModel +import dagger.MapKey +import kotlin.reflect.KClass + +@Target( + AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, + AnnotationTarget.PROPERTY_SETTER +) +@MapKey +annotation class ViewModelKey(val value: KClass) \ No newline at end of file diff --git a/app/src/main/java/jp/co/soramitsu/irohaandroid/domain/MainInteractor.kt b/app/src/main/java/jp/co/soramitsu/irohaandroid/domain/MainInteractor.kt new file mode 100644 index 0000000..a91cf4a --- /dev/null +++ b/app/src/main/java/jp/co/soramitsu/irohaandroid/domain/MainInteractor.kt @@ -0,0 +1,49 @@ +package jp.co.soramitsu.irohaandroid.domain + +import io.reactivex.Completable +import io.reactivex.Single +import jp.co.soramitsu.irohaandroid.data.transfer.TransferRepository +import jp.co.soramitsu.irohaandroid.data.user.UserRepository +import jp.co.soramitsu.irohaandroid.presentation.model.TransactionModel +import jp.co.soramitsu.irohaandroid.presentation.model.Transfer +import javax.inject.Inject + +class MainInteractor @Inject constructor(private val userRepository: UserRepository, private val tranferRepository: TransferRepository) { + + fun getUsername(): Single { + return userRepository.getUsername() + } + + fun getUserDetails(): Single { + return userRepository.getUserDetails() + } + + fun getBalance(): Single { + return userRepository.getUserBalance() + } + + fun removeUserData(): Completable { + return userRepository.removeUserData() + } + + fun generateQrJsonString(amount: String): Single { + return tranferRepository.getReceiveQrString(amount) + } + + fun transfer(username: String, amount: String): Completable { + return tranferRepository.transfer(username, amount) + } + + fun getTransactionHistory(): Single> { + return tranferRepository.getTransactionHistory() + } + + fun processQr(contents: String): Single { + return tranferRepository.processQr(contents) + } + + fun setAccountDetails(details: String): Completable { + return userRepository.setAccountDetails(details) + } + +} \ No newline at end of file diff --git a/app/src/main/java/jp/co/soramitsu/irohaandroid/domain/SignupInteractor.kt b/app/src/main/java/jp/co/soramitsu/irohaandroid/domain/SignupInteractor.kt new file mode 100644 index 0000000..12b5091 --- /dev/null +++ b/app/src/main/java/jp/co/soramitsu/irohaandroid/domain/SignupInteractor.kt @@ -0,0 +1,18 @@ +package jp.co.soramitsu.irohaandroid.domain + +import io.reactivex.Completable +import io.reactivex.Single +import jp.co.soramitsu.irohaandroid.data.user.UserRepository +import jp.co.soramitsu.irohaandroid.util.RegistrationState +import javax.inject.Inject + +class SignupInteractor @Inject constructor(private val userRepository: UserRepository) { + + fun createAccount(username: String): Completable { + return userRepository.createAccount(username) + } + + fun getRegistrationState(): Single { + return userRepository.getRegistrationState() + } +} \ No newline at end of file diff --git a/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/base/BaseActivity.kt b/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/base/BaseActivity.kt new file mode 100644 index 0000000..b35ecde --- /dev/null +++ b/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/base/BaseActivity.kt @@ -0,0 +1,59 @@ +package jp.co.soramitsu.irohaandroid.presentation.base + +import android.os.Bundle +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.LiveData +import androidx.lifecycle.Observer +import jp.co.soramitsu.irohaandroid.R +import javax.inject.Inject + +abstract class BaseActivity: AppCompatActivity() { + + private val observables = mutableListOf>() + @Inject open lateinit var viewModel: T + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + inject() + subscribe(viewModel) + + observe(viewModel.errorLiveData, Observer { + AlertDialog.Builder(this) + .setTitle(R.string.general_error_title) + .setMessage(it) + .setPositiveButton(android.R.string.ok) { _, _ -> } + .show() + }) + + observe(viewModel.errorResourceLiveData, Observer { + AlertDialog.Builder(this) + .setTitle(R.string.general_error_title) + .setMessage(it) + .setPositiveButton(android.R.string.ok) { _, _ -> } + .show() + }) + } + + override fun onStart() { + super.onStart() + initViews() + } + + override fun onDestroy() { + observables.forEach { it.removeObservers(this) } + super.onDestroy() + } + + @Suppress("unchecked_cast") + protected fun observe(source: LiveData, observer: Observer) { + source.observe(this, observer as Observer) + observables.add(source) + } + + abstract fun inject() + + abstract fun initViews() + + abstract fun subscribe(viewmodel: T) +} \ No newline at end of file diff --git a/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/base/BaseFragment.kt b/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/base/BaseFragment.kt new file mode 100644 index 0000000..f3e1670 --- /dev/null +++ b/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/base/BaseFragment.kt @@ -0,0 +1,40 @@ +package jp.co.soramitsu.irohaandroid.presentation.base + +import android.os.Bundle +import androidx.appcompat.app.AlertDialog +import androidx.fragment.app.Fragment +import androidx.lifecycle.LiveData +import androidx.lifecycle.Observer +import jp.co.soramitsu.irohaandroid.R +import javax.inject.Inject + +abstract class BaseFragment: Fragment() { + + @Inject protected open lateinit var viewModel: T + + private val observables = mutableListOf>() + + override fun onActivityCreated(savedInstanceState: Bundle?) { + super.onActivityCreated(savedInstanceState) + inject() + initViews() + subscribe(viewModel) + } + + override fun onDestroyView() { + observables.forEach { it.removeObservers(this) } + super.onDestroyView() + } + + @Suppress("unchecked_cast") + protected fun observe(source: LiveData, observer: Observer) { + source.observe(this, observer as Observer) + observables.add(source) + } + + abstract fun initViews() + + abstract fun inject() + + abstract fun subscribe(viewModel: T) +} \ No newline at end of file diff --git a/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/base/BaseViewModel.kt b/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/base/BaseViewModel.kt new file mode 100644 index 0000000..1768809 --- /dev/null +++ b/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/base/BaseViewModel.kt @@ -0,0 +1,37 @@ +package jp.co.soramitsu.irohaandroid.presentation.base + +import androidx.annotation.StringRes +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import io.reactivex.disposables.CompositeDisposable +import jp.co.soramitsu.irohaandroid.R +import jp.co.soramitsu.irohaandroid.util.IrohaException + +open class BaseViewModel: ViewModel() { + + protected val disposables = CompositeDisposable() + + private val _errorLiveData = MutableLiveData() + val errorLiveData: LiveData = _errorLiveData + + private val _errorResourceLiveData = MutableLiveData() + val errorResourceLiveData: LiveData = _errorResourceLiveData + + override fun onCleared() { + super.onCleared() + if (!disposables.isDisposed) disposables.dispose() + } + + fun onError(throwable: Throwable) { + if (throwable is IrohaException) { + _errorLiveData.value = throwable.message + } else { + onError(R.string.server_is_not_reachable) + } + } + + fun onError(@StringRes errorMessage: Int) { + _errorResourceLiveData.value = errorMessage + } +} \ No newline at end of file diff --git a/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/base/BioEnterDialog.kt b/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/base/BioEnterDialog.kt new file mode 100644 index 0000000..6615ea1 --- /dev/null +++ b/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/base/BioEnterDialog.kt @@ -0,0 +1,43 @@ +package jp.co.soramitsu.irohaandroid.presentation.base + +import android.content.Context +import android.view.LayoutInflater +import androidx.appcompat.app.AlertDialog +import com.jakewharton.rxbinding2.widget.RxTextView +import jp.co.soramitsu.irohaandroid.R +import kotlinx.android.synthetic.main.dialog_enter_bio.view.details +import kotlinx.android.synthetic.main.dialog_enter_bio.view.symbols_left + +class BioEnterDialog( + context: Context, + bioEnteredCallback: (String) -> Unit +) { + companion object { + private const val detailsLength = 32 + } + + private val dialog: AlertDialog + + init { + val view = LayoutInflater.from(context).inflate(R.layout.dialog_enter_bio, null) + + RxTextView.textChanges(view.details) + .subscribe { + view.symbols_left.text = "${detailsLength - it.length}" + } + + + dialog = AlertDialog.Builder(context).setView(view) + .setTitle(R.string.enter_invitation_code) + .setNegativeButton(R.string.cancel) { _, _ -> + } + .setPositiveButton(R.string.save) { _, _ -> + bioEnteredCallback(view.details.text.toString()) + } + .create() + } + + fun show() { + dialog.show() + } +} \ No newline at end of file diff --git a/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/base/ChooserDialog.kt b/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/base/ChooserDialog.kt new file mode 100644 index 0000000..bb7cb04 --- /dev/null +++ b/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/base/ChooserDialog.kt @@ -0,0 +1,33 @@ +package jp.co.soramitsu.irohaandroid.presentation.base + +import android.app.AlertDialog +import android.app.Dialog +import android.content.Context +import jp.co.soramitsu.irohaandroid.R + +class ChooserDialog(val context: Context, cameraClick: () -> Unit, galleryClick: () -> Unit) { + + private val instance: Dialog + + init { + instance = AlertDialog.Builder(context) + .setTitle(R.string.qr_chooser_title) + .setItems(R.array.qr_dialog_array) { _, item -> + when (item) { + 0 -> cameraClick() + 1 -> galleryClick() + } + } + .setCancelable(true) + .create() + } + + fun show() { + instance.show() + } + + fun dismiss() { + instance.dismiss() + } + +} \ No newline at end of file diff --git a/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/base/IrohaViewModelFactory.kt b/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/base/IrohaViewModelFactory.kt new file mode 100644 index 0000000..5f9f62c --- /dev/null +++ b/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/base/IrohaViewModelFactory.kt @@ -0,0 +1,22 @@ +package jp.co.soramitsu.irohaandroid.presentation.base + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import javax.inject.Inject +import javax.inject.Provider + +class IrohaViewModelFactory @Inject constructor( + private val creators: @JvmSuppressWildcards Map, Provider> +) : ViewModelProvider.Factory { + + override fun create(modelClass: Class): T { + val found = creators.entries.find { modelClass.isAssignableFrom(it.key) } + val creator = found?.value + ?: throw IllegalArgumentException("Unknown model class $modelClass") + try { + return creator.get() as T + } catch (e: Exception) { + throw RuntimeException(e) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/base/LoadingDialog.kt b/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/base/LoadingDialog.kt new file mode 100644 index 0000000..3083a47 --- /dev/null +++ b/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/base/LoadingDialog.kt @@ -0,0 +1,15 @@ +package jp.co.soramitsu.irohaandroid.presentation.base + +import android.app.ProgressDialog +import androidx.appcompat.app.AppCompatActivity +import jp.co.soramitsu.irohaandroid.R +import javax.inject.Inject + + +class LoadingDialog @Inject constructor(context: AppCompatActivity) : ProgressDialog(context) { + + init { + setMessage(context.getString(R.string.loading)) + setCancelable(false) + } +} \ No newline at end of file diff --git a/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/main/MainActivity.kt b/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/main/MainActivity.kt new file mode 100644 index 0000000..2e37eb9 --- /dev/null +++ b/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/main/MainActivity.kt @@ -0,0 +1,90 @@ +package jp.co.soramitsu.irohaandroid.presentation.main + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import androidx.lifecycle.Observer +import jp.co.soramitsu.irohaandroid.IrohaSampleApp +import jp.co.soramitsu.irohaandroid.R +import jp.co.soramitsu.irohaandroid.presentation.base.BaseActivity +import jp.co.soramitsu.irohaandroid.presentation.base.BioEnterDialog +import jp.co.soramitsu.irohaandroid.presentation.base.LoadingDialog +import jp.co.soramitsu.irohaandroid.presentation.main.history.HistoryFragment +import jp.co.soramitsu.irohaandroid.presentation.main.receive.ReceiveFragment +import jp.co.soramitsu.irohaandroid.presentation.main.send.SendFragment +import jp.co.soramitsu.irohaandroid.presentation.signup.SignupActivity +import jp.co.soramitsu.irohaandroid.util.Const +import kotlinx.android.synthetic.main.activity_main.* +import javax.inject.Inject + +class MainActivity : BaseActivity() { + + companion object { + + fun start(context: Context) { + val intent = Intent(context, MainActivity::class.java).apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + } + context.startActivity(intent) + } + } + + @Inject lateinit var loader: LoadingDialog + + private lateinit var dialog: BioEnterDialog + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + } + + override fun inject() { + (application as IrohaSampleApp) + .appComponent + .mainComponentBuilder() + .withActivity(this) + .build() + .inject(this) + } + + override fun initViews() { + dialog = BioEnterDialog(this) { viewModel.bioEntered(it) } + + viewModel.getUserInfo() + + logout.setOnClickListener { viewModel.logoutClicked() } + + bio.setOnClickListener { viewModel.bioClicked() } + + val adapter = TabsViewpagerAdapter(supportFragmentManager).apply { + addPage(getString(R.string.send), SendFragment.newInstance()) + addPage(getString(R.string.receive), ReceiveFragment.newInstance()) + addPage(getString(R.string.history), HistoryFragment.newInstance()) + } + contentPager.adapter = adapter + tabs.setupWithViewPager(contentPager) + } + + override fun subscribe(viewmodel: MainViewModel) { + observe(viewmodel.userLiveData, Observer { + username.text = it.username + + if (it.userDetails.isNotEmpty()) { + bio.text = it.userDetails + } + balance.text = "${it.balance} ${Const.ASSET_NAME}" + }) + + observe(viewmodel.loadingEventLiveData, Observer { + if (it) loader.show() else loader.dismiss() + }) + + observe(viewmodel.logoutEventLiveData, Observer { + SignupActivity.start(this) + }) + + observe(viewmodel.bioClickedEventLiveData, Observer { + dialog.show() + }) + } +} diff --git a/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/main/MainViewModel.kt b/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/main/MainViewModel.kt new file mode 100644 index 0000000..4769548 --- /dev/null +++ b/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/main/MainViewModel.kt @@ -0,0 +1,177 @@ +package jp.co.soramitsu.irohaandroid.presentation.main + +import android.graphics.Bitmap +import android.net.Uri +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.schedulers.Schedulers +import jp.co.soramitsu.irohaandroid.R +import jp.co.soramitsu.irohaandroid.domain.MainInteractor +import jp.co.soramitsu.irohaandroid.presentation.base.BaseViewModel +import jp.co.soramitsu.irohaandroid.presentation.model.TransactionModel +import jp.co.soramitsu.irohaandroid.presentation.model.Transfer +import jp.co.soramitsu.irohaandroid.presentation.model.User +import jp.co.soramitsu.irohaandroid.util.Const +import jp.co.soramitsu.irohaandroid.util.Event +import jp.co.soramitsu.irohaandroid.util.QrCodeDecoder +import jp.co.soramitsu.irohaandroid.util.QrCodeGenerator +import java.math.BigDecimal +import javax.inject.Inject + +class MainViewModel @Inject constructor( + private val mainInteractor: MainInteractor, + private val qrCodeGenerator: QrCodeGenerator, + private val qrCodeDecoder: QrCodeDecoder +) : BaseViewModel() { + + private val _loadingEventLiveData = MutableLiveData() + val loadingEventLiveData: LiveData = _loadingEventLiveData + + private val _userLiveData = MutableLiveData() + val userLiveData: LiveData = _userLiveData + + private val _logoutEventLiveData = MutableLiveData>() + val logoutEventLiveData: LiveData> = _logoutEventLiveData + + private val _qrBitmapLiveData = MutableLiveData() + val qrBitmapLiveData: LiveData = _qrBitmapLiveData + + private val _transferLiveData = MutableLiveData() + val transferLiveData: LiveData = _transferLiveData + + private val _transactionsHistoryLiveData = MutableLiveData>() + val transactionsHistoryLiveData: LiveData> = _transactionsHistoryLiveData + + private val _bioClickedEventLiveData = MutableLiveData>() + val bioClickedEventLiveData: LiveData> = _bioClickedEventLiveData + + private val _emptyHistoryPlacholderVisibilityLiveData = MutableLiveData() + val emptyHistoryPlacholderVisibilityLiveData: LiveData = _emptyHistoryPlacholderVisibilityLiveData + + private val _transactionsListVisibilityLiveData = MutableLiveData() + val transactionsListVisibilityLiveData: LiveData = _transactionsListVisibilityLiveData + + fun getUserInfo() { + disposables.add( + mainInteractor.getUsername() + .flatMap { username -> + mainInteractor.getUserDetails() + .flatMap { details -> + mainInteractor.getBalance() + .map { User(username, details, it) } + } + } + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ _userLiveData.value = it }, { onError(it) }) + ) + } + + fun logoutClicked() { + disposables.add( + mainInteractor.removeUserData() + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ _logoutEventLiveData.value = Event(Unit) }, { it.printStackTrace() }) + ) + } + + fun getQrImage(amount: String) { + disposables.add( + mainInteractor.generateQrJsonString(amount) + .map { qrCodeGenerator.generateQrBitmap(it) } + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ _qrBitmapLiveData.value = it }, { it.printStackTrace() }) + ) + } + + fun transfer(username: String, amount: String) { + if (areFieldsValid(amount, _userLiveData.value?.balance, username)) { + disposables.add( + mainInteractor.transfer(username, amount) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .doOnSubscribe { _loadingEventLiveData.value = true } + .doFinally { _loadingEventLiveData.value = false } + .subscribe({ getUserInfo() }, { onError(it) }) + ) + } + } + + fun getTransactionHistory() { + disposables.add( + mainInteractor.getTransactionHistory() + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ + _emptyHistoryPlacholderVisibilityLiveData.value = it.isEmpty() + _transactionsListVisibilityLiveData.value = it.isNotEmpty() + + _transactionsHistoryLiveData.value = it + }, { it.printStackTrace() }) + ) + } + + private fun areFieldsValid(amount: String?, balance: String?, username: String): Boolean { + if (username.isNullOrEmpty()) { + onError(R.string.username_empty_error) + return false + } + + if (amount.isNullOrEmpty()) { + onError(R.string.amount_error) + return false + } + + if (amount.toDouble() <= 0) { + onError(R.string.amount_is_zero_error) + return false + } + + if (BigDecimal(amount) > BigDecimal(balance ?: "0")) { + onError(R.string.insufficient_balance) + return false + } + + return true + } + + fun qrStringProcess(contents: String) { + disposables.add( + mainInteractor.processQr(contents) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .doOnSubscribe { _loadingEventLiveData.value = true } + .doFinally { _loadingEventLiveData.value = false } + .subscribe({ _transferLiveData.value = it }, { onError(it) }) + ) + } + + fun decodeTextFromBitmapQr(uri: Uri) { + disposables.add( + qrCodeDecoder.decodeQrFromUri(uri) + .flatMap { mainInteractor.processQr(it) } + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ _transferLiveData.value = it }, { onError(it) }) + ) + } + + fun bioClicked() { + _bioClickedEventLiveData.value = Event(Unit) + } + + fun bioEntered(it: String) { + disposables.add( + mainInteractor.setAccountDetails(it) + .doOnComplete { (getUserInfo()) } + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .doOnSubscribe { _loadingEventLiveData.value = true } + .doFinally { _loadingEventLiveData.value = false } + .subscribe({}, {onError(it)}) + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/main/TabsViewpagerAdapter.kt b/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/main/TabsViewpagerAdapter.kt new file mode 100644 index 0000000..b2ac0e0 --- /dev/null +++ b/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/main/TabsViewpagerAdapter.kt @@ -0,0 +1,22 @@ +package jp.co.soramitsu.irohaandroid.presentation.main + +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentManager +import androidx.fragment.app.FragmentPagerAdapter + +class TabsViewpagerAdapter( + fragmentManager: FragmentManager +) : FragmentPagerAdapter(fragmentManager) { + + private val pages = mutableListOf>() + + fun addPage(title: String, fragment: Fragment) { + pages.add(Pair(title, fragment)) + } + + override fun getPageTitle(position: Int) = pages[position].first + + override fun getItem(position: Int) = pages[position].second + + override fun getCount() = pages.size +} \ No newline at end of file diff --git a/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/main/di/MainComponent.kt b/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/main/di/MainComponent.kt new file mode 100644 index 0000000..e25c682 --- /dev/null +++ b/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/main/di/MainComponent.kt @@ -0,0 +1,36 @@ +package jp.co.soramitsu.irohaandroid.presentation.main.di + +import androidx.appcompat.app.AppCompatActivity +import dagger.BindsInstance +import dagger.Subcomponent +import jp.co.soramitsu.irohaandroid.presentation.main.MainActivity +import jp.co.soramitsu.irohaandroid.presentation.main.history.HistoryFragment +import jp.co.soramitsu.irohaandroid.presentation.main.receive.ReceiveFragment +import jp.co.soramitsu.irohaandroid.presentation.main.send.SendFragment + +@Subcomponent( + modules = [ + MainModule::class + ] +) + +interface MainComponent { + + @Subcomponent.Builder + interface Builder { + + @BindsInstance + fun withActivity(activity: AppCompatActivity): Builder + + fun build(): MainComponent + + } + + fun inject(mainActivity: MainActivity) + + fun inject(receiveFragment: ReceiveFragment) + + fun inject(sendFragment: SendFragment) + + fun inject(historyFragment: HistoryFragment) +} \ No newline at end of file diff --git a/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/main/di/MainModule.kt b/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/main/di/MainModule.kt new file mode 100644 index 0000000..42b4d65 --- /dev/null +++ b/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/main/di/MainModule.kt @@ -0,0 +1,32 @@ +package jp.co.soramitsu.irohaandroid.presentation.main.di + +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.ViewModelProviders +import dagger.Module +import dagger.Provides +import dagger.multibindings.IntoMap +import jp.co.soramitsu.irohaandroid.di.ViewModelKey +import jp.co.soramitsu.irohaandroid.domain.MainInteractor +import jp.co.soramitsu.irohaandroid.presentation.main.MainViewModel +import jp.co.soramitsu.irohaandroid.util.QrCodeDecoder +import jp.co.soramitsu.irohaandroid.util.QrCodeGenerator + +@Module +class MainModule { + + @Provides + @IntoMap + @ViewModelKey(MainViewModel::class) + fun provideViewModel(mainInteractor: MainInteractor, qrCodeGenerator: QrCodeGenerator, qrCodeDecoder: QrCodeDecoder): ViewModel { + return MainViewModel(mainInteractor, qrCodeGenerator, qrCodeDecoder) + } + + @Provides + fun provideMainViewModel(activity: AppCompatActivity, viewmodelFactory: ViewModelProvider.Factory): MainViewModel { + return ViewModelProviders.of(activity, viewmodelFactory).get(MainViewModel::class.java) + } + + +} \ No newline at end of file diff --git a/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/main/history/HistoryAdapter.kt b/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/main/history/HistoryAdapter.kt new file mode 100644 index 0000000..804545e --- /dev/null +++ b/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/main/history/HistoryAdapter.kt @@ -0,0 +1,50 @@ +package jp.co.soramitsu.irohaandroid.presentation.main.history + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import jp.co.soramitsu.irohaandroid.R +import jp.co.soramitsu.irohaandroid.presentation.model.TransactionModel +import jp.co.soramitsu.irohaandroid.util.Const + +class HistoryAdapter(private val context: Context) : ListAdapter(DiffCallback) { + + override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): TransactionViewHolder { + val view = LayoutInflater.from(viewGroup.context).inflate(R.layout.item_transaction, viewGroup, false) + return TransactionViewHolder(view) + } + + override fun onBindViewHolder(transactionViewHolder: TransactionViewHolder, position: Int) { + transactionViewHolder.bind(getItem(position)) + } +} + +class TransactionViewHolder( + itemView: View +) : RecyclerView.ViewHolder(itemView) { + + private val username: TextView = itemView.findViewById(R.id.username) + private val date: TextView = itemView.findViewById(R.id.date) + private val amount: TextView = itemView.findViewById(R.id.amount) + + fun bind(transaction: TransactionModel) { + username.text = transaction.username + date.text = transaction.prettyDate + amount.text = "${transaction.prettyAmount} ${Const.ASSET_NAME}" + } +} + +object DiffCallback : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: TransactionModel, newItem: TransactionModel): Boolean { + return oldItem.id == newItem.id + } + + override fun areContentsTheSame(oldItem: TransactionModel, newItem: TransactionModel): Boolean { + return oldItem == newItem + } +} \ No newline at end of file diff --git a/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/main/history/HistoryFragment.kt b/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/main/history/HistoryFragment.kt new file mode 100644 index 0000000..253016e --- /dev/null +++ b/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/main/history/HistoryFragment.kt @@ -0,0 +1,66 @@ +package jp.co.soramitsu.irohaandroid.presentation.main.history + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.Observer +import androidx.recyclerview.widget.LinearLayoutManager +import jp.co.soramitsu.irohaandroid.IrohaSampleApp + +import jp.co.soramitsu.irohaandroid.R +import jp.co.soramitsu.irohaandroid.presentation.base.BaseFragment +import jp.co.soramitsu.irohaandroid.presentation.main.MainViewModel +import kotlinx.android.synthetic.main.fragment_history.* + +class HistoryFragment : BaseFragment() { + + companion object { + fun newInstance(): HistoryFragment { + return HistoryFragment() + } + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.fragment_history, container, false) + } + + override fun initViews() { + viewModel.getTransactionHistory() + + refresh.setOnRefreshListener { + viewModel.getTransactionHistory() + } + } + + override fun inject() { + (activity!!.application as IrohaSampleApp) + .appComponent + .mainComponentBuilder() + .withActivity(activity as AppCompatActivity) + .build() + .inject(this) + } + + override fun subscribe(viewModel: MainViewModel) { + observe(viewModel.transactionsHistoryLiveData, Observer { + if (transactions.adapter == null) { + transactions.layoutManager = LinearLayoutManager(activity!!) + transactions.adapter = HistoryAdapter(activity!!) + } + + (transactions.adapter as HistoryAdapter).submitList(it) + + refresh.isRefreshing = false + }) + + observe(viewModel.emptyHistoryPlacholderVisibilityLiveData, Observer { + transactionsPlaceholder.visibility = if (it) View.VISIBLE else View.GONE + }) + + observe(viewModel.transactionsListVisibilityLiveData, Observer { + transactions.visibility = if (it) View.VISIBLE else View.GONE + }) + } +} diff --git a/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/main/receive/ReceiveFragment.kt b/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/main/receive/ReceiveFragment.kt new file mode 100644 index 0000000..7b31335 --- /dev/null +++ b/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/main/receive/ReceiveFragment.kt @@ -0,0 +1,49 @@ +package jp.co.soramitsu.irohaandroid.presentation.main.receive + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.Observer +import com.jakewharton.rxbinding2.widget.RxTextView +import jp.co.soramitsu.irohaandroid.IrohaSampleApp + +import jp.co.soramitsu.irohaandroid.R +import jp.co.soramitsu.irohaandroid.presentation.base.BaseFragment +import jp.co.soramitsu.irohaandroid.presentation.main.MainViewModel +import kotlinx.android.synthetic.main.fragment_receive.amount +import kotlinx.android.synthetic.main.fragment_receive.qr_code_image_view + +class ReceiveFragment : BaseFragment() { + + companion object { + fun newInstance(): ReceiveFragment { + return ReceiveFragment() + } + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.fragment_receive, container, false) + } + + override fun initViews() { + RxTextView.textChanges(amount) + .subscribe { viewModel.getQrImage(it.toString()) } + } + + override fun inject() { + (activity!!.application as IrohaSampleApp) + .appComponent + .mainComponentBuilder() + .withActivity(activity as AppCompatActivity) + .build() + .inject(this) + } + + override fun subscribe(viewModel: MainViewModel) { + observe(viewModel.qrBitmapLiveData, Observer { + qr_code_image_view.setImageBitmap(it) + }) + } +} diff --git a/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/main/send/SendFragment.kt b/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/main/send/SendFragment.kt new file mode 100644 index 0000000..ac31937 --- /dev/null +++ b/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/main/send/SendFragment.kt @@ -0,0 +1,114 @@ +package jp.co.soramitsu.irohaandroid.presentation.main.send + +import android.Manifest +import android.app.Activity +import android.content.Intent +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.Observer +import com.google.zxing.integration.android.IntentIntegrator +import com.jakewharton.rxbinding2.view.RxView +import com.tbruyelle.rxpermissions2.RxPermissions +import jp.co.soramitsu.irohaandroid.IrohaSampleApp +import jp.co.soramitsu.irohaandroid.R +import jp.co.soramitsu.irohaandroid.presentation.base.BaseFragment +import jp.co.soramitsu.irohaandroid.presentation.base.ChooserDialog +import jp.co.soramitsu.irohaandroid.presentation.main.MainViewModel +import jp.co.soramitsu.irohaandroid.util.username +import kotlinx.android.synthetic.main.fragment_send.* + +class SendFragment : BaseFragment() { + + companion object { + private const val PICK_IMAGE_REQUEST = 101 + + fun newInstance(): SendFragment { + return SendFragment() + } + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + return inflater.inflate(R.layout.fragment_send, container, false) + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + val result = IntentIntegrator.parseActivityResult(requestCode, resultCode, data) + if (result != null) { + if (result.contents == null) { + Toast.makeText(activity, R.string.scan_canceled, Toast.LENGTH_LONG).show() + } else { + viewModel.qrStringProcess(result.contents) + } + } else { + super.onActivityResult(requestCode, resultCode, data) + } + + if (requestCode == PICK_IMAGE_REQUEST && resultCode == Activity.RESULT_OK && data != null && data.data != null) { + viewModel.decodeTextFromBitmapQr(data.data!!) + } + } + + private lateinit var chooserDialog: ChooserDialog + private lateinit var integrator: IntentIntegrator + + override fun initViews() { + send.setOnClickListener { viewModel.transfer(to.text.toString(), amount.text.toString()) } + + qr.setOnClickListener { showQrChooser() } + + integrator = IntentIntegrator.forSupportFragment(this).apply { + setDesiredBarcodeFormats(IntentIntegrator.QR_CODE_TYPES) + setPrompt(getString(R.string.scan_qr)) + setOrientationLocked(true) + setBeepEnabled(false) + } + + chooserDialog = ChooserDialog(activity!!, { initiateScan() }, { selectQrFromGallery() }) + } + + override fun inject() { + (activity!!.application as IrohaSampleApp) + .appComponent + .mainComponentBuilder() + .withActivity(activity as AppCompatActivity) + .build() + .inject(this) + } + + override fun subscribe(viewModel: MainViewModel) { + observe(viewModel.transferLiveData, Observer { + to.setText(it.accountId.username()) + amount.setText(it.amount) + }) + } + + private fun initiateScan() { + RxPermissions(this) + .request(Manifest.permission.CAMERA) + .subscribe { + if (it) integrator.initiateScan() + } + } + + private fun selectQrFromGallery() { + val intent = Intent().apply { + type = "image/*" + action = Intent.ACTION_GET_CONTENT + } + + startActivityForResult(Intent.createChooser(intent, getString(R.string.select_qr)), PICK_IMAGE_REQUEST) + } + + private fun showQrChooser() { + chooserDialog.show() + } +} diff --git a/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/model/TransactionModel.kt b/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/model/TransactionModel.kt new file mode 100644 index 0000000..f49780d --- /dev/null +++ b/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/model/TransactionModel.kt @@ -0,0 +1,8 @@ +package jp.co.soramitsu.irohaandroid.presentation.model + +data class TransactionModel( + val id: String, + val prettyDate: String, + val username: String, + val prettyAmount: String +) \ No newline at end of file diff --git a/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/model/Transfer.kt b/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/model/Transfer.kt new file mode 100644 index 0000000..3cc467c --- /dev/null +++ b/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/model/Transfer.kt @@ -0,0 +1,3 @@ +package jp.co.soramitsu.irohaandroid.presentation.model + +data class Transfer(val amount: String, val accountId: String) \ No newline at end of file diff --git a/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/model/User.kt b/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/model/User.kt new file mode 100644 index 0000000..48523dd --- /dev/null +++ b/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/model/User.kt @@ -0,0 +1,3 @@ +package jp.co.soramitsu.irohaandroid.presentation.model + +data class User(val username: String, val userDetails: String, val balance: String) \ No newline at end of file diff --git a/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/model/mapper/TransactionMapper.kt b/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/model/mapper/TransactionMapper.kt new file mode 100644 index 0000000..38e6e93 --- /dev/null +++ b/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/model/mapper/TransactionMapper.kt @@ -0,0 +1,33 @@ +package jp.co.soramitsu.irohaandroid.presentation.model.mapper + +import iroha.protocol.TransactionOuterClass +import jp.co.soramitsu.irohaandroid.presentation.model.TransactionModel +import jp.co.soramitsu.irohaandroid.util.DateFormatter +import jp.co.soramitsu.irohaandroid.util.accountId +import jp.co.soramitsu.irohaandroid.util.username +import java.util.* + +fun mapIrohaTransactionsToTransactions( + transactionsDto: List, + dateFormatter: DateFormatter, + username: String +): List { + val transactions = mutableListOf() + + for (transaction in transactionsDto) { + transaction.payload.reducedPayload.getCommands(0).transferAsset.let { + if (it.amount.isNotEmpty()) { + transactions.add( + TransactionModel( + transaction.getSignatures(0).signature.substring(0, 8), + dateFormatter.formatDate(Date(transaction.payload.reducedPayload.createdTime)), + if (it.srcAccountId == username.accountId()) it.destAccountId.username() else it.srcAccountId.username(), + if (it.srcAccountId == username.accountId()) "- ${it.amount}" else it.amount + ) + ) + } + } + } + + return transactions.reversed() +} \ No newline at end of file diff --git a/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/signup/SignupActivity.kt b/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/signup/SignupActivity.kt new file mode 100644 index 0000000..141b233 --- /dev/null +++ b/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/signup/SignupActivity.kt @@ -0,0 +1,58 @@ +package jp.co.soramitsu.irohaandroid.presentation.signup + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import androidx.lifecycle.Observer +import jp.co.soramitsu.irohaandroid.IrohaSampleApp +import jp.co.soramitsu.irohaandroid.R +import jp.co.soramitsu.irohaandroid.presentation.base.BaseActivity +import jp.co.soramitsu.irohaandroid.presentation.base.LoadingDialog +import jp.co.soramitsu.irohaandroid.presentation.main.MainActivity +import kotlinx.android.synthetic.main.activity_signup.* +import javax.inject.Inject + +class SignupActivity : BaseActivity() { + + companion object { + fun start(context: Context) { + val intent = Intent(context, SignupActivity::class.java).apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + } + context.startActivity(intent) + } + } + + @Inject lateinit var viewmodel: SignupViewModel + @Inject lateinit var loader: LoadingDialog + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_signup) + } + + override fun initViews() { + registerButton.setOnClickListener { + viewmodel.signupButtonPressed(usernameTextView.text.toString()) + } + } + + override fun subscribe(viewmodel: SignupViewModel) { + observe(viewmodel.loadingLiveData, Observer { + if (it) loader.show() else loader.dismiss() + }) + + observe(viewmodel.registrationSuccessEventLiveData, Observer { + MainActivity.start(this) + }) + } + + override fun inject() { + (application as IrohaSampleApp) + .appComponent + .signupComponentBuilder() + .withActivity(this) + .build() + .inject(this) + } +} diff --git a/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/signup/SignupViewModel.kt b/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/signup/SignupViewModel.kt new file mode 100644 index 0000000..fcc5960 --- /dev/null +++ b/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/signup/SignupViewModel.kt @@ -0,0 +1,38 @@ +package jp.co.soramitsu.irohaandroid.presentation.signup + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.schedulers.Schedulers +import jp.co.soramitsu.irohaandroid.domain.SignupInteractor +import jp.co.soramitsu.irohaandroid.presentation.base.BaseViewModel +import jp.co.soramitsu.irohaandroid.util.Event +import javax.inject.Inject + +class SignupViewModel @Inject constructor(var signupInteractor: SignupInteractor) : + BaseViewModel() { + + private val _loadingLiveData = MutableLiveData() + val loadingLiveData: LiveData = _loadingLiveData + + private val _registrationSuccessEventLiveData = MutableLiveData>() + val registrationSuccessEventLiveData: LiveData> = _registrationSuccessEventLiveData + + fun signupButtonPressed(username: String) { + if (username.isEmpty()) { + return + } + + disposables.add( + signupInteractor.createAccount(username) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .doOnSubscribe { _loadingLiveData.value = true } + .doFinally { _loadingLiveData.value = false } + .subscribe( + { _registrationSuccessEventLiveData.value = Event(Unit) }, + { onError(it) } + ) + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/signup/di/SignupComponent.kt b/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/signup/di/SignupComponent.kt new file mode 100644 index 0000000..2d36e85 --- /dev/null +++ b/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/signup/di/SignupComponent.kt @@ -0,0 +1,27 @@ +package jp.co.soramitsu.irohaandroid.presentation.signup.di + +import androidx.appcompat.app.AppCompatActivity +import dagger.BindsInstance +import dagger.Subcomponent +import jp.co.soramitsu.irohaandroid.presentation.signup.SignupActivity + +@Subcomponent( + modules = [ + SignupModule::class + ] +) + +interface SignupComponent { + + @Subcomponent.Builder + interface Builder { + + @BindsInstance + fun withActivity(activity: AppCompatActivity): Builder + + fun build(): SignupComponent + + } + + fun inject(signupActivity: SignupActivity) +} \ No newline at end of file diff --git a/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/signup/di/SignupModule.kt b/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/signup/di/SignupModule.kt new file mode 100644 index 0000000..8a1df9c --- /dev/null +++ b/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/signup/di/SignupModule.kt @@ -0,0 +1,30 @@ +package jp.co.soramitsu.irohaandroid.presentation.signup.di + +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.ViewModelProviders +import dagger.Module +import dagger.Provides +import dagger.multibindings.IntoMap +import jp.co.soramitsu.irohaandroid.di.ViewModelKey +import jp.co.soramitsu.irohaandroid.domain.SignupInteractor +import jp.co.soramitsu.irohaandroid.presentation.signup.SignupViewModel + +@Module +class SignupModule { + + @Provides + @IntoMap + @ViewModelKey(SignupViewModel::class) + fun provideViewModel(signupInteractor: SignupInteractor): ViewModel { + return SignupViewModel(signupInteractor) + } + + @Provides + fun provideSignupViewModel(activity: AppCompatActivity, viewmodelFactory: ViewModelProvider.Factory): SignupViewModel { + return ViewModelProviders.of(activity, viewmodelFactory).get(SignupViewModel::class.java) + } + + +} \ No newline at end of file diff --git a/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/splash/SplashActivity.kt b/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/splash/SplashActivity.kt new file mode 100644 index 0000000..139cade --- /dev/null +++ b/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/splash/SplashActivity.kt @@ -0,0 +1,32 @@ +package jp.co.soramitsu.irohaandroid.presentation.splash + +import androidx.lifecycle.Observer +import jp.co.soramitsu.irohaandroid.IrohaSampleApp +import jp.co.soramitsu.irohaandroid.presentation.base.BaseActivity +import jp.co.soramitsu.irohaandroid.presentation.main.MainActivity +import jp.co.soramitsu.irohaandroid.presentation.signup.SignupActivity +import jp.co.soramitsu.irohaandroid.util.RegistrationState + +class SplashActivity : BaseActivity() { + + override fun inject() { + (application as IrohaSampleApp) + .appComponent + .splashComponentBuilder() + .build() + .inject(this) + } + + override fun initViews() { + viewModel.nextScreen() + } + + override fun subscribe(viewmodel: SplashViewModel) { + observe(viewmodel.registrationStateLiveData, Observer { + when(it) { + RegistrationState.INITIAL -> SignupActivity.start(this) + RegistrationState.REGISTERED -> MainActivity.start(this) + } + }) + } +} diff --git a/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/splash/SplashViewModel.kt b/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/splash/SplashViewModel.kt new file mode 100644 index 0000000..f03c7c0 --- /dev/null +++ b/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/splash/SplashViewModel.kt @@ -0,0 +1,24 @@ +package jp.co.soramitsu.irohaandroid.presentation.splash + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.schedulers.Schedulers +import jp.co.soramitsu.irohaandroid.domain.SignupInteractor +import jp.co.soramitsu.irohaandroid.presentation.base.BaseViewModel +import jp.co.soramitsu.irohaandroid.util.RegistrationState +import javax.inject.Inject + +class SplashViewModel @Inject constructor(private val signupInteractor: SignupInteractor): BaseViewModel() { + + private val _registrationStateLiveData = MutableLiveData() + val registrationStateLiveData: LiveData = _registrationStateLiveData + + fun nextScreen() { + signupInteractor.getRegistrationState() + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({_registrationStateLiveData.value = it}, { it.printStackTrace() }) + } + +} \ No newline at end of file diff --git a/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/splash/di/SplashComponent.kt b/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/splash/di/SplashComponent.kt new file mode 100644 index 0000000..984d8f8 --- /dev/null +++ b/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/splash/di/SplashComponent.kt @@ -0,0 +1,22 @@ +package jp.co.soramitsu.irohaandroid.presentation.splash.di + +import dagger.Subcomponent +import jp.co.soramitsu.irohaandroid.presentation.splash.SplashActivity + +@Subcomponent( + modules = [ + SplashModule::class + ] +) + +interface SplashComponent { + + @Subcomponent.Builder + interface Builder { + + fun build(): SplashComponent + + } + + fun inject(splashActivity: SplashActivity) +} \ No newline at end of file diff --git a/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/splash/di/SplashModule.kt b/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/splash/di/SplashModule.kt new file mode 100644 index 0000000..4db3e3a --- /dev/null +++ b/app/src/main/java/jp/co/soramitsu/irohaandroid/presentation/splash/di/SplashModule.kt @@ -0,0 +1,30 @@ +package jp.co.soramitsu.irohaandroid.presentation.splash.di + +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.ViewModelProviders +import dagger.Module +import dagger.Provides +import dagger.multibindings.IntoMap +import jp.co.soramitsu.irohaandroid.di.ViewModelKey +import jp.co.soramitsu.irohaandroid.domain.SignupInteractor +import jp.co.soramitsu.irohaandroid.presentation.signup.SignupViewModel + +@Module +class SplashModule { + + @Provides + @IntoMap + @ViewModelKey(SignupViewModel::class) + fun provideViewModel(signupInteractor: SignupInteractor): ViewModel { + return SignupViewModel(signupInteractor) + } + + @Provides + fun provideSignupViewModel(activity: AppCompatActivity, viewmodelFactory: ViewModelProvider.Factory): SignupViewModel { + return ViewModelProviders.of(activity, viewmodelFactory).get(SignupViewModel::class.java) + } + + +} \ No newline at end of file diff --git a/app/src/main/java/jp/co/soramitsu/irohaandroid/util/Const.kt b/app/src/main/java/jp/co/soramitsu/irohaandroid/util/Const.kt new file mode 100644 index 0000000..73d36cc --- /dev/null +++ b/app/src/main/java/jp/co/soramitsu/irohaandroid/util/Const.kt @@ -0,0 +1,11 @@ +package jp.co.soramitsu.irohaandroid.util + +object Const { + const val PUB_KEY = "313a07e6384776ed95447710d15e59148473ccfc052a681317a72a69f2a49910" + const val PRIV_KEY = "f101537e319568c765b2cc89698325604991dca57b9716b58016b253506cab70" + const val CREATOR = "admin@test" + const val QUERY_COUNTER: Long = 1 + const val DOMAIN_ID = "test" + val ASSET_NAME = "IRH" + val ASSET_ID = "${ASSET_NAME.toLowerCase()}#$DOMAIN_ID" +} \ No newline at end of file diff --git a/app/src/main/java/jp/co/soramitsu/irohaandroid/util/DateFormatter.kt b/app/src/main/java/jp/co/soramitsu/irohaandroid/util/DateFormatter.kt new file mode 100644 index 0000000..6aee06b --- /dev/null +++ b/app/src/main/java/jp/co/soramitsu/irohaandroid/util/DateFormatter.kt @@ -0,0 +1,17 @@ +package jp.co.soramitsu.irohaandroid.util + +import java.text.SimpleDateFormat +import java.util.Date + +class DateFormatter { + + companion object { + val PATTERN = "yyyy-MM-dd HH:mm" + } + + fun formatDate(date: Date): String { + val formatter = SimpleDateFormat(PATTERN) + return formatter.format(date) + } + +} \ No newline at end of file diff --git a/app/src/main/java/jp/co/soramitsu/irohaandroid/util/Event.kt b/app/src/main/java/jp/co/soramitsu/irohaandroid/util/Event.kt new file mode 100644 index 0000000..bd3501e --- /dev/null +++ b/app/src/main/java/jp/co/soramitsu/irohaandroid/util/Event.kt @@ -0,0 +1,43 @@ +package jp.co.soramitsu.irohaandroid.util + +import androidx.lifecycle.Observer + +/** + * Used as a wrapper for data that is exposed via a LiveData that represents an event. + */ +open class Event(private val content: T) { + + var hasBeenHandled = false + private set // Allow external read but not write + + /** + * Returns the content and prevents its use again. + */ + fun getContentIfNotHandled(): T? { + return if (hasBeenHandled) { + null + } else { + hasBeenHandled = true + content + } + } + + /** + * Returns the content, even if it's already been handled. + */ + fun peekContent(): T = content +} + +/** + * An [Observer] for [Event]s, simplifying the pattern of checking if the [Event]'s content has + * already been handled. + * + * [onEventUnhandledContent] is *only* called if the [Event]'s contents has not been handled. + */ +class EventObserver(private val onEventUnhandledContent: (T) -> Unit) : Observer> { + override fun onChanged(event: Event?) { + event?.getContentIfNotHandled()?.let { value -> + onEventUnhandledContent(value) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/jp/co/soramitsu/irohaandroid/util/Ext.kt b/app/src/main/java/jp/co/soramitsu/irohaandroid/util/Ext.kt new file mode 100644 index 0000000..3770777 --- /dev/null +++ b/app/src/main/java/jp/co/soramitsu/irohaandroid/util/Ext.kt @@ -0,0 +1,37 @@ +package jp.co.soramitsu.irohaandroid.util + +import iroha.protocol.Endpoint +import jp.co.soramitsu.iroha.java.IrohaAPI +import org.spongycastle.util.encoders.Hex + +fun Endpoint.ToriiResponse.getTransactionStatus(irohaAPI: IrohaAPI, txHash: String): Endpoint.TxStatus { + var txStatus: Endpoint.TxStatus + + do { + txStatus = irohaAPI.txStatusSync(Hex.decode(txHash)).txStatus + Thread.sleep(1000) + } while (txStatus != Endpoint.TxStatus.COMMITTED && txStatus != Endpoint.TxStatus.REJECTED) + + return txStatus +} + +fun Endpoint.ToriiResponse.getErrorString(irohaAPI: IrohaAPI, txHash: String): String { + var response: Endpoint.ToriiResponse + + do { + response = irohaAPI.txStatusSync(Hex.decode(txHash)) + Thread.sleep(1000) + } while (response.txStatus != Endpoint.TxStatus.COMMITTED && response.txStatus != Endpoint.TxStatus.REJECTED) + + return if (response.txStatus == Endpoint.TxStatus.COMMITTED) "" else response.initializationErrorString +} + +fun String.accountId() = "$this@${Const.DOMAIN_ID}" + +fun String.username(): String { + return if (this.isNotEmpty()) { + this.split("@")[0] + } else { + this + } +} \ No newline at end of file diff --git a/app/src/main/java/jp/co/soramitsu/irohaandroid/util/IrohaException.kt b/app/src/main/java/jp/co/soramitsu/irohaandroid/util/IrohaException.kt new file mode 100644 index 0000000..40727eb --- /dev/null +++ b/app/src/main/java/jp/co/soramitsu/irohaandroid/util/IrohaException.kt @@ -0,0 +1,24 @@ +package jp.co.soramitsu.irohaandroid.util + +import jp.co.soramitsu.irohaandroid.R +import java.lang.RuntimeException + +class IrohaException(message: String) : RuntimeException(message) { + + companion object { + + fun businessError(message: Int, resourceManager: ResourceManager): IrohaException { + return IrohaException(resourceManager.getString(message)) + } + + fun unexpectedError(throwable: Throwable): IrohaException { + return IrohaException(throwable.message ?: "") + } + + fun networkException(resourceManager: ResourceManager): IrohaException { + return IrohaException(resourceManager.getString(R.string.server_is_not_reachable)) + } + + } + +} \ No newline at end of file diff --git a/app/src/main/java/jp/co/soramitsu/irohaandroid/util/PrefsUtil.kt b/app/src/main/java/jp/co/soramitsu/irohaandroid/util/PrefsUtil.kt new file mode 100644 index 0000000..b63b76b --- /dev/null +++ b/app/src/main/java/jp/co/soramitsu/irohaandroid/util/PrefsUtil.kt @@ -0,0 +1,29 @@ +package jp.co.soramitsu.irohaandroid.util + +import android.content.Context +import android.content.SharedPreferences + +class PrefsUtil(context: Context) { + + companion object { + private const val PREFS_NAME = "iroha" + } + + private var sharedPreferences: SharedPreferences + + init { + sharedPreferences = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) + } + + fun saveString(key: String, value: String) { + sharedPreferences.edit().putString(key, value).apply() + } + + fun getString(key: String, defaultValue: String): String { + return sharedPreferences.getString(key, defaultValue) + } + + fun clearData() { + sharedPreferences.edit().clear().apply() + } +} \ No newline at end of file diff --git a/app/src/main/java/jp/co/soramitsu/irohaandroid/util/QrCodeDecoder.kt b/app/src/main/java/jp/co/soramitsu/irohaandroid/util/QrCodeDecoder.kt new file mode 100644 index 0000000..99e286c --- /dev/null +++ b/app/src/main/java/jp/co/soramitsu/irohaandroid/util/QrCodeDecoder.kt @@ -0,0 +1,41 @@ +package jp.co.soramitsu.irohaandroid.util + +import android.content.ContentResolver +import android.net.Uri +import android.provider.MediaStore +import com.google.zxing.BinaryBitmap +import com.google.zxing.MultiFormatReader +import com.google.zxing.RGBLuminanceSource +import com.google.zxing.common.HybridBinarizer +import io.reactivex.Single + +class QrCodeDecoder( + private val contentResolver: ContentResolver +) { + + fun decodeQrFromUri(data: Uri): Single { + return decode(data) + .onErrorResumeNext { Single.error(Throwable()) } + } + + private fun decode(data: Uri): Single { + return Single.create { + val qrBitmap = MediaStore.Images.Media.getBitmap(contentResolver, data) + + val pixels = IntArray(qrBitmap.height * qrBitmap.width) + qrBitmap.getPixels(pixels, 0, qrBitmap.width, 0, 0, qrBitmap.width, qrBitmap.height) + qrBitmap.recycle() + val source = RGBLuminanceSource(qrBitmap.width, qrBitmap.height, pixels) + val bBitmap = BinaryBitmap(HybridBinarizer(source)) + val reader = MultiFormatReader() + + val textResult = reader.decode(bBitmap).text + + if (textResult.isNullOrEmpty()) { + it.onError(Throwable()) + } else { + it.onSuccess(textResult) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/jp/co/soramitsu/irohaandroid/util/QrCodeGenerator.kt b/app/src/main/java/jp/co/soramitsu/irohaandroid/util/QrCodeGenerator.kt new file mode 100644 index 0000000..78e7859 --- /dev/null +++ b/app/src/main/java/jp/co/soramitsu/irohaandroid/util/QrCodeGenerator.kt @@ -0,0 +1,37 @@ +package jp.co.soramitsu.irohaandroid.util + +import android.graphics.Bitmap +import com.google.zxing.EncodeHintType +import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel +import com.google.zxing.qrcode.encoder.Encoder + +class QrCodeGenerator( + private val firstColor: Int, + private val secondColor: Int +) { + + companion object { + private const val RECEIVE_QR_SCALE_SIZE = 1024 + private const val PADDING_SIZE = 2 + } + + fun generateQrBitmap(input: String): Bitmap { + val hints = HashMap() + hints[EncodeHintType.CHARACTER_SET] = "UTF-8" + val qrCode = Encoder.encode(input, ErrorCorrectionLevel.H, hints) + val byteMatrix = qrCode.matrix + val width = byteMatrix.width + PADDING_SIZE + val height = byteMatrix.height + PADDING_SIZE + val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) + for (y in 0 until height) { + for (x in 0 until width) { + if (y == 0 || y > byteMatrix.height || x == 0 || x > byteMatrix.width) { + bitmap.setPixel(x, y, secondColor) + } else { + bitmap.setPixel(x, y, if (byteMatrix.get(x - PADDING_SIZE / 2, y - PADDING_SIZE / 2).toInt() == 1) firstColor else secondColor) + } + } + } + return Bitmap.createScaledBitmap(bitmap, RECEIVE_QR_SCALE_SIZE, RECEIVE_QR_SCALE_SIZE, false) + } +} \ No newline at end of file diff --git a/app/src/main/java/jp/co/soramitsu/irohaandroid/util/RegistrationState.kt b/app/src/main/java/jp/co/soramitsu/irohaandroid/util/RegistrationState.kt new file mode 100644 index 0000000..7a15d0d --- /dev/null +++ b/app/src/main/java/jp/co/soramitsu/irohaandroid/util/RegistrationState.kt @@ -0,0 +1,6 @@ +package jp.co.soramitsu.irohaandroid.util + +enum class RegistrationState { + INITIAL, + REGISTERED +} \ No newline at end of file diff --git a/app/src/main/java/jp/co/soramitsu/irohaandroid/util/ResourceManager.kt b/app/src/main/java/jp/co/soramitsu/irohaandroid/util/ResourceManager.kt new file mode 100644 index 0000000..0a99c9c --- /dev/null +++ b/app/src/main/java/jp/co/soramitsu/irohaandroid/util/ResourceManager.kt @@ -0,0 +1,11 @@ +package jp.co.soramitsu.irohaandroid.util + +import android.content.Context + +class ResourceManager(private val context: Context) { + + fun getString(resource: Int): String { + return context.getString(resource) + } + +} \ No newline at end of file diff --git a/iroha-android-sample/src/main/res/drawable-hdpi/background.png b/app/src/main/res/drawable-hdpi/background.png similarity index 100% rename from iroha-android-sample/src/main/res/drawable-hdpi/background.png rename to app/src/main/res/drawable-hdpi/background.png diff --git a/iroha-android-sample/src/main/res/drawable-hdpi/ic_logo.png b/app/src/main/res/drawable-hdpi/ic_logo.png similarity index 100% rename from iroha-android-sample/src/main/res/drawable-hdpi/ic_logo.png rename to app/src/main/res/drawable-hdpi/ic_logo.png diff --git a/iroha-android-sample/src/main/res/drawable-hdpi/iroha_logo_big.png b/app/src/main/res/drawable-hdpi/iroha_logo_big.png similarity index 100% rename from iroha-android-sample/src/main/res/drawable-hdpi/iroha_logo_big.png rename to app/src/main/res/drawable-hdpi/iroha_logo_big.png diff --git a/iroha-android-sample/src/main/res/drawable-hdpi/qr.png b/app/src/main/res/drawable-hdpi/qr.png similarity index 100% rename from iroha-android-sample/src/main/res/drawable-hdpi/qr.png rename to app/src/main/res/drawable-hdpi/qr.png diff --git a/iroha-android-sample/src/main/res/drawable-ldpi/background.png b/app/src/main/res/drawable-ldpi/background.png similarity index 100% rename from iroha-android-sample/src/main/res/drawable-ldpi/background.png rename to app/src/main/res/drawable-ldpi/background.png diff --git a/iroha-android-sample/src/main/res/drawable-ldpi/ic_logo.png b/app/src/main/res/drawable-ldpi/ic_logo.png similarity index 100% rename from iroha-android-sample/src/main/res/drawable-ldpi/ic_logo.png rename to app/src/main/res/drawable-ldpi/ic_logo.png diff --git a/iroha-android-sample/src/main/res/drawable-ldpi/iroha_logo_big.png b/app/src/main/res/drawable-ldpi/iroha_logo_big.png similarity index 100% rename from iroha-android-sample/src/main/res/drawable-ldpi/iroha_logo_big.png rename to app/src/main/res/drawable-ldpi/iroha_logo_big.png diff --git a/iroha-android-sample/src/main/res/drawable-ldpi/qr.png b/app/src/main/res/drawable-ldpi/qr.png similarity index 100% rename from iroha-android-sample/src/main/res/drawable-ldpi/qr.png rename to app/src/main/res/drawable-ldpi/qr.png diff --git a/iroha-android-sample/src/main/res/drawable-mdpi/background.png b/app/src/main/res/drawable-mdpi/background.png similarity index 100% rename from iroha-android-sample/src/main/res/drawable-mdpi/background.png rename to app/src/main/res/drawable-mdpi/background.png diff --git a/iroha-android-sample/src/main/res/drawable-mdpi/ic_logo.png b/app/src/main/res/drawable-mdpi/ic_logo.png similarity index 100% rename from iroha-android-sample/src/main/res/drawable-mdpi/ic_logo.png rename to app/src/main/res/drawable-mdpi/ic_logo.png diff --git a/iroha-android-sample/src/main/res/drawable-mdpi/iroha_logo_big.png b/app/src/main/res/drawable-mdpi/iroha_logo_big.png similarity index 100% rename from iroha-android-sample/src/main/res/drawable-mdpi/iroha_logo_big.png rename to app/src/main/res/drawable-mdpi/iroha_logo_big.png diff --git a/iroha-android-sample/src/main/res/drawable-mdpi/qr.png b/app/src/main/res/drawable-mdpi/qr.png similarity index 100% rename from iroha-android-sample/src/main/res/drawable-mdpi/qr.png rename to app/src/main/res/drawable-mdpi/qr.png diff --git a/iroha-android-sample/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml similarity index 100% rename from iroha-android-sample/src/main/res/drawable-v24/ic_launcher_foreground.xml rename to app/src/main/res/drawable-v24/ic_launcher_foreground.xml diff --git a/iroha-android-sample/src/main/res/drawable-xhdpi/background.png b/app/src/main/res/drawable-xhdpi/background.png similarity index 100% rename from iroha-android-sample/src/main/res/drawable-xhdpi/background.png rename to app/src/main/res/drawable-xhdpi/background.png diff --git a/iroha-android-sample/src/main/res/drawable-xhdpi/ic_logo.png b/app/src/main/res/drawable-xhdpi/ic_logo.png similarity index 100% rename from iroha-android-sample/src/main/res/drawable-xhdpi/ic_logo.png rename to app/src/main/res/drawable-xhdpi/ic_logo.png diff --git a/iroha-android-sample/src/main/res/drawable-xhdpi/iroha_logo_big.png b/app/src/main/res/drawable-xhdpi/iroha_logo_big.png similarity index 100% rename from iroha-android-sample/src/main/res/drawable-xhdpi/iroha_logo_big.png rename to app/src/main/res/drawable-xhdpi/iroha_logo_big.png diff --git a/iroha-android-sample/src/main/res/drawable-xhdpi/qr.png b/app/src/main/res/drawable-xhdpi/qr.png similarity index 100% rename from iroha-android-sample/src/main/res/drawable-xhdpi/qr.png rename to app/src/main/res/drawable-xhdpi/qr.png diff --git a/iroha-android-sample/src/main/res/drawable-xxhdpi/background.png b/app/src/main/res/drawable-xxhdpi/background.png similarity index 100% rename from iroha-android-sample/src/main/res/drawable-xxhdpi/background.png rename to app/src/main/res/drawable-xxhdpi/background.png diff --git a/iroha-android-sample/src/main/res/drawable-xxhdpi/ic_logo.png b/app/src/main/res/drawable-xxhdpi/ic_logo.png similarity index 100% rename from iroha-android-sample/src/main/res/drawable-xxhdpi/ic_logo.png rename to app/src/main/res/drawable-xxhdpi/ic_logo.png diff --git a/iroha-android-sample/src/main/res/drawable-xxhdpi/iroha_logo_big.png b/app/src/main/res/drawable-xxhdpi/iroha_logo_big.png similarity index 100% rename from iroha-android-sample/src/main/res/drawable-xxhdpi/iroha_logo_big.png rename to app/src/main/res/drawable-xxhdpi/iroha_logo_big.png diff --git a/iroha-android-sample/src/main/res/drawable-xxhdpi/qr.png b/app/src/main/res/drawable-xxhdpi/qr.png similarity index 100% rename from iroha-android-sample/src/main/res/drawable-xxhdpi/qr.png rename to app/src/main/res/drawable-xxhdpi/qr.png diff --git a/iroha-android-sample/src/main/res/drawable-xxxhdpi/background.png b/app/src/main/res/drawable-xxxhdpi/background.png similarity index 100% rename from iroha-android-sample/src/main/res/drawable-xxxhdpi/background.png rename to app/src/main/res/drawable-xxxhdpi/background.png diff --git a/iroha-android-sample/src/main/res/drawable-xxxhdpi/ic_logo.png b/app/src/main/res/drawable-xxxhdpi/ic_logo.png similarity index 100% rename from iroha-android-sample/src/main/res/drawable-xxxhdpi/ic_logo.png rename to app/src/main/res/drawable-xxxhdpi/ic_logo.png diff --git a/iroha-android-sample/src/main/res/drawable-xxxhdpi/iroha_logo_big.png b/app/src/main/res/drawable-xxxhdpi/iroha_logo_big.png similarity index 100% rename from iroha-android-sample/src/main/res/drawable-xxxhdpi/iroha_logo_big.png rename to app/src/main/res/drawable-xxxhdpi/iroha_logo_big.png diff --git a/iroha-android-sample/src/main/res/drawable-xxxhdpi/qr.png b/app/src/main/res/drawable-xxxhdpi/qr.png similarity index 100% rename from iroha-android-sample/src/main/res/drawable-xxxhdpi/qr.png rename to app/src/main/res/drawable-xxxhdpi/qr.png diff --git a/app/src/main/res/drawable/bg_splash.xml b/app/src/main/res/drawable/bg_splash.xml new file mode 100644 index 0000000..c696a17 --- /dev/null +++ b/app/src/main/res/drawable/bg_splash.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + diff --git a/iroha-android-sample/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml similarity index 100% rename from iroha-android-sample/src/main/res/drawable/ic_launcher_background.xml rename to app/src/main/res/drawable/ic_launcher_background.xml diff --git a/iroha-android-sample/src/main/res/drawable/ic_logout.xml b/app/src/main/res/drawable/ic_logout.xml similarity index 100% rename from iroha-android-sample/src/main/res/drawable/ic_logout.xml rename to app/src/main/res/drawable/ic_logout.xml diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..a69cc56 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,119 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_signup.xml b/app/src/main/res/layout/activity_signup.xml new file mode 100644 index 0000000..ac8c2fa --- /dev/null +++ b/app/src/main/res/layout/activity_signup.xml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + +