├── .gitignore ├── Dangerfile ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── app ├── build.gradle ├── flavors.gradle ├── proguard-rules.pro └── src │ ├── debug │ ├── AndroidManifest.xml │ └── kotlin │ │ └── jp │ │ └── wasabeef │ │ ├── DebugApp.kt │ │ └── module │ │ └── DebugNetworkModule.kt │ ├── main │ ├── AndroidManifest.xml │ ├── kotlin │ │ └── jp │ │ │ └── wasabeef │ │ │ ├── App.kt │ │ │ ├── data │ │ │ ├── local │ │ │ │ ├── ApodOfNasa.kt │ │ │ │ └── room │ │ │ │ │ ├── AppDatabase.kt │ │ │ │ │ ├── DeviceDao.kt │ │ │ │ │ └── DeviceEntity.kt │ │ │ ├── remote │ │ │ │ ├── PlanetDataSource.kt │ │ │ │ └── PlanetService.kt │ │ │ └── repository │ │ │ │ ├── Device.kt │ │ │ │ ├── DeviceRepository.kt │ │ │ │ ├── Planet.kt │ │ │ │ └── PlanetRepository.kt │ │ │ ├── di │ │ │ ├── ActivityBuilder.kt │ │ │ ├── AppComponent.kt │ │ │ ├── AppModule.kt │ │ │ ├── DatabaseModule.kt │ │ │ ├── MainModule.kt │ │ │ ├── NetworkModule.kt │ │ │ ├── Qualifiers.kt │ │ │ ├── RepositoryModule.kt │ │ │ ├── ViewModelFactory.kt │ │ │ ├── ViewModelKey.kt │ │ │ └── ViewModelModule.kt │ │ │ ├── ui │ │ │ ├── DeviceViewModel.kt │ │ │ ├── component │ │ │ │ ├── activity │ │ │ │ │ └── BaseActivity.kt │ │ │ │ ├── adapter │ │ │ │ │ └── .gitkeep │ │ │ │ ├── binding │ │ │ │ │ ├── ImageBindingAdapters.kt │ │ │ │ │ └── TextBindingAdapters.kt │ │ │ │ ├── callback │ │ │ │ │ └── .gitkeep │ │ │ │ ├── notification │ │ │ │ │ └── .gitkeep │ │ │ │ ├── service │ │ │ │ │ └── .gitkeep │ │ │ │ └── widget │ │ │ │ │ └── ForegroundImageView.kt │ │ │ └── main │ │ │ │ ├── MainActivity.kt │ │ │ │ ├── MainViewModel.kt │ │ │ │ ├── home │ │ │ │ ├── HomeFragment.kt │ │ │ │ └── HomeViewModel.kt │ │ │ │ ├── info │ │ │ │ └── InfoFragment.kt │ │ │ │ └── mypage │ │ │ │ └── MyPageFragment.kt │ │ │ └── util │ │ │ ├── AppError.kt │ │ │ ├── AppInjection.kt │ │ │ ├── ApplicationJsonAdapterFactory.kt │ │ │ ├── Display.kt │ │ │ ├── ErrorHandler.kt │ │ │ ├── GlideModule.kt │ │ │ ├── InstantAdapter.kt │ │ │ ├── Memory.kt │ │ │ ├── Result.kt │ │ │ ├── SingleLiveEvent.kt │ │ │ └── ext │ │ │ ├── ActivityExt.kt │ │ │ ├── FlowableExt.kt │ │ │ ├── StreamObserverExt.kt │ │ │ └── ViewExt.kt │ └── res │ │ ├── color │ │ └── selector_bottom_nav_icon.xml │ │ ├── drawable-xxhdpi │ │ ├── ic_home.webp │ │ ├── ic_info.webp │ │ └── ic_my_page.webp │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── fragment_home.xml │ │ ├── fragment_info.xml │ │ └── fragment_my_page.xml │ │ ├── menu │ │ └── bottom_nav_main.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.webp │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.webp │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.webp │ │ └── ic_launcher_round.png │ │ ├── values-v21 │ │ ├── styles.xml │ │ └── themes.xml │ │ └── values │ │ ├── attrs.xml │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ ├── styles.xml │ │ └── themes.xml │ └── test │ ├── AndroidManifest.xml │ └── resources │ └── robolectric.properties ├── build.gradle ├── fastlane ├── Appfile └── Fastfile ├── gradle.properties ├── gradle ├── android-lint-config.xml ├── findbugs-android-exclude.xml ├── findbugs.gradle ├── jacoco.gradle ├── ktlint.gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── keystore ├── debug.gradle └── debug.keystore └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_store 2 | # Built application files 3 | *.apk 4 | *.ap_ 5 | 6 | # Files for the ART/Dalvik VM 7 | *.dex 8 | 9 | # Java class files 10 | *.class 11 | 12 | # Generated files 13 | bin/ 14 | gen/ 15 | out/ 16 | 17 | # Gradle files 18 | .gradle/ 19 | build/ 20 | 21 | # Local configuration file (sdk path, etc) 22 | local.properties 23 | 24 | # Log Files 25 | *.log 26 | 27 | # Android Studio Navigation editor temp files 28 | .navigation/ 29 | 30 | # Android Studio captures folder 31 | captures/ 32 | 33 | # Intellij 34 | *.iml 35 | .idea/ 36 | projectFilesBackup/ 37 | 38 | # External native build folder generated in Android Studio 2.2 and later 39 | .externalNativeBuild 40 | 41 | # Fastlane 42 | fastlane/README.md 43 | fastlane/report.xml 44 | -------------------------------------------------------------------------------- /Dangerfile: -------------------------------------------------------------------------------- 1 | # Sometimes it's a README fix, or something like that - which isn't relevant for 2 | # including in a project's CHANGELOG for example 3 | declared_trivial = github.pr_title.include? "#trivial" 4 | 5 | # Make it more obvious that a PR is a work in progress and shouldn't be merged yet 6 | warn("Work in Progress") if github.pr_title.include? "WIP" 7 | 8 | # Warn when there is a big PR 9 | warn("Big PR > 1000") if git.lines_of_code > 1000 10 | 11 | # Reviewers 12 | has_assignee = github.pr_json["assignee"] != nil 13 | warn("No Assign", sticky: false) unless has_assignee 14 | 15 | # Testing 16 | # @TODO I'll think this later. 17 | # is_source_changed = !git.modified_files.grep(/^main\/kotlin/).empty? 18 | # is_test_changed = !git.modified_files.grep(/^test\/kotlin/).empty? 19 | #if is_source_changed && !is_test_changed 20 | # warn('Must update to tests') 21 | #end 22 | 23 | # Android Lint 24 | android_lint.gradle_task = "app:lintDevelopRelease" 25 | android_lint.report_file = "app/build/reports/lint-results-developRelease.xml" 26 | android_lint.filtering = true 27 | android_lint.lint(inline_mode: true) 28 | 29 | # Findbugs 30 | findbugs.gradle_module = "app" 31 | findbugs.gradle_task = "app:findbugs" 32 | findbugs.report_file = "app/build/reports/findbugs/findbugs.xml" 33 | findbugs.report 34 | 35 | # checkstyle 36 | github.dismiss_out_of_range_messages 37 | checkstyle_format.base_path = Dir.pwd 38 | checkstyle_format.report "app/build/reports/ktlint/ktlint-checkstyle-report.xml" 39 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | source "https://rubygems.org" 3 | 4 | git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } 5 | 6 | # gem "rails" 7 | gem "danger" 8 | gem "danger-android_lint" 9 | gem "danger-findbugs" 10 | gem "danger-checkstyle_format" 11 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | addressable (2.5.2) 5 | public_suffix (>= 2.0.2, < 4.0) 6 | ansi (1.5.0) 7 | ast (2.3.0) 8 | claide (1.0.2) 9 | claide-plugins (0.9.2) 10 | cork 11 | nap 12 | open4 (~> 1.3) 13 | colored2 (3.1.2) 14 | cork (0.3.0) 15 | colored2 (~> 3.1) 16 | danger (5.5.5) 17 | claide (~> 1.0) 18 | claide-plugins (>= 0.9.2) 19 | colored2 (~> 3.1) 20 | cork (~> 0.1) 21 | faraday (~> 0.9) 22 | faraday-http-cache (~> 1.0) 23 | git (~> 1) 24 | kramdown (~> 1.5) 25 | no_proxy_fix 26 | octokit (~> 4.7) 27 | terminal-table (~> 1) 28 | danger-android_lint (0.0.5) 29 | danger-plugin-api (~> 1.0) 30 | oga 31 | danger-checkstyle_format (0.1.0) 32 | danger-plugin-api (~> 1.0) 33 | ox (~> 2.0) 34 | danger-findbugs (0.0.5) 35 | danger-plugin-api (~> 1.0) 36 | oga (~> 2.10) 37 | danger-plugin-api (1.0.0) 38 | danger (> 2.0) 39 | faraday (0.13.1) 40 | multipart-post (>= 1.2, < 3) 41 | faraday-http-cache (1.3.1) 42 | faraday (~> 0.8) 43 | git (1.3.0) 44 | kramdown (1.16.2) 45 | multipart-post (2.0.0) 46 | nap (1.1.0) 47 | no_proxy_fix (0.1.2) 48 | octokit (4.7.0) 49 | sawyer (~> 0.8.0, >= 0.5.3) 50 | oga (2.11) 51 | ast 52 | ruby-ll (~> 2.1) 53 | open4 (1.3.4) 54 | ox (2.8.2) 55 | public_suffix (3.0.1) 56 | ruby-ll (2.1.2) 57 | ansi 58 | ast 59 | sawyer (0.8.1) 60 | addressable (>= 2.3.5, < 2.6) 61 | faraday (~> 0.8, < 1.0) 62 | terminal-table (1.8.0) 63 | unicode-display_width (~> 1.1, >= 1.1.1) 64 | unicode-display_width (1.3.0) 65 | 66 | PLATFORMS 67 | ruby 68 | 69 | DEPENDENCIES 70 | danger 71 | danger-android_lint 72 | danger-checkstyle_format 73 | danger-findbugs 74 | 75 | BUNDLED WITH 76 | 1.16.0 77 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Android 2 | 3 | ## Getting Started 4 | 5 | ### Major libraries 6 | 7 | - Kotlin 8 | - Android Architecture Components (ViewModel, LiveData, Room) 9 | - Dagger2 10 | - Retrofit 11 | - RxJava 12 | - Moshi and Kotshi 13 | - Glide 14 | 15 | ### Naming rules 16 | 17 | #### XMLs 18 | http://standards.mediarain.com/android/java 19 | 20 | 21 | 22 | 23 | 24 | #### Colors 25 | 26 | Examples.. 27 | ```xml 28 | #FFFFFF 29 | #33FFFFFF 30 | #80000000 31 | #555555 32 | #6447FF 33 | ``` 34 | http://www.htmlcsscolor.com/ 35 | 36 | ## Update gem's versions for Danger 37 | 38 | ``` 39 | $ brew update 40 | $ brew install rbenv ruby-build 41 | $ rbenv install -l 42 | $ rbenv install {targetVerion} 43 | $ rbenv global {targetVersion} 44 | $ rbenv exec gem install bundler 45 | $ rbenv exec bundle update 46 | ``` 47 | 48 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'com.google.protobuf' 3 | apply plugin: 'kotlin-android' 4 | apply plugin: 'kotlin-android-extensions' 5 | apply plugin: 'kotlin-kapt' 6 | apply from: '../gradle/ktlint.gradle' 7 | apply from: '../gradle/findbugs.gradle' 8 | apply from: '../gradle/jacoco.gradle' 9 | 10 | android { 11 | compileSdkVersion COMPILE_SDK_VERSION as int 12 | buildToolsVersion BUILD_TOOLS_VERSION 13 | 14 | defaultConfig { 15 | applicationId PACKAGE_NAME 16 | minSdkVersion MIN_SDK_VERSION as int 17 | targetSdkVersion TARGET_SDK_VERSION as int 18 | versionCode VERSION_CODE as int 19 | versionName VERSION_NAME 20 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 21 | multiDexEnabled true 22 | } 23 | 24 | // SigningConfigs 25 | apply from: '../keystore/debug.gradle', to: android 26 | 27 | buildTypes { 28 | debug { 29 | debuggable true 30 | zipAlignEnabled true 31 | testCoverageEnabled false 32 | applicationIdSuffix ".debug" 33 | signingConfig signingConfigs.debug 34 | ext.enableCrashlytics = false 35 | ext.alwaysUpdateBuildId = false 36 | } 37 | release { 38 | debuggable false 39 | zipAlignEnabled true 40 | minifyEnabled true 41 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 42 | shrinkResources true 43 | // FIXME 44 | signingConfig signingConfigs.debug 45 | ext.betaDistributionGroupAliases = "developers" 46 | ext.betaDistributionReleaseNotes = "git log --oneline".execute().text.split('\n')[0] 47 | } 48 | } 49 | 50 | // Variable environment for productFlavors 51 | apply from: 'flavors.gradle' 52 | 53 | dataBinding { 54 | enabled = true 55 | } 56 | 57 | compileOptions { 58 | sourceCompatibility JavaVersion.VERSION_1_8 59 | targetCompatibility JavaVersion.VERSION_1_8 60 | } 61 | 62 | sourceSets { 63 | main.java.srcDirs += 'src/main/kotlin' 64 | test.java.srcDirs += ['src/test/kotlin', 'src/debug/kotlin'] 65 | } 66 | 67 | lintOptions { 68 | lintConfig file('../gradle/android-lint-config.xml') 69 | } 70 | } 71 | 72 | dependencies { 73 | // Multidex 74 | implementation 'com.android.support:multidex:1.0.3' 75 | implementation 'com.android.support:multidex-instrumentation:1.0.3' 76 | 77 | // Google 78 | implementation "com.android.support:support-compat:$SUPPORT_PACKAGE_VERSION" 79 | implementation "com.android.support:support-core-utils:$SUPPORT_PACKAGE_VERSION" 80 | implementation "com.android.support:support-core-ui:$SUPPORT_PACKAGE_VERSION" 81 | implementation "com.android.support:support-fragment:$SUPPORT_PACKAGE_VERSION" 82 | implementation "com.android.support:appcompat-v7:$SUPPORT_PACKAGE_VERSION" 83 | implementation "com.android.support:design:$SUPPORT_PACKAGE_VERSION" 84 | implementation "com.android.support:customtabs:$SUPPORT_PACKAGE_VERSION" 85 | implementation "com.android.support:cardview-v7:$SUPPORT_PACKAGE_VERSION" 86 | implementation "com.android.support:recyclerview-v7:$SUPPORT_PACKAGE_VERSION" 87 | implementation "com.android.support:support-annotations:$SUPPORT_PACKAGE_VERSION" 88 | implementation "com.google.android.gms:play-services-base:$GOOGLE_SERVICE_VERSION" 89 | implementation 'com.android.support.constraint:constraint-layout:1.1.0-beta5' 90 | implementation 'androidx.core:core-ktx:0.2' 91 | implementation 'android.arch.lifecycle:extensions:1.1.0' 92 | kapt 'android.arch.lifecycle:compiler:1.1.0' 93 | implementation 'android.arch.persistence.room:runtime:1.1.0-alpha3' 94 | kapt 'android.arch.persistence.room:compiler:1.1.0-alpha3' 95 | implementation 'android.arch.paging:runtime:1.0.0-alpha6' 96 | implementation 'android.arch.persistence.room:rxjava2:1.0.0' 97 | implementation 'android.arch.lifecycle:reactivestreams:1.1.0' 98 | // FRP 99 | implementation 'io.reactivex.rxjava2:rxjava:2.1.10' 100 | implementation 'io.reactivex.rxjava2:rxandroid:2.0.2' 101 | implementation 'io.reactivex.rxjava2:rxkotlin:2.2.0' 102 | 103 | // OkHttp, Retrofit 104 | implementation 'com.squareup.okhttp3:okhttp:3.10.0' 105 | implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0' 106 | implementation 'com.squareup.retrofit2:retrofit:2.3.0' 107 | implementation 'com.squareup.retrofit2:adapter-rxjava2:2.3.0' 108 | // Moshi 109 | implementation 'com.squareup.moshi:moshi:1.5.0' 110 | implementation 'com.squareup.moshi:moshi-kotlin:1.5.0' 111 | implementation 'com.squareup.retrofit2:converter-moshi:2.3.0' 112 | implementation 'se.ansman.kotshi:api:1.0.2' 113 | kapt 'se.ansman.kotshi:compiler:1.0.2' 114 | // gRPC 115 | implementation "io.grpc:grpc-okhttp:$GRPC_VERSION" 116 | implementation "io.grpc:grpc-protobuf:$GRPC_VERSION" 117 | implementation "io.grpc:grpc-stub:$GRPC_VERSION" 118 | implementation 'javax.annotation:javax.annotation-api:1.3.2' 119 | // Protobuf 120 | implementation 'com.google.protobuf:protobuf-java:3.5.1' 121 | 122 | // Timber 123 | implementation 'com.jakewharton.timber:timber:4.6.1' 124 | 125 | // ThreeTenABP 126 | implementation 'com.jakewharton.threetenabp:threetenabp:1.0.5' 127 | 128 | // Kotlin 129 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jre8:$KOTLIN_VERSION" 130 | 131 | // Dagger2 132 | implementation "com.google.dagger:dagger:$DAGGER_VERSION" 133 | implementation "com.google.dagger:dagger-android:$DAGGER_VERSION" 134 | implementation "com.google.dagger:dagger-android-support:$DAGGER_VERSION" 135 | kapt "com.google.dagger:dagger-compiler:$DAGGER_VERSION" 136 | kapt "com.google.dagger:dagger-android-processor:$DAGGER_VERSION" 137 | 138 | // Glide 139 | implementation 'com.github.bumptech.glide:glide:4.6.1' 140 | kapt 'com.github.bumptech.glide:compiler:4.6.1' 141 | implementation 'com.github.bumptech.glide:okhttp3-integration:4.6.1' 142 | implementation 'jp.wasabeef:glide-transformations:3.1.1' 143 | 144 | // ReactiveLiveData 145 | implementation 'com.github.musichin.reactivelivedata:reactivelivedata:0.13.0' 146 | } 147 | 148 | // For Debugging 149 | dependencies { 150 | debugImplementation "com.facebook.stetho:stetho:$STETHO_VERSION" 151 | debugImplementation "com.facebook.stetho:stetho-okhttp3:$STETHO_VERSION" 152 | debugImplementation "com.facebook.stetho:stetho-timber:$STETHO_VERSION" 153 | } 154 | 155 | // For Testing 156 | dependencies { 157 | testImplementation "junit:junit:$JUNIT_VERSION" 158 | testImplementation 'org.robolectric:robolectric:3.7.1' 159 | testImplementation 'org.mockito:mockito-core:2.15.0' 160 | testImplementation 'org.mockito:mockito-inline:2.15.0' 161 | testImplementation 'com.squareup.okhttp3:mockwebserver:3.10.0' 162 | 163 | testImplementation "com.facebook.stetho:stetho:$STETHO_VERSION" 164 | testImplementation "com.facebook.stetho:stetho-okhttp3:$STETHO_VERSION" 165 | testImplementation "com.facebook.stetho:stetho-timber:$STETHO_VERSION" 166 | } -------------------------------------------------------------------------------- /app/flavors.gradle: -------------------------------------------------------------------------------- 1 | android { 2 | defaultConfig { 3 | buildConfigField("int", "DB_SCHEMA_VERSION", DB_SCHEMA_VERSION) 4 | 5 | buildConfigField "String", "HTTPS", "\"https\"" 6 | buildConfigField "String", "HTTP", "\"http\"" 7 | buildConfigField "int", "PORT_GRPC", "3000" 8 | 9 | // FIXME should delete 10 | buildConfigField "String", "NASA_ENDPOINT", "\"api.nasa.gov\"" 11 | } 12 | 13 | flavorDimensions "develop" 14 | productFlavors { 15 | develop { 16 | dimension "develop" 17 | resValue "string", "app_name", "Wasa Dev" 18 | manifestPlaceholders = [domain: 'wasabeef.jp'] 19 | } 20 | staging { 21 | dimension "develop" 22 | resValue "string", "app_name", "Wasa Stg" 23 | manifestPlaceholders = [domain: 'wasabeef.jp'] 24 | } 25 | production { 26 | resValue "string", "app_name", "Wasa" 27 | manifestPlaceholders = [domain: 'wasabeef.jp'] 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | 23 | -verbose 24 | -dontnote 25 | -useuniqueclassmembernames 26 | -printmapping mapping.txt 27 | 28 | ##### Google ##### 29 | # Databinding 30 | -dontwarn android.databinding.** 31 | # Dagger 32 | -dontwarn com.google.errorprone.annotations.** 33 | ################## 34 | 35 | ##### OkHttp, Retrofit ##### 36 | -dontwarn okhttp3.** 37 | -dontwarn okio.** 38 | -dontwarn javax.annotation.** 39 | -keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase 40 | 41 | # Platform calls Class.forName on types which do not exist on Android to determine platform. 42 | -dontnote retrofit2.Platform 43 | # Platform used when running on Java 8 VMs. Will not be used at runtime. 44 | -dontwarn retrofit2.Platform$Java8 45 | # Retain generic type information for use by reflection by converters and adapters. 46 | -keepattributes Signature 47 | # Retain declared checked exceptions for use by a Proxy instance. 48 | -keepattributes Exceptions 49 | ################# 50 | 51 | ##### Moshi for JSON ##### 52 | -dontwarn okio.** 53 | -dontwarn javax.annotation.** 54 | -keepclasseswithmembers class * { 55 | @com.squareup.moshi.* ; 56 | } 57 | -keep @com.squareup.moshi.JsonQualifier interface * 58 | 59 | -keepclassmembers class kotlin.Metadata { 60 | public ; 61 | } 62 | ########################### 63 | 64 | ##### gRPC ##### 65 | -dontwarn android.test.** 66 | -dontwarn com.google.common.** 67 | -dontwarn javax.naming.** 68 | -dontwarn okio.** 69 | -dontwarn org.junit.** 70 | -dontwarn org.mockito.** 71 | -dontwarn sun.reflect.** 72 | # Ignores: can't find referenced class javax.lang.model.element.Modifier 73 | -dontwarn com.google.errorprone.annotations.** 74 | ################## 75 | 76 | ##### Protobuf ##### 77 | -keep class * extends com.google.protobuf.** { *; } 78 | -dontwarn com.google.protobuf.** 79 | #################### 80 | 81 | ##### Crashlytics ##### 82 | -keepattributes *Annotation* 83 | -keepattributes SourceFile,LineNumberTable 84 | -keep public class * extends java.lang.Exception 85 | -keep class com.crashlytics.** { *; } 86 | -dontwarn com.crashlytics.** 87 | ####################### 88 | 89 | ##### Glide ##### 90 | -keep public class * implements com.bumptech.glide.module.GlideModule 91 | -keep public class * extends com.bumptech.glide.module.AppGlideModule 92 | -keep class com.bumptech.glide.GeneratedAppGlideModuleImpl 93 | -keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** { 94 | **[] $VALUES; 95 | public *; 96 | } 97 | ################# 98 | 99 | ##### Wasabeef ##### 100 | # Protobuf 101 | -keep class jp.wasabeef.data.proto.** { *; } 102 | -keep class jp.wasabeef.data.grpc.** { *; } 103 | ################## -------------------------------------------------------------------------------- /app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/debug/kotlin/jp/wasabeef/DebugApp.kt: -------------------------------------------------------------------------------- 1 | package jp.wasabeef 2 | 3 | import jp.wasabeef.module.DebugNetworkModule 4 | import jp.wasabeef.di.NetworkModule 5 | import com.facebook.stetho.Stetho 6 | import com.facebook.stetho.timber.StethoTree 7 | import timber.log.Timber 8 | 9 | /** 10 | * Created by Wasabeef on 2017/10/17. 11 | */ 12 | class DebugApp : App() { 13 | 14 | override fun onCreate() { 15 | super.onCreate() 16 | initStetho() 17 | } 18 | 19 | override fun networkModule(): NetworkModule = DebugNetworkModule() 20 | 21 | override fun initTimber() { 22 | Timber.plant(Timber.DebugTree()) 23 | Timber.plant(StethoTree()) 24 | } 25 | 26 | fun initStetho() = Stetho.initializeWithDefaults(this) 27 | } -------------------------------------------------------------------------------- /app/src/debug/kotlin/jp/wasabeef/module/DebugNetworkModule.kt: -------------------------------------------------------------------------------- 1 | package jp.wasabeef.module 2 | 3 | import jp.wasabeef.App 4 | import jp.wasabeef.di.NetworkModule 5 | import com.facebook.stetho.okhttp3.StethoInterceptor 6 | import dagger.Module 7 | import okhttp3.OkHttpClient 8 | import okhttp3.logging.HttpLoggingInterceptor 9 | 10 | /** 11 | * Created by Wasabeef on 2017/10/19. 12 | */ 13 | @Module 14 | class DebugNetworkModule : NetworkModule() { 15 | 16 | override fun buildOkHttpClient(app: App): OkHttpClient = 17 | super.buildOkHttpClient(app).newBuilder() 18 | .addInterceptor(HttpLoggingInterceptor().apply { 19 | level = HttpLoggingInterceptor.Level.HEADERS 20 | }) 21 | .addNetworkInterceptor(StethoInterceptor()) 22 | .build() 23 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 17 | 18 | 22 | 23 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /app/src/main/kotlin/jp/wasabeef/App.kt: -------------------------------------------------------------------------------- 1 | package jp.wasabeef 2 | 3 | import android.content.Context 4 | import android.support.multidex.MultiDex 5 | import com.jakewharton.threetenabp.AndroidThreeTen 6 | import dagger.android.AndroidInjector 7 | import dagger.android.DaggerApplication 8 | import jp.wasabeef.di.DaggerAppComponent 9 | import jp.wasabeef.di.DatabaseModule 10 | import jp.wasabeef.di.NetworkModule 11 | import timber.log.Timber 12 | 13 | /** 14 | * Created by Wasabeef on 2017/09/27. 15 | */ 16 | open class App : DaggerApplication() { 17 | 18 | lateinit var androidInjector: AndroidInjector 19 | 20 | override fun attachBaseContext(base: Context?) { 21 | super.attachBaseContext(base) 22 | MultiDex.install(this) 23 | androidInjector = DaggerAppComponent.builder() 24 | .application(this) 25 | .database(databaseModule()) 26 | .network(networkModule()) 27 | .build() 28 | } 29 | 30 | override fun onCreate() { 31 | super.onCreate() 32 | initTimber() 33 | initThreeTenABP() 34 | } 35 | 36 | public override fun applicationInjector(): AndroidInjector = androidInjector 37 | 38 | protected open fun databaseModule(): DatabaseModule = DatabaseModule() 39 | 40 | protected open fun networkModule(): NetworkModule = NetworkModule() 41 | 42 | protected open fun initTimber() = Timber.plant() 43 | 44 | protected open fun initThreeTenABP() = AndroidThreeTen.init(this) 45 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/jp/wasabeef/data/local/ApodOfNasa.kt: -------------------------------------------------------------------------------- 1 | package jp.wasabeef.data.local 2 | 3 | import com.squareup.moshi.Json 4 | import se.ansman.kotshi.JsonSerializable 5 | 6 | /** 7 | * Created by Wasabeef on 2018/03/05. 8 | */ 9 | @JsonSerializable 10 | data class ApodOfNasa( 11 | val date: String?, 12 | val explanation: String?, 13 | @Json(name = "media_type") 14 | val mediaType: String?, 15 | @Json(name = "service_version") 16 | val version: String?, 17 | val title: String?, 18 | val url: String? 19 | ) 20 | -------------------------------------------------------------------------------- /app/src/main/kotlin/jp/wasabeef/data/local/room/AppDatabase.kt: -------------------------------------------------------------------------------- 1 | package jp.wasabeef.data.local.room 2 | 3 | import android.arch.persistence.room.Database 4 | import android.arch.persistence.room.RoomDatabase 5 | import jp.wasabeef.BuildConfig 6 | 7 | /** 8 | * Created by Wasabeef on 2018/03/09. 9 | */ 10 | @Database( 11 | entities = [DeviceEntity::class], 12 | version = BuildConfig.DB_SCHEMA_VERSION, 13 | // TODO 14 | exportSchema = false 15 | ) 16 | abstract class AppDatabase : RoomDatabase() { 17 | abstract val deviceDao: DeviceDao 18 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/jp/wasabeef/data/local/room/DeviceDao.kt: -------------------------------------------------------------------------------- 1 | package jp.wasabeef.data.local.room 2 | 3 | import android.arch.persistence.room.Dao 4 | import android.arch.persistence.room.Insert 5 | import android.arch.persistence.room.OnConflictStrategy 6 | import android.arch.persistence.room.Query 7 | import io.reactivex.Single 8 | 9 | /** 10 | * Created by Wasabeef on 2018/03/09. 11 | */ 12 | @Dao 13 | interface DeviceDao { 14 | @Insert(onConflict = OnConflictStrategy.REPLACE) 15 | fun save(device: DeviceEntity): Long 16 | 17 | @Query("SELECT * FROM Device") 18 | fun get(): Single 19 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/jp/wasabeef/data/local/room/DeviceEntity.kt: -------------------------------------------------------------------------------- 1 | package jp.wasabeef.data.local.room 2 | 3 | import android.arch.persistence.room.ColumnInfo 4 | import android.arch.persistence.room.Entity 5 | import android.arch.persistence.room.PrimaryKey 6 | import android.os.Build 7 | import jp.wasabeef.BuildConfig 8 | 9 | /** 10 | * Created by Wasabeef on 2018/03/09. 11 | */ 12 | @Entity(tableName = "device") 13 | data class DeviceEntity constructor( 14 | @PrimaryKey 15 | @ColumnInfo(name = "device_id") 16 | val deviceId: String, 17 | 18 | val model: String = "${Build.BRAND} ${Build.MODEL}", 19 | 20 | val os: String = "android", 21 | 22 | @ColumnInfo(name = "os_version") 23 | val osVersion: String = Build.VERSION.RELEASE, 24 | 25 | @ColumnInfo(name = "app_version") 26 | val appVersion: String = BuildConfig.VERSION_NAME, 27 | 28 | @ColumnInfo(name = "user_agent") 29 | val userAgent: String, 30 | 31 | @ColumnInfo(name = "source_ip") 32 | val sourceIp: String? = null 33 | ) 34 | -------------------------------------------------------------------------------- /app/src/main/kotlin/jp/wasabeef/data/remote/PlanetDataSource.kt: -------------------------------------------------------------------------------- 1 | package jp.wasabeef.data.remote 2 | 3 | import javax.inject.Inject 4 | 5 | /** 6 | * Created by Wasabeef on 2018/03/05. 7 | */ 8 | class PlanetDataSource @Inject constructor(private val planetService: PlanetService) { 9 | fun getInfo() = planetService.getInfo("DEMO_KEY") 10 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/jp/wasabeef/data/remote/PlanetService.kt: -------------------------------------------------------------------------------- 1 | package jp.wasabeef.data.remote 2 | 3 | import io.reactivex.Flowable 4 | import jp.wasabeef.data.local.ApodOfNasa 5 | import retrofit2.http.GET 6 | import retrofit2.http.Query 7 | 8 | /** 9 | * Created by Wasabeef on 2018/03/05. 10 | */ 11 | interface PlanetService { 12 | @GET("/planetary/apod") 13 | fun getInfo(@Query("api_key") key: String): Flowable 14 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/jp/wasabeef/data/repository/Device.kt: -------------------------------------------------------------------------------- 1 | package jp.wasabeef.data.repository 2 | 3 | import io.reactivex.Completable 4 | import io.reactivex.Single 5 | import jp.wasabeef.data.local.room.DeviceEntity 6 | 7 | /** 8 | * Created by Wasabeef on 2018/03/09. 9 | */ 10 | interface Device { 11 | fun save(device: DeviceEntity): Completable 12 | fun get(): Single 13 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/jp/wasabeef/data/repository/DeviceRepository.kt: -------------------------------------------------------------------------------- 1 | package jp.wasabeef.data.repository 2 | 3 | import io.reactivex.Completable 4 | import io.reactivex.Single 5 | import jp.wasabeef.data.local.room.AppDatabase 6 | import jp.wasabeef.data.local.room.DeviceDao 7 | import jp.wasabeef.data.local.room.DeviceEntity 8 | import javax.inject.Inject 9 | import javax.inject.Singleton 10 | 11 | /** 12 | * Created by Wasabeef on 2018/03/09. 13 | */ 14 | @Singleton 15 | class DeviceRepository @Inject constructor( 16 | private val database: AppDatabase, 17 | private val dao: DeviceDao 18 | ) : Device { 19 | override fun save(device: DeviceEntity): Completable { 20 | 21 | return Completable.create({ 22 | database.runInTransaction { dao.save(device) } 23 | it.onComplete() 24 | }) 25 | } 26 | 27 | override fun get(): Single = dao.get() 28 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/jp/wasabeef/data/repository/Planet.kt: -------------------------------------------------------------------------------- 1 | package jp.wasabeef.data.repository 2 | 3 | import io.reactivex.Flowable 4 | import jp.wasabeef.data.local.ApodOfNasa 5 | 6 | /** 7 | * Created by Wasabeef on 2018/03/05. 8 | */ 9 | interface Planet { 10 | fun getInfo(): Flowable 11 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/jp/wasabeef/data/repository/PlanetRepository.kt: -------------------------------------------------------------------------------- 1 | package jp.wasabeef.data.repository 2 | 3 | import io.reactivex.Flowable 4 | import jp.wasabeef.data.local.ApodOfNasa 5 | import jp.wasabeef.data.remote.PlanetDataSource 6 | import javax.inject.Inject 7 | import javax.inject.Singleton 8 | 9 | /** 10 | * Created by Wasabeef on 2018/03/05. 11 | */ 12 | @Singleton 13 | class PlanetRepository @Inject constructor( 14 | private val planetDataSource: PlanetDataSource 15 | ) : Planet { 16 | 17 | override fun getInfo(): Flowable { 18 | return planetDataSource.getInfo() 19 | } 20 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/jp/wasabeef/di/ActivityBuilder.kt: -------------------------------------------------------------------------------- 1 | package jp.wasabeef.di 2 | 3 | import dagger.Module 4 | import dagger.android.ContributesAndroidInjector 5 | import jp.wasabeef.ui.main.MainActivity 6 | 7 | /** 8 | * Created by Wasabeef on 2017/10/16. 9 | */ 10 | @Module 11 | internal abstract class ActivityBuilder { 12 | @ContributesAndroidInjector(modules = [MainModule::class]) 13 | internal abstract fun contributeMainInjector(): MainActivity 14 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/jp/wasabeef/di/AppComponent.kt: -------------------------------------------------------------------------------- 1 | package jp.wasabeef.di 2 | 3 | import dagger.BindsInstance 4 | import dagger.Component 5 | import dagger.android.AndroidInjector 6 | import dagger.android.support.AndroidSupportInjectionModule 7 | import jp.wasabeef.App 8 | import jp.wasabeef.util.GlideModule 9 | import javax.inject.Singleton 10 | 11 | /** 12 | * Created by Wasabeef on 2017/10/16. 13 | */ 14 | @Singleton 15 | @Component( 16 | modules = [ 17 | AndroidSupportInjectionModule::class, 18 | ActivityBuilder::class, 19 | AppModule::class 20 | ]) 21 | interface AppComponent : AndroidInjector { 22 | 23 | @Component.Builder 24 | interface Builder { 25 | @BindsInstance 26 | fun application(application: App): Builder 27 | 28 | fun database(database: DatabaseModule): Builder 29 | 30 | fun network(network: NetworkModule): Builder 31 | 32 | fun build(): AppComponent 33 | } 34 | 35 | override fun inject(app: App) 36 | 37 | fun inject(glideModule: GlideModule) 38 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/jp/wasabeef/di/AppModule.kt: -------------------------------------------------------------------------------- 1 | package jp.wasabeef.di 2 | 3 | import dagger.Module 4 | 5 | /** 6 | * Created by Wasabeef on 2017/10/16. 7 | */ 8 | @Module(includes = [DatabaseModule::class, NetworkModule::class, ViewModelModule::class]) 9 | internal object AppModule { 10 | // If you need. 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/kotlin/jp/wasabeef/di/DatabaseModule.kt: -------------------------------------------------------------------------------- 1 | package jp.wasabeef.di 2 | 3 | import android.arch.persistence.room.Room 4 | import dagger.Module 5 | import dagger.Provides 6 | import jp.wasabeef.App 7 | import jp.wasabeef.data.local.room.AppDatabase 8 | import jp.wasabeef.data.local.room.DeviceDao 9 | import javax.inject.Singleton 10 | 11 | /** 12 | * Created by Wasabeef on 2018/03/08. 13 | */ 14 | @Module 15 | class DatabaseModule { 16 | @Provides 17 | @Singleton 18 | fun provideDatabase(app: App): AppDatabase { 19 | return Room.databaseBuilder(app, AppDatabase::class.java, "wasa.db") 20 | .fallbackToDestructiveMigration() 21 | .build() 22 | } 23 | 24 | @Provides 25 | @Singleton 26 | fun provideDeviceDao(db: AppDatabase): DeviceDao = db.deviceDao 27 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/jp/wasabeef/di/MainModule.kt: -------------------------------------------------------------------------------- 1 | package jp.wasabeef.di 2 | 3 | import dagger.Module 4 | import dagger.android.ContributesAndroidInjector 5 | import jp.wasabeef.ui.main.home.HomeFragment 6 | 7 | /** 8 | * Created by Wasabeef on 2017/10/17. 9 | */ 10 | @Module 11 | internal abstract class MainModule { 12 | @ContributesAndroidInjector 13 | internal abstract fun contributeTopFragmentInjector(): HomeFragment 14 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/jp/wasabeef/di/NetworkModule.kt: -------------------------------------------------------------------------------- 1 | package jp.wasabeef.di 2 | 3 | import com.squareup.moshi.Moshi 4 | import dagger.Module 5 | import dagger.Provides 6 | import jp.wasabeef.App 7 | import jp.wasabeef.BuildConfig.HTTPS 8 | import jp.wasabeef.BuildConfig.NASA_ENDPOINT 9 | import jp.wasabeef.data.remote.PlanetService 10 | import jp.wasabeef.di.Qualifiers.NASA 11 | import jp.wasabeef.util.ApplicationJsonAdapterFactory 12 | import jp.wasabeef.util.InstantAdapter 13 | import jp.wasabeef.util.Memory 14 | import okhttp3.Cache 15 | import okhttp3.OkHttpClient 16 | import retrofit2.Retrofit 17 | import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory 18 | import retrofit2.converter.moshi.MoshiConverterFactory 19 | import java.io.File 20 | import java.util.concurrent.TimeUnit 21 | import javax.inject.Named 22 | import javax.inject.Singleton 23 | 24 | /** 25 | * Created by Wasabeef on 2017/10/16. 26 | */ 27 | @Module 28 | open class NetworkModule { 29 | 30 | open fun buildOkHttpClient(app: App): OkHttpClient = 31 | OkHttpClient.Builder() 32 | .connectTimeout(10L, TimeUnit.SECONDS) 33 | .writeTimeout(10L, TimeUnit.SECONDS) 34 | .readTimeout(30L, TimeUnit.SECONDS) 35 | .cache(Cache(File(app.cacheDir, "OkCache"), 36 | Memory.calcCacheSize(app, .25f))) 37 | .build() 38 | 39 | @Provides 40 | @Singleton 41 | fun provideOkHttpClient(app: App): OkHttpClient = buildOkHttpClient(app) 42 | 43 | @Singleton 44 | @Provides 45 | fun provideMoshi() = Moshi.Builder() 46 | .add(ApplicationJsonAdapterFactory.INSTANCE) 47 | .add(InstantAdapter.INSTANCE) 48 | .build() 49 | 50 | @Provides 51 | @Singleton 52 | @Named(NASA) 53 | fun provideRetrofitForNasa(okHttpClient: OkHttpClient, moshi: Moshi): Retrofit = Retrofit.Builder() 54 | .baseUrl("$HTTPS://$NASA_ENDPOINT") 55 | .client(okHttpClient) 56 | .addConverterFactory(MoshiConverterFactory.create(moshi)) 57 | .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) 58 | .build() 59 | 60 | @Provides 61 | @Singleton 62 | fun providePlanetService(@Named(NASA) retrofit: Retrofit): PlanetService = 63 | retrofit.create(PlanetService::class.java) 64 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/jp/wasabeef/di/Qualifiers.kt: -------------------------------------------------------------------------------- 1 | package jp.wasabeef.di 2 | 3 | /** 4 | * Created by Wasabeef on 2018/03/05. 5 | */ 6 | object Qualifiers { 7 | const val JSON = "for_json" 8 | const val NASA = "for_nasa" 9 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/jp/wasabeef/di/RepositoryModule.kt: -------------------------------------------------------------------------------- 1 | package jp.wasabeef.di 2 | 3 | import dagger.Binds 4 | import dagger.Module 5 | import jp.wasabeef.data.repository.Device 6 | import jp.wasabeef.data.repository.DeviceRepository 7 | import jp.wasabeef.data.repository.Planet 8 | import jp.wasabeef.data.repository.PlanetRepository 9 | 10 | /** 11 | * Created by Wasabeef on 2018/03/05. 12 | */ 13 | @Module 14 | interface RepositoryModule { 15 | @Binds 16 | fun bindPlanetRepository(repository: PlanetRepository): Planet 17 | 18 | @Binds 19 | fun bindDeviceRepository(repository: DeviceRepository): Device 20 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/jp/wasabeef/di/ViewModelFactory.kt: -------------------------------------------------------------------------------- 1 | package jp.wasabeef.di 2 | 3 | import android.arch.lifecycle.ViewModel 4 | import android.arch.lifecycle.ViewModelProvider 5 | import javax.inject.Inject 6 | import javax.inject.Provider 7 | import javax.inject.Singleton 8 | 9 | /** 10 | * Created by Wasabeef on 2018/03/05. 11 | */ 12 | @Singleton 13 | class ViewModelFactory @Inject constructor( 14 | private val creators: Map, 15 | @JvmSuppressWildcards Provider> 16 | ) : ViewModelProvider.Factory { 17 | 18 | @Suppress("unchecked_cast") 19 | override fun create(modelClass: Class): T { 20 | val creator = creators[modelClass as Class] 21 | ?: creators.entries.firstOrNull { (c, _) -> modelClass.isAssignableFrom(c) }?.value 22 | ?: throw IllegalArgumentException("Unknown model class $modelClass") 23 | 24 | return creator.get() as T 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/jp/wasabeef/di/ViewModelKey.kt: -------------------------------------------------------------------------------- 1 | package jp.wasabeef.di 2 | 3 | import android.arch.lifecycle.ViewModel 4 | import dagger.MapKey 5 | import kotlin.reflect.KClass 6 | 7 | /** 8 | * Created by Wasabeef on 2018/03/05. 9 | */ 10 | @Target(allowedTargets = [AnnotationTarget.FUNCTION]) 11 | @Retention(value = AnnotationRetention.RUNTIME) 12 | @MapKey 13 | annotation class ViewModelKey(val value: KClass) -------------------------------------------------------------------------------- /app/src/main/kotlin/jp/wasabeef/di/ViewModelModule.kt: -------------------------------------------------------------------------------- 1 | package jp.wasabeef.di 2 | 3 | import android.arch.lifecycle.ViewModel 4 | import android.arch.lifecycle.ViewModelProvider 5 | import dagger.Binds 6 | import dagger.Module 7 | import dagger.multibindings.IntoMap 8 | import jp.wasabeef.ui.main.home.HomeViewModel 9 | 10 | /** 11 | * Created by Wasabeef on 2018/03/05. 12 | */ 13 | @Module(includes = [RepositoryModule::class]) 14 | interface ViewModelModule { 15 | @Binds 16 | fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory 17 | 18 | // @Binds 19 | // @IntoMap 20 | // @ViewModelKey(DeviceViewModel::class) 21 | // fun bindDeviceViewModel(viewModel: DeviceViewModel): ViewModel 22 | 23 | // @Binds 24 | // @IntoMap 25 | // @ViewModelKey(MainViewModel::class) 26 | // fun bindMainViewModel(viewModel: MainViewModel): ViewModel 27 | 28 | @Binds 29 | @IntoMap 30 | @ViewModelKey(HomeViewModel::class) 31 | fun bindHomeViewModel(viewModel: HomeViewModel): ViewModel 32 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/jp/wasabeef/ui/DeviceViewModel.kt: -------------------------------------------------------------------------------- 1 | package jp.wasabeef.ui 2 | 3 | import android.arch.lifecycle.LiveData 4 | import android.arch.lifecycle.MutableLiveData 5 | import android.arch.lifecycle.ViewModel 6 | import android.webkit.WebView 7 | import io.reactivex.android.schedulers.AndroidSchedulers 8 | import io.reactivex.disposables.CompositeDisposable 9 | import io.reactivex.rxkotlin.addTo 10 | import io.reactivex.rxkotlin.subscribeBy 11 | import io.reactivex.schedulers.Schedulers 12 | import jp.wasabeef.App 13 | import jp.wasabeef.data.local.room.DeviceEntity 14 | import jp.wasabeef.data.repository.DeviceRepository 15 | import jp.wasabeef.util.Result 16 | import jp.wasabeef.util.defaultErrorHandler 17 | import java.util.* 18 | import javax.inject.Inject 19 | 20 | /** 21 | * Created by Wasabeef on 2018/03/10. 22 | */ 23 | class DeviceViewModel @Inject constructor( 24 | private val app: App, 25 | private val repo: DeviceRepository 26 | ) : ViewModel() { 27 | 28 | private val disposable: CompositeDisposable = CompositeDisposable() 29 | 30 | private val _deviceInfo: MutableLiveData> = MutableLiveData() 31 | val deviceInfo: LiveData> = _deviceInfo 32 | 33 | private val deviceId: String by lazy { 34 | UUID.randomUUID().toString() 35 | // AdvertisingIdClient.getAdvertisingIdInfo(app).id 36 | } 37 | 38 | private val userAgent: String by lazy { WebView(app).settings.userAgentString } 39 | 40 | init { 41 | val entity = DeviceEntity(deviceId = deviceId, userAgent = userAgent) 42 | repo.get() 43 | .observeOn(Schedulers.io()) 44 | .subscribeOn(AndroidSchedulers.mainThread()) 45 | .onErrorResumeNext { repo.save(entity).toSingleDefault(entity) } 46 | .subscribeBy( 47 | onError = defaultErrorHandler(), 48 | onSuccess = { _deviceInfo.postValue(Result.success(it)) } 49 | ) 50 | .addTo(disposable) 51 | } 52 | 53 | fun save(device: DeviceEntity) { 54 | repo.save(device) 55 | .observeOn(Schedulers.io()) 56 | .subscribeOn(AndroidSchedulers.mainThread()) 57 | .subscribeBy( 58 | onError = defaultErrorHandler(), 59 | onComplete = { _deviceInfo.postValue(Result.success(device)) } 60 | ).addTo(disposable) 61 | } 62 | 63 | override fun onCleared() { 64 | super.onCleared() 65 | disposable.clear() 66 | } 67 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/jp/wasabeef/ui/component/activity/BaseActivity.kt: -------------------------------------------------------------------------------- 1 | package jp.wasabeef.ui.component.activity 2 | 3 | import android.view.MenuItem 4 | import dagger.android.support.DaggerAppCompatActivity 5 | 6 | abstract class BaseActivity : DaggerAppCompatActivity() { 7 | 8 | override fun onOptionsItemSelected(item: MenuItem?): Boolean { 9 | if (item?.itemId == android.R.id.home) finish() 10 | return super.onOptionsItemSelected(item) 11 | } 12 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/jp/wasabeef/ui/component/adapter/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasabeef/kotlin-mvvm/130aa438e5a665f366c1fbd493a82c9b7b926c8c/app/src/main/kotlin/jp/wasabeef/ui/component/adapter/.gitkeep -------------------------------------------------------------------------------- /app/src/main/kotlin/jp/wasabeef/ui/component/binding/ImageBindingAdapters.kt: -------------------------------------------------------------------------------- 1 | package jp.wasabeef.ui.component.binding 2 | 3 | import android.databinding.BindingAdapter 4 | import android.widget.ImageView 5 | import com.bumptech.glide.request.RequestOptions 6 | import jp.wasabeef.util.GlideApp 7 | 8 | /** 9 | * Created by Wasabeef on 2017/10/24. 10 | */ 11 | object ImageBindingAdapters { 12 | @JvmStatic 13 | @BindingAdapter("image") 14 | fun loadImage(view: ImageView, imagePath: String) { 15 | GlideApp.with(view.context) 16 | .load(imagePath) 17 | .into(view) 18 | } 19 | 20 | @JvmStatic 21 | @BindingAdapter("circle_image") 22 | fun loadCircleImage(view: ImageView, imagePath: String) { 23 | GlideApp.with(view.context) 24 | .load(imagePath) 25 | .apply(RequestOptions.circleCropTransform()) 26 | .into(view) 27 | } 28 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/jp/wasabeef/ui/component/binding/TextBindingAdapters.kt: -------------------------------------------------------------------------------- 1 | package jp.wasabeef.ui.component.binding 2 | 3 | import android.databinding.BindingAdapter 4 | import android.widget.TextView 5 | 6 | /** 7 | * Created by Wasabeef on 2017/10/24. 8 | */ 9 | object TextBindingAdapters { 10 | @JvmStatic 11 | @BindingAdapter("android:text") 12 | fun bindText(view: TextView, value: Int) { 13 | view.text = value.toString() 14 | } 15 | 16 | @JvmStatic 17 | @BindingAdapter("android:text", "android:text_format") 18 | fun bindTextWithFormat(view: TextView, value: Int, format: String) { 19 | view.text = String.format(format, value) 20 | } 21 | 22 | @JvmStatic 23 | @BindingAdapter("android:text", "android:text_format") 24 | fun bindTextWithFormat(view: TextView, value: Double, format: String) { 25 | view.text = String.format(format, value) 26 | } 27 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/jp/wasabeef/ui/component/callback/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasabeef/kotlin-mvvm/130aa438e5a665f366c1fbd493a82c9b7b926c8c/app/src/main/kotlin/jp/wasabeef/ui/component/callback/.gitkeep -------------------------------------------------------------------------------- /app/src/main/kotlin/jp/wasabeef/ui/component/notification/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasabeef/kotlin-mvvm/130aa438e5a665f366c1fbd493a82c9b7b926c8c/app/src/main/kotlin/jp/wasabeef/ui/component/notification/.gitkeep -------------------------------------------------------------------------------- /app/src/main/kotlin/jp/wasabeef/ui/component/service/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasabeef/kotlin-mvvm/130aa438e5a665f366c1fbd493a82c9b7b926c8c/app/src/main/kotlin/jp/wasabeef/ui/component/service/.gitkeep -------------------------------------------------------------------------------- /app/src/main/kotlin/jp/wasabeef/ui/component/widget/ForegroundImageView.kt: -------------------------------------------------------------------------------- 1 | package jp.wasabeef.ui.component.widget 2 | 3 | import android.content.Context 4 | import android.graphics.Canvas 5 | import android.graphics.drawable.Drawable 6 | import android.os.Build 7 | import android.support.v4.content.ContextCompat 8 | import android.support.v7.widget.AppCompatImageView 9 | import android.util.AttributeSet 10 | import jp.wasabeef.R 11 | 12 | /** 13 | * An ImageView which supports a foreground drawable. 14 | * 15 | * https://gist.github.com/JakeWharton/0a251d67649305d84e8a 16 | */ 17 | class ForegroundImageView @JvmOverloads constructor( 18 | context: Context, attrs: AttributeSet? = null) : AppCompatImageView(context, attrs) { 19 | 20 | private var innerForeground: Drawable? = null 21 | 22 | init { 23 | val typedArray = context.obtainStyledAttributes(attrs, 24 | R.styleable.ForegroundImageView) 25 | val foreground = typedArray 26 | .getDrawable(R.styleable.ForegroundImageView_android_foreground) 27 | if (foreground != null) { 28 | setForeground(foreground) 29 | } 30 | typedArray.recycle() 31 | } 32 | 33 | override fun jumpDrawablesToCurrentState() { 34 | super.jumpDrawablesToCurrentState() 35 | if (innerForeground != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { 36 | innerForeground!!.jumpToCurrentState() 37 | } 38 | } 39 | 40 | override fun verifyDrawable(who: Drawable): Boolean { 41 | return super.verifyDrawable(who) || who === innerForeground 42 | } 43 | 44 | override fun drawableStateChanged() { 45 | super.drawableStateChanged() 46 | if (innerForeground != null && innerForeground!!.isStateful) { 47 | innerForeground!!.state = drawableState 48 | } 49 | } 50 | 51 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { 52 | super.onMeasure(widthMeasureSpec, heightMeasureSpec) 53 | if (innerForeground != null) { 54 | innerForeground!!.setBounds(0, 0, measuredWidth, measuredHeight) 55 | invalidate() 56 | } 57 | } 58 | 59 | override fun onSizeChanged(width: Int, height: Int, oldWidth: Int, oldHeight: Int) { 60 | super.onSizeChanged(width, height, oldWidth, oldHeight) 61 | if (innerForeground != null) { 62 | innerForeground!!.setBounds(0, 0, width, height) 63 | invalidate() 64 | } 65 | } 66 | 67 | override fun draw(canvas: Canvas) { 68 | super.draw(canvas) 69 | 70 | if (innerForeground != null) { 71 | innerForeground!!.draw(canvas) 72 | } 73 | } 74 | 75 | /** 76 | * Supply a drawable resource that is to be rendered on top of all of the child 77 | * views in the frame layout. 78 | * 79 | * @param drawableResId The drawable resource to be drawn on top of the children. 80 | */ 81 | fun setForegroundResource(drawableResId: Int) { 82 | setForeground(ContextCompat.getDrawable(context, drawableResId)) 83 | } 84 | 85 | /** 86 | * Supply a Drawable that is to be rendered on top of all of the child 87 | * views in the frame layout. 88 | * 89 | * @param drawable The Drawable to be drawn on top of the children. 90 | */ 91 | override fun setForeground(drawable: Drawable?) { 92 | if (innerForeground === drawable) { 93 | return 94 | } 95 | if (innerForeground != null) { 96 | innerForeground!!.callback = null 97 | unscheduleDrawable(innerForeground) 98 | } 99 | 100 | innerForeground = drawable 101 | 102 | if (drawable != null) { 103 | drawable.callback = this 104 | if (drawable.isStateful) { 105 | drawable.state = drawableState 106 | } 107 | } 108 | requestLayout() 109 | invalidate() 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /app/src/main/kotlin/jp/wasabeef/ui/main/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package jp.wasabeef.ui.main 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.databinding.DataBindingUtil 6 | import android.os.Bundle 7 | import android.support.design.widget.BottomNavigationView 8 | import android.view.MenuItem 9 | import jp.wasabeef.R 10 | import jp.wasabeef.databinding.ActivityMainBinding 11 | import jp.wasabeef.ui.component.activity.BaseActivity 12 | import jp.wasabeef.ui.main.home.HomeFragment 13 | import jp.wasabeef.ui.main.info.InfoFragment 14 | import jp.wasabeef.ui.main.mypage.MyPageFragment 15 | import jp.wasabeef.util.Display 16 | import jp.wasabeef.util.ext.replaceFragment 17 | import jp.wasabeef.util.ext.setFragment 18 | 19 | class MainActivity : BaseActivity(), BottomNavigationView.OnNavigationItemSelectedListener { 20 | 21 | companion object { 22 | fun start(context: Context) { 23 | context.startActivity(Intent(context, MainActivity::class.java)) 24 | } 25 | } 26 | 27 | // @Inject 28 | // lateinit var mainViewModel: MainViewModel 29 | 30 | val binding: ActivityMainBinding by lazy { 31 | DataBindingUtil.setContentView(this, R.layout.activity_main) 32 | } 33 | 34 | override fun onCreate(savedInstanceState: Bundle?) { 35 | super.onCreate(savedInstanceState) 36 | 37 | setFragment(R.id.frame_main_content, ::HomeFragment) 38 | binding.bottomNavMain.setOnNavigationItemSelectedListener(this) 39 | 40 | Display.showSystemUi(window) 41 | } 42 | 43 | override fun onNavigationItemSelected(item: MenuItem): Boolean { 44 | when (item.itemId) { 45 | binding.bottomNavMain.selectedItemId -> return false 46 | R.id.action_home -> replaceFragment(R.id.frame_main_content, ::HomeFragment) 47 | R.id.action_info -> replaceFragment(R.id.frame_main_content, ::InfoFragment) 48 | R.id.action_my_page -> replaceFragment(R.id.frame_main_content, ::MyPageFragment) 49 | else -> throw IllegalStateException("Not founded this ID") 50 | } 51 | return true 52 | } 53 | 54 | override fun onBackPressed() { 55 | super.onBackPressed() 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/src/main/kotlin/jp/wasabeef/ui/main/MainViewModel.kt: -------------------------------------------------------------------------------- 1 | package jp.wasabeef.ui.main 2 | 3 | import android.arch.lifecycle.ViewModel 4 | import io.reactivex.disposables.CompositeDisposable 5 | 6 | /** 7 | * Created by Wasabeef on 2018/03/02. 8 | */ 9 | class MainViewModel : ViewModel() { 10 | 11 | private val disposable: CompositeDisposable = CompositeDisposable() 12 | 13 | override fun onCleared() { 14 | super.onCleared() 15 | disposable.clear() 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/jp/wasabeef/ui/main/home/HomeFragment.kt: -------------------------------------------------------------------------------- 1 | package jp.wasabeef.ui.main.home 2 | 3 | import android.arch.lifecycle.ViewModelProvider 4 | import android.arch.lifecycle.ViewModelProviders 5 | import android.content.Context 6 | import android.databinding.DataBindingUtil 7 | import android.os.Bundle 8 | import android.support.v4.app.Fragment 9 | import android.view.LayoutInflater 10 | import android.view.View 11 | import android.view.ViewGroup 12 | import dagger.android.support.AndroidSupportInjection 13 | import jp.wasabeef.R 14 | import jp.wasabeef.databinding.FragmentHomeBinding 15 | import javax.inject.Inject 16 | 17 | /** 18 | * Created by Wasabeef on 2017/10/20. 19 | */ 20 | class HomeFragment : Fragment() { 21 | 22 | @Inject 23 | lateinit var viewModelFactory: ViewModelProvider.Factory 24 | 25 | val homeViewModel: HomeViewModel by lazy { 26 | ViewModelProviders.of(this, viewModelFactory).get(HomeViewModel::class.java) 27 | } 28 | 29 | override fun onAttach(context: Context?) { 30 | AndroidSupportInjection.inject(this) 31 | super.onAttach(context) 32 | } 33 | 34 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = 35 | inflater.inflate(R.layout.fragment_home, container, false) 36 | 37 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 38 | super.onViewCreated(view, savedInstanceState) 39 | val binding = DataBindingUtil.bind(view) 40 | 41 | lifecycle.addObserver(homeViewModel) 42 | binding.let { 43 | it!!.homeViewModel = homeViewModel 44 | it.setLifecycleOwner(this) 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/jp/wasabeef/ui/main/home/HomeViewModel.kt: -------------------------------------------------------------------------------- 1 | package jp.wasabeef.ui.main.home 2 | 3 | import android.arch.lifecycle.LifecycleObserver 4 | import android.arch.lifecycle.LiveData 5 | import android.arch.lifecycle.ViewModel 6 | import io.reactivex.android.schedulers.AndroidSchedulers 7 | import io.reactivex.disposables.CompositeDisposable 8 | import io.reactivex.schedulers.Schedulers 9 | import jp.wasabeef.data.local.ApodOfNasa 10 | import jp.wasabeef.data.repository.Planet 11 | import jp.wasabeef.util.Result 12 | import jp.wasabeef.util.defaultErrorHandler 13 | import jp.wasabeef.util.ext.toLiveData 14 | import javax.inject.Inject 15 | 16 | /** 17 | * Created by Wasabeef on 2018/03/02. 18 | */ 19 | class HomeViewModel @Inject constructor( 20 | private val planetRepository: Planet 21 | ) : ViewModel(), LifecycleObserver { 22 | 23 | private val disposable: CompositeDisposable = CompositeDisposable() 24 | 25 | val planet: LiveData> by lazy { 26 | planetRepository.getInfo() 27 | .subscribeOn(Schedulers.io()) 28 | .observeOn(AndroidSchedulers.mainThread()) 29 | .map { Result.success(it) } 30 | .doOnError(defaultErrorHandler()) 31 | .onErrorReturn { Result.failure(it) } 32 | .startWith(Result.loading()) 33 | .toLiveData() 34 | } 35 | 36 | override fun onCleared() { 37 | super.onCleared() 38 | disposable.clear() 39 | } 40 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/jp/wasabeef/ui/main/info/InfoFragment.kt: -------------------------------------------------------------------------------- 1 | package jp.wasabeef.ui.main.info 2 | 3 | import android.os.Bundle 4 | import android.support.v4.app.Fragment 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import jp.wasabeef.R 9 | 10 | /** 11 | * Created by Wasabeef on 2018/02/15. 12 | */ 13 | class InfoFragment : Fragment() { 14 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = 15 | inflater.inflate(R.layout.fragment_info, container, false) 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/kotlin/jp/wasabeef/ui/main/mypage/MyPageFragment.kt: -------------------------------------------------------------------------------- 1 | package jp.wasabeef.ui.main.mypage 2 | 3 | import android.os.Bundle 4 | import android.support.v4.app.Fragment 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import jp.wasabeef.R 9 | 10 | /** 11 | * Created by Wasabeef on 2018/02/15. 12 | */ 13 | class MyPageFragment : Fragment() { 14 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = 15 | inflater.inflate(R.layout.fragment_my_page, container, false) 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/kotlin/jp/wasabeef/util/AppError.kt: -------------------------------------------------------------------------------- 1 | package jp.wasabeef.util 2 | 3 | import java.lang.RuntimeException 4 | 5 | /** 6 | * Created by Wasabeef on 2017/12/04. 7 | */ 8 | open class AppError : RuntimeException { 9 | 10 | enum class Cause { 11 | /** ネットワーク未接続 */ 12 | UNKNOWN_HOST, 13 | /** ネットワーク通信タイムアウト */ 14 | TIMEOUT, 15 | /** ネットワーク全般 */ 16 | NETWORK, 17 | /** APIエラー全般 */ 18 | API, 19 | /** DBエラー全般 */ 20 | DB, 21 | /** その他エラー全般 */ 22 | ANY 23 | } 24 | 25 | private val causeType: Cause 26 | 27 | constructor(type: Cause = Cause.ANY) : super() { 28 | this.causeType = type 29 | } 30 | 31 | constructor(message: String?, type: Cause = Cause.ANY) : super(message) { 32 | this.causeType = type 33 | } 34 | 35 | constructor(error: Throwable?, type: Cause = Cause.ANY) : super(error) { 36 | this.causeType = type 37 | } 38 | 39 | constructor(message: String?, error: Throwable?, type: Cause = Cause.ANY) : super(message, error) { 40 | this.causeType = type 41 | } 42 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/jp/wasabeef/util/AppInjection.kt: -------------------------------------------------------------------------------- 1 | package jp.wasabeef.util 2 | 3 | import android.content.Context 4 | import jp.wasabeef.App 5 | import jp.wasabeef.di.AppComponent 6 | 7 | /** 8 | * Created by Wasabeef on 2017/10/26. 9 | */ 10 | object AppInjection { 11 | fun of(context: Context?): AppComponent { 12 | return (context as App).applicationInjector() as AppComponent 13 | } 14 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/jp/wasabeef/util/ApplicationJsonAdapterFactory.kt: -------------------------------------------------------------------------------- 1 | package jp.wasabeef.util 2 | 3 | import com.squareup.moshi.JsonAdapter 4 | import se.ansman.kotshi.KotshiJsonAdapterFactory 5 | 6 | /** 7 | * Created by Wasabeef on 2018/03/06. 8 | * 9 | * See ansman/kotshi 10 | */ 11 | @KotshiJsonAdapterFactory 12 | abstract class ApplicationJsonAdapterFactory : JsonAdapter.Factory { 13 | companion object { 14 | val INSTANCE: ApplicationJsonAdapterFactory = KotshiApplicationJsonAdapterFactory() 15 | } 16 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/jp/wasabeef/util/Display.kt: -------------------------------------------------------------------------------- 1 | package jp.wasabeef.util 2 | 3 | import android.view.View 4 | import android.view.Window 5 | 6 | /** 7 | * Created by Wasabeef on 2017/10/23. 8 | */ 9 | class Display { 10 | companion object { 11 | fun showSystemUi(window: Window) { 12 | window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/jp/wasabeef/util/ErrorHandler.kt: -------------------------------------------------------------------------------- 1 | package jp.wasabeef.util 2 | 3 | import timber.log.Timber 4 | 5 | /** 6 | * Created by Wasabeef on 2018/03/05. 7 | */ 8 | fun defaultErrorHandler(): (Throwable) -> Unit = { e -> Timber.e(e) } -------------------------------------------------------------------------------- /app/src/main/kotlin/jp/wasabeef/util/GlideModule.kt: -------------------------------------------------------------------------------- 1 | package jp.wasabeef.util 2 | 3 | import android.content.Context 4 | import com.bumptech.glide.Glide 5 | import com.bumptech.glide.GlideBuilder 6 | import com.bumptech.glide.Registry 7 | import com.bumptech.glide.annotation.GlideModule 8 | import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader 9 | import com.bumptech.glide.load.model.GlideUrl 10 | import com.bumptech.glide.module.AppGlideModule 11 | import okhttp3.OkHttpClient 12 | import java.io.InputStream 13 | import javax.inject.Inject 14 | 15 | /** 16 | * Created by Wasabeef on 2017/10/24. 17 | */ 18 | @GlideModule 19 | class GlideModule : AppGlideModule() { 20 | 21 | @Inject 22 | lateinit var okHttpClient: OkHttpClient 23 | 24 | override fun registerComponents(context: Context, glide: Glide, registry: Registry) { 25 | registry.replace(GlideUrl::class.java, InputStream::class.java, OkHttpUrlLoader.Factory(okHttpClient)) 26 | super.registerComponents(context, glide, registry) 27 | } 28 | 29 | override fun applyOptions(context: Context, builder: GlideBuilder) { 30 | AppInjection.of(context).inject(this) 31 | super.applyOptions(context, builder) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/kotlin/jp/wasabeef/util/InstantAdapter.kt: -------------------------------------------------------------------------------- 1 | package jp.wasabeef.util 2 | 3 | import com.squareup.moshi.FromJson 4 | import com.squareup.moshi.ToJson 5 | import org.threeten.bp.Instant 6 | import org.threeten.bp.ZoneId 7 | import org.threeten.bp.format.DateTimeFormatter.ISO_INSTANT 8 | 9 | /** 10 | * Created by Wasabeef on 2018/03/06. 11 | * 12 | * See square/moshi 13 | */ 14 | class InstantAdapter { 15 | 16 | companion object { 17 | val INSTANCE = InstantAdapter() 18 | } 19 | 20 | @ToJson 21 | fun toJson(time: Instant): String = ISO_INSTANT.format(time) 22 | 23 | @FromJson 24 | fun fromJson(time: String): Instant = 25 | ISO_INSTANT.parse(time, Instant.FROM).atZone(ZoneId.systemDefault()).toInstant() 26 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/jp/wasabeef/util/Memory.kt: -------------------------------------------------------------------------------- 1 | package jp.wasabeef.util 2 | 3 | import android.app.ActivityManager 4 | import android.content.Context 5 | import android.content.Context.ACTIVITY_SERVICE 6 | import android.content.pm.ApplicationInfo.FLAG_LARGE_HEAP 7 | import android.support.annotation.FloatRange 8 | 9 | /** 10 | * Created by Wasabeef on 2017/10/16. 11 | */ 12 | object Memory { 13 | 14 | /** 15 | * 指定された百分率をメモリから計算する 16 | */ 17 | fun calcCacheSize(context: Context, @FloatRange(from = 0.01, to = 1.0) size: Float): Long { 18 | val am = context.getSystemService(ACTIVITY_SERVICE) as ActivityManager 19 | val largeHeap = context.applicationInfo.flags and FLAG_LARGE_HEAP != 0 20 | val memoryClass = if (largeHeap) am.largeMemoryClass else am.memoryClass 21 | return (memoryClass * 1024L * 1024L * size).toLong() 22 | } 23 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/jp/wasabeef/util/Result.kt: -------------------------------------------------------------------------------- 1 | package jp.wasabeef.util 2 | 3 | /** 4 | * Created by Wasabeef on 2018/03/08. 5 | */ 6 | sealed class Result { 7 | class Loading : Result() 8 | data class Success(val data: T) : Result() 9 | data class Failure(val message: Throwable) : Result() 10 | 11 | companion object { 12 | fun loading(): Result = Loading() 13 | fun success(data: T): Result = Success(data) 14 | fun failure(message: Throwable): Result = Failure(message) 15 | } 16 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/jp/wasabeef/util/SingleLiveEvent.kt: -------------------------------------------------------------------------------- 1 | package jp.wasabeef.util 2 | 3 | import android.arch.lifecycle.LifecycleOwner 4 | import android.arch.lifecycle.MutableLiveData 5 | import android.arch.lifecycle.Observer 6 | import android.support.annotation.MainThread 7 | import android.support.annotation.Nullable 8 | import timber.log.Timber 9 | import java.util.concurrent.atomic.AtomicBoolean 10 | 11 | /** 12 | * A lifecycle-aware observable that sends only new updates after subscription, used for events like 13 | * navigation and Snackbar messages. 14 | *

15 | * This avoids a common problem with events: on configuration change (like rotation) an update 16 | * can be emitted if the observer is active. This LiveData only calls the observable if there's an 17 | * explicit call to setValue() or call(). 18 | *

19 | * Note that only one observer is going to be notified of changes. 20 | */ 21 | class SingleLiveEvent : MutableLiveData() { 22 | 23 | private val mPending = AtomicBoolean(false) 24 | 25 | @MainThread 26 | override fun observe(owner: LifecycleOwner, observer: Observer) { 27 | 28 | if (hasActiveObservers()) { 29 | Timber.w("Multiple observers registered but only one will be notified of changes.") 30 | } 31 | 32 | // Observe the internal MutableLiveData 33 | super.observe(owner, Observer { t -> 34 | if (mPending.compareAndSet(true, false)) { 35 | observer.onChanged(t) 36 | } 37 | }) 38 | } 39 | 40 | @MainThread 41 | override fun setValue(@Nullable t: T?) { 42 | mPending.set(true) 43 | super.setValue(t) 44 | } 45 | 46 | /** 47 | * Used for cases where T is Void, to make calls cleaner. 48 | */ 49 | @MainThread 50 | fun call() { 51 | value = null 52 | } 53 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/jp/wasabeef/util/ext/ActivityExt.kt: -------------------------------------------------------------------------------- 1 | package jp.wasabeef.util.ext 2 | 3 | import android.support.annotation.IdRes 4 | import android.support.v4.app.Fragment 5 | import android.support.v4.app.FragmentActivity 6 | 7 | /** 8 | * Created by Wasabeef on 2017/10/20. 9 | */ 10 | @Suppress("UNCHECKED_CAST") 11 | fun FragmentActivity.findFragmentById(@IdRes id: Int): T = supportFragmentManager.findFragmentById(id) as T 12 | 13 | inline fun FragmentActivity.setFragment(containerViewId: Int, f: () -> Fragment): Fragment? { 14 | val manager = supportFragmentManager 15 | val fragment = manager?.findFragmentById(containerViewId) 16 | fragment?.let { return it } 17 | return f().apply { manager?.beginTransaction()?.add(containerViewId, this)?.commit() } 18 | } 19 | 20 | inline fun FragmentActivity.replaceFragment(containerViewId: Int, f: () -> Fragment): Fragment? { 21 | return f().apply { supportFragmentManager?.beginTransaction()?.replace(containerViewId, this)?.commit() } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/kotlin/jp/wasabeef/util/ext/FlowableExt.kt: -------------------------------------------------------------------------------- 1 | package jp.wasabeef.util.ext 2 | 3 | import android.arch.lifecycle.LiveData 4 | import android.arch.lifecycle.LiveDataReactiveStreams 5 | import io.reactivex.BackpressureStrategy 6 | import io.reactivex.Observable 7 | import org.reactivestreams.Publisher 8 | 9 | /** 10 | * Created by Wasabeef on 2018/03/05. 11 | */ 12 | fun Publisher.toLiveData() = LiveDataReactiveStreams.fromPublisher(this) as LiveData 13 | 14 | fun Observable.toLiveData(strategy: BackpressureStrategy = BackpressureStrategy.LATEST) = toFlowable(strategy).toLiveData() -------------------------------------------------------------------------------- /app/src/main/kotlin/jp/wasabeef/util/ext/StreamObserverExt.kt: -------------------------------------------------------------------------------- 1 | package jp.wasabeef.util.ext 2 | 3 | import io.grpc.stub.StreamObserver 4 | import io.reactivex.Observable 5 | 6 | /** 7 | * Created by Wasabeef on 2018/03/08. 8 | */ 9 | inline fun asObservable(crossinline body: (StreamObserver) -> Unit): Observable { 10 | return Observable.create { subscription -> 11 | val observer = object : StreamObserver { 12 | override fun onNext(value: T) { 13 | subscription.onNext(value) 14 | } 15 | 16 | override fun onError(error: Throwable) { 17 | subscription.onError(error) 18 | } 19 | 20 | override fun onCompleted() { 21 | subscription.onComplete() 22 | } 23 | } 24 | body(observer) 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/jp/wasabeef/util/ext/ViewExt.kt: -------------------------------------------------------------------------------- 1 | package jp.wasabeef.util.ext 2 | 3 | import android.view.View 4 | 5 | /** 6 | * Viewの汎用的な拡張関数 7 | * Created by Wasabeef on 2017/10/02. 8 | */ 9 | fun View.toVisible() { 10 | visibility = View.VISIBLE 11 | } 12 | 13 | fun View.toInvisible() { 14 | visibility = View.INVISIBLE 15 | } 16 | 17 | fun View.toGone() { 18 | visibility = View.GONE 19 | } -------------------------------------------------------------------------------- /app/src/main/res/color/selector_bottom_nav_icon.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_home.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasabeef/kotlin-mvvm/130aa438e5a665f366c1fbd493a82c9b7b926c8c/app/src/main/res/drawable-xxhdpi/ic_home.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_info.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasabeef/kotlin-mvvm/130aa438e5a665f366c1fbd493a82c9b7b926c8c/app/src/main/res/drawable-xxhdpi/ic_info.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_my_page.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasabeef/kotlin-mvvm/130aa438e5a665f366c1fbd493a82c9b7b926c8c/app/src/main/res/drawable-xxhdpi/ic_my_page.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 15 | 21 | 27 | 33 | 39 | 45 | 51 | 57 | 63 | 69 | 75 | 81 | 87 | 93 | 99 | 105 | 111 | 117 | 123 | 129 | 135 | 136 | 137 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 23 | 24 | 25 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_home.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 13 | 14 | 15 | 19 | 20 | 32 | 33 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_info.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_my_page.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/menu/bottom_nav_main.xml: -------------------------------------------------------------------------------- 1 | 2 |

7 | 14 | 21 | 28 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasabeef/kotlin-mvvm/130aa438e5a665f366c1fbd493a82c9b7b926c8c/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasabeef/kotlin-mvvm/130aa438e5a665f366c1fbd493a82c9b7b926c8c/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasabeef/kotlin-mvvm/130aa438e5a665f366c1fbd493a82c9b7b926c8c/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasabeef/kotlin-mvvm/130aa438e5a665f366c1fbd493a82c9b7b926c8c/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasabeef/kotlin-mvvm/130aa438e5a665f366c1fbd493a82c9b7b926c8c/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasabeef/kotlin-mvvm/130aa438e5a665f366c1fbd493a82c9b7b926c8c/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasabeef/kotlin-mvvm/130aa438e5a665f366c1fbd493a82c9b7b926c8c/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasabeef/kotlin-mvvm/130aa438e5a665f366c1fbd493a82c9b7b926c8c/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasabeef/kotlin-mvvm/130aa438e5a665f366c1fbd493a82c9b7b926c8c/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values-v21/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /app/src/main/res/values-v21/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 13 | 14 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | #FFFFFF 10 | #33FFFFFF 11 | #80FFFFFF 12 | #1F000000 13 | #80000000 14 | #333333 15 | #222222 16 | #22222222 17 | 18 | #555555 19 | #7F7F7F 20 | #337F7F7F 21 | #4D7F7F7F 22 | #807F7F7F 23 | #E5E5E5 24 | 25 | 26 | #6447FF 27 | #55ACEE 28 | #3B5998 29 | 30 | 31 | #FB7442 32 | 33 | 34 | 35 | 36 | 37 | @color/white_20 38 | @color/black_12 39 | @color/black_50 40 | @color/burnt_orange 41 | @color/tin 42 | @color/tin_20 43 | @color/tin_30 44 | @color/night_rider 45 | @color/whisper 46 | @color/neon_blue 47 | 48 | @android:color/transparent 49 | @android:color/black 50 | @color/neon_blue 51 | 52 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 14sp 6 | 20sp 7 | 24sp 8 | 9 | 10 | 8dp 11 | 16dp 12 | 24dp 13 | 48dp 14 | 15 | 16 | 56dp 17 | 500dp 18 | 19 | 20 | 10sp 21 | 10sp 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Home 8 | Info 9 | MyPage 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | 15 | 18 | 19 | 22 | 23 | 25 | 26 | 28 | -------------------------------------------------------------------------------- /app/src/test/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/test/resources/robolectric.properties: -------------------------------------------------------------------------------- 1 | sdk=19 2 | #application=jp.wasabeef.TestApp 3 | #constants=jp.wasabeef.BuildConfig 4 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | google() 6 | jcenter() 7 | maven { url 'https://maven.fabric.io/public' } 8 | maven { url 'http://dl.bintray.com/kotlin/kotlin-eap-1.2' } 9 | } 10 | dependencies { 11 | classpath 'com.android.tools.build:gradle:3.2.0-alpha06' 12 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$KOTLIN_VERSION" 13 | classpath 'com.github.ben-manes:gradle-versions-plugin:0.17.0' 14 | classpath 'io.fabric.tools:gradle:1.25.1' 15 | classpath 'com.google.gms:google-services:3.2.0' 16 | classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.4' 17 | 18 | // NOTE: Do not place your application dependencies here; they belong 19 | // in the individual module build.gradle files 20 | } 21 | } 22 | 23 | allprojects { 24 | repositories { 25 | google() 26 | jcenter() 27 | maven { url 'http://dl.bintray.com/kotlin/kotlin-eap-1.2' } 28 | maven { url 'https://dl.bintray.com/musichin/maven' } 29 | } 30 | } 31 | 32 | task clean(type: Delete) { 33 | delete rootProject.buildDir 34 | } 35 | 36 | apply plugin: 'com.github.ben-manes.versions' -------------------------------------------------------------------------------- /fastlane/Appfile: -------------------------------------------------------------------------------- 1 | package_name "jp.wasabeef" 2 | -------------------------------------------------------------------------------- /fastlane/Fastfile: -------------------------------------------------------------------------------- 1 | # More documentation about how to customize your build 2 | # can be found here: 3 | # https://docs.fastlane.tools 4 | fastlane_version "1.109.0" 5 | 6 | # This value helps us track success metrics for Fastfiles 7 | # we automatically generate. Feel free to remove this line 8 | # once you get things running smoothly! 9 | generated_fastfile_id "07e8a036-9dc4-4904-ab30-d2f6664c77ba" 10 | 11 | default_platform :android 12 | 13 | # Fastfile actions accept additional configuration, but 14 | # don't worry, fastlane will prompt you for required 15 | # info which you can add here later 16 | lane :beta do 17 | # build the release variant 18 | gradle(task: "assembleDevelopRelease") 19 | 20 | # upload to Beta by Crashlytics 21 | crashlytics( 22 | # keys for organization: Wasabeef 23 | api_token: "*************", 24 | build_secret: "***************************" 25 | ) 26 | end 27 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx4608m -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | org.gradle.parallel=true 14 | org.gradle.daemon=true 15 | org.gradle.configureondemand=true 16 | org.gradle.caching=true 17 | android.enableBuildCache=true 18 | # Use R8 instead of ProGuard for code shrinking. 19 | #android.enableR8=true 20 | 21 | VERSION_CODE = 1 22 | VERSION_NAME = 1.0.0 23 | 24 | DB_SCHEMA_VERSION = 1 25 | 26 | PACKAGE_NAME = jp.wasabeef 27 | COMPILE_SDK_VERSION = 27 28 | BUILD_TOOLS_VERSION = 27.0.3 29 | MIN_SDK_VERSION = 19 30 | TARGET_SDK_VERSION = 27 31 | 32 | KOTLIN_VERSION = 1.2.30 33 | SUPPORT_PACKAGE_VERSION = 27.1.0 34 | GOOGLE_SERVICE_VERSION = 11.8.0 35 | OKHTTP_VERSION = 3.9.0 36 | OKIO_VERSION = 1.13.0 37 | RETROFIT_VERSION = 2.3.0 38 | DAGGER_VERSION = 2.14.1 39 | FIREBASE_UI_VERSION = 3.2.2 40 | FACEBOOK_SDK_VERSION = 4.31.0 41 | TWITTER_SDK_VERSION = 3.2.0 42 | GRPC_VERSION = 1.10.0 43 | 44 | ESPRESSO_VERSION = 3.0.1 45 | JUNIT_VERSION = 4.12 46 | 47 | STETHO_VERSION = 1.5.0 48 | 49 | KTLINT_VERSION = 0.19.0 50 | FINDBUGS_VERSION = 3.0.1 -------------------------------------------------------------------------------- /gradle/android-lint-config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /gradle/findbugs-android-exclude.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /gradle/findbugs.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'findbugs' 2 | 3 | dependencies { 4 | findbugs "com.google.code.findbugs:findbugs:$FINDBUGS_VERSION" 5 | } 6 | 7 | task findbugs(type: FindBugs) { 8 | classes = fileTree("$buildDir/tmp/kotlin-classes") 9 | source = fileTree('src/main/kotlin') 10 | classpath = files() 11 | 12 | findbugs { 13 | toolVersion = "$FINDBUGS_VERSION" 14 | sourceSets = [android.sourceSets] 15 | ignoreFailures = true 16 | effort = "max" 17 | reportLevel = "low" 18 | excludeFilter = file("$project.rootDir/gradle/findbugs-android-exclude.xml") 19 | } 20 | 21 | reports { 22 | xml.enabled = true 23 | html.enabled = false 24 | } 25 | } -------------------------------------------------------------------------------- /gradle/jacoco.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "jacoco" 2 | 3 | android.applicationVariants.all { variant -> 4 | def variantName = variant.name.capitalize() 5 | def autoGenerated = ['**/R.class', 6 | '**/R$*.class', 7 | '**/Manifest*.*', 8 | 'android/**/*.*', 9 | '**/BuildConfig.*', 10 | '**/*$ViewBinder*.*', 11 | '**/*$ViewInjector*.*', 12 | 13 | // Dagger 14 | '**/*_MembersInjector.class', 15 | '**/Dagger*Component.class', 16 | '**/Dagger*Component$Builder.class', 17 | '**/*Module_*Factory.class', 18 | '**/*_Factory.class'] 19 | 20 | /** 21 | * Generates Jacoco coverage reports based off the unit tests.*/ 22 | task("jacoco${variantName}Report", type: JacocoReport, dependsOn: "test${variantName}UnitTest") { 23 | group "Reporting" 24 | description "Generate ${variantName} Jacoco coverage reports." 25 | 26 | reports { 27 | xml.enabled = true 28 | html.enabled = true 29 | } 30 | 31 | // variant.javaCompile.source does not work 32 | // traverses from starting point 33 | sourceDirectories = files(android.sourceSets.main.java.srcDirs) 34 | executionData = files("${buildDir}/jacoco/test${variantName}UnitTest.exec") 35 | classDirectories = files( 36 | [fileTree(dir: "${buildDir}/intermediates/classes/${variantName}", 37 | excludes: autoGenerated)], 38 | [fileTree(dir: "${buildDir}/tmp/kotlin-classes/${variantName}UnitTest", 39 | excludes: autoGenerated)]) 40 | } 41 | } -------------------------------------------------------------------------------- /gradle/ktlint.gradle: -------------------------------------------------------------------------------- 1 | configurations { 2 | ktlint 3 | } 4 | 5 | dependencies { 6 | ktlint "com.github.shyiko:ktlint:$KTLINT_VERSION" 7 | } 8 | 9 | task ktlint(type: Exec) { 10 | commandLine 'java', '-cp', configurations.ktlint.join(System.getProperty('path.separator')), 11 | 'com.github.shyiko.ktlint.Main', '--reporter=checkstyle', 'src/**/*.kt' 12 | def outputDirectory = "$buildDir/reports/ktlint" 13 | def outputFile = "${outputDirectory}/ktlint-checkstyle-report.xml" 14 | ignoreExitValue = true 15 | doFirst { 16 | new File(outputDirectory).mkdirs() 17 | standardOutput = new FileOutputStream(outputFile) 18 | } 19 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasabeef/kotlin-mvvm/130aa438e5a665f366c1fbd493a82c9b7b926c8c/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Feb 13 10:57:45 JST 2018 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.5-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /keystore/debug.gradle: -------------------------------------------------------------------------------- 1 | signingConfigs { 2 | debug { 3 | storeFile file("debug.keystore") 4 | storePassword "android" 5 | keyAlias "androiddebugkey" 6 | keyPassword "android" 7 | } 8 | } -------------------------------------------------------------------------------- /keystore/debug.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasabeef/kotlin-mvvm/130aa438e5a665f366c1fbd493a82c9b7b926c8c/keystore/debug.keystore -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | --------------------------------------------------------------------------------