├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── dependencies.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── br │ │ └── com │ │ └── liveo │ │ └── mvp │ │ ├── ApplicationTestComponentRule.kt │ │ ├── base │ │ ├── BaseActivityTestRule.kt │ │ └── BaseViewAction.kt │ │ └── screen │ │ └── home │ │ └── HomeActivityTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── br │ │ │ └── com │ │ │ └── liveo │ │ │ └── mvp │ │ │ ├── App.kt │ │ │ ├── base │ │ │ ├── BaseActivity.kt │ │ │ ├── BaseAdapter.kt │ │ │ ├── BaseDialog.kt │ │ │ ├── BaseException.kt │ │ │ ├── BaseModel.kt │ │ │ ├── BasePresenter.kt │ │ │ ├── BaseScheduler.kt │ │ │ └── BaseView.kt │ │ │ ├── data │ │ │ ├── local │ │ │ │ ├── Preferences.java │ │ │ │ ├── PreferencesHelper.java │ │ │ │ └── endpoint │ │ │ │ │ ├── EndPointJson.kt │ │ │ │ │ └── EndPointMocked.kt │ │ │ ├── remote │ │ │ │ ├── ApiHelper.kt │ │ │ │ ├── endpoint │ │ │ │ │ ├── EndPoint.kt │ │ │ │ │ └── EndPointHelper.kt │ │ │ │ └── interceptor │ │ │ │ │ └── RequestInterceptor.kt │ │ │ └── scheduler │ │ │ │ ├── InjectionScheduler.kt │ │ │ │ ├── SchedulerProvider.kt │ │ │ │ └── TestSchedulerProvider.kt │ │ │ ├── di │ │ │ ├── components │ │ │ │ ├── ApplicationComponent.kt │ │ │ │ ├── ApplicationTestComponent.kt │ │ │ │ └── BaseApplicationComponent.kt │ │ │ ├── modules │ │ │ │ ├── HelperModule.kt │ │ │ │ └── HelperTestModule.kt │ │ │ └── scope │ │ │ │ ├── ActivityScoped.kt │ │ │ │ ├── FragmentScoped.kt │ │ │ │ ├── LocalScoped.kt │ │ │ │ └── RemoteScoped.kt │ │ │ ├── extension │ │ │ ├── ActivityExtension.kt │ │ │ └── ViewExtension.kt │ │ │ ├── main │ │ │ ├── MainPresenter.kt │ │ │ └── MainView.kt │ │ │ ├── model │ │ │ ├── User.kt │ │ │ └── domain │ │ │ │ └── UserResponse.kt │ │ │ ├── screen │ │ │ ├── home │ │ │ │ ├── HomeActivity.kt │ │ │ │ ├── HomeAdapter.kt │ │ │ │ ├── HomeContract.kt │ │ │ │ ├── HomePresenter.kt │ │ │ │ ├── HomeRepository.kt │ │ │ │ ├── HomeViewHolder.kt │ │ │ │ └── di │ │ │ │ │ ├── HomeComponent.kt │ │ │ │ │ └── HomeModule.kt │ │ │ └── login │ │ │ │ ├── LoginActivity.kt │ │ │ │ ├── LoginContract.kt │ │ │ │ └── LoginPresenter.kt │ │ │ └── util │ │ │ ├── Constant.kt │ │ │ ├── DateUtil.kt │ │ │ ├── Util.kt │ │ │ └── view │ │ │ ├── DatePickerDialogFragment.kt │ │ │ └── TimePickerDialogFragment.kt │ ├── res-screen │ │ ├── base │ │ │ ├── anim │ │ │ │ ├── translate_fade_in.xml │ │ │ │ ├── translate_fade_out.xml │ │ │ │ ├── translate_left_enter.xml │ │ │ │ ├── translate_left_exit.xml │ │ │ │ ├── translate_no_change.xml │ │ │ │ ├── translate_right_enter.xml │ │ │ │ ├── translate_right_exit.xml │ │ │ │ ├── translate_slide_down.xml │ │ │ │ └── translate_slide_up.xml │ │ │ └── layout │ │ │ │ └── appbar_and_toolbar.xml │ │ ├── home │ │ │ └── layout │ │ │ │ ├── activity_home.xml │ │ │ │ └── activity_home_item.xml │ │ └── splash │ │ │ └── layout │ │ │ └── activity_splash.xml │ └── res │ │ ├── drawable │ │ └── ic_arrow_back.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── values-v21 │ │ └── theme.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ ├── styles.xml │ │ └── theme.xml │ └── test │ ├── java │ └── br │ │ └── com │ │ └── liveo │ │ └── mvp │ │ └── screen │ │ └── home │ │ ├── HomePresenterTest.kt │ │ └── HomeRepositoryTest.kt │ └── resources │ └── mockito-extensions │ └── org.mockito.plugins.MockMaker ├── build.gradle ├── gradle.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # files for the dex VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # generated files 12 | bin/ 13 | gen/ 14 | */build/ 15 | build/ 16 | /build 17 | gradle/ 18 | .gradle 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | /local.properties 23 | 24 | # IntelliJ / Android Studio 25 | .idea 26 | /.idea 27 | .idea/ 28 | 29 | .idea/vcs.xml 30 | .idea/workspace.xml 31 | .idea/runConfigurations.xml 32 | .idea/libraries 33 | .idea/inspectionProfiles/ 34 | /.idea/vcs.xml 35 | /.idea/workspace.xml 36 | /.idea/runConfigurations.xml 37 | /.idea/libraries 38 | /.idea/inspectionProfiles/ 39 | /.idea/workspace.xml 40 | /.idea/libraries 41 | /.idea/inspectionProfiles/ 42 | /.idea/runConfigurations.xml 43 | 44 | .gradle 45 | /local.properties 46 | /.idea/workspace.xml 47 | /.idea/libraries 48 | .DS_Store 49 | /build 50 | 51 | *.iml 52 | 53 | # Gradle 54 | .gradle/ 55 | 56 | # Eclipse project files 57 | .classpath 58 | .project 59 | 60 | # DSStore 61 | .DS_Store 62 | /release.keystore 63 | /release.properties -------------------------------------------------------------------------------- /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 | # Liveo kotlin - mvp 2 | Simple project in Kotlin using MVP + Data Binding + Retrofit + Dagger2 e RxJava 3 | 4 | The same project using Java: https://github.com/rudsonlive/liveomvp 5 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | # built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # files for the dex VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # generated files 12 | bin/ 13 | gen/ 14 | */build/ 15 | build/ 16 | /build 17 | gradle/ 18 | .gradle 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | /local.properties 23 | 24 | # IntelliJ / Android Studio 25 | .idea 26 | /.idea 27 | .idea/ 28 | 29 | .idea/vcs.xml 30 | .idea/workspace.xml 31 | .idea/runConfigurations.xml 32 | .idea/libraries 33 | .idea/inspectionProfiles/ 34 | /.idea/vcs.xml 35 | /.idea/workspace.xml 36 | /.idea/runConfigurations.xml 37 | /.idea/libraries 38 | /.idea/inspectionProfiles/ 39 | /.idea/workspace.xml 40 | /.idea/libraries 41 | /.idea/inspectionProfiles/ 42 | /.idea/runConfigurations.xml 43 | 44 | .gradle 45 | /local.properties 46 | /.idea/workspace.xml 47 | /.idea/libraries 48 | .DS_Store 49 | /build 50 | 51 | *.iml 52 | 53 | # Gradle 54 | .gradle/ 55 | 56 | # Eclipse project files 57 | .classpath 58 | .project 59 | 60 | # DSStore 61 | .DS_Store 62 | /release.keystore 63 | /release.properties -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | apply plugin: 'kotlin-kapt' 5 | apply from: './dependencies.gradle' 6 | 7 | android { 8 | compileSdkVersion 27 9 | defaultConfig { 10 | applicationId "br.com.liveo.liveomvp" 11 | minSdkVersion 18 12 | targetSdkVersion 27 13 | versionCode 1 14 | versionName "1.0" 15 | 16 | vectorDrawables.useSupportLibrary = true 17 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 18 | 19 | buildConfigField("String", "BASE_URL", "\"https://reqres.in/\"") 20 | } 21 | 22 | dataBinding { 23 | enabled = true 24 | } 25 | 26 | kapt { 27 | generateStubs = true 28 | } 29 | 30 | buildTypes { 31 | release { 32 | minifyEnabled false 33 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 34 | } 35 | 36 | debug { 37 | testCoverageEnabled true 38 | } 39 | } 40 | 41 | sourceSets { 42 | main { 43 | file('src/main/res-screen') 44 | .listFiles() 45 | .each { res.srcDirs += it.path } 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /app/dependencies.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | implementation fileTree(dir: 'libs', include: ['*.jar']) 3 | 4 | //Test 5 | testImplementation "junit:junit:${junit_version}" 6 | testImplementation "org.mockito:mockito-core:${mockito_version}" 7 | 8 | androidTestImplementation("com.android.support.test.espresso:espresso-core:${espresso_core_version}", { 9 | exclude group: 'com.android.support', module: 'support-annotations' 10 | }) 11 | 12 | androidTestImplementation("com.android.support.test.espresso:espresso-contrib:${espresso_contrib_version}", { 13 | exclude group: 'com.android.support', module: 'support-annotations' 14 | exclude group: 'com.android.support', module: 'support-v4' 15 | exclude group: 'com.android.support', module: 'design' 16 | exclude group: 'com.android.support', module: 'recyclerview-v7' 17 | }) 18 | 19 | androidTestImplementation "com.android.support.test.uiautomator:uiautomator-v18:${uiautomator_v18_version}" 20 | 21 | //Google 22 | implementation "com.android.support:design:${google_support_version}" 23 | implementation "com.android.support:appcompat-v7:${google_support_version}" 24 | implementation "com.android.support:recyclerview-v7:${google_support_version}" 25 | implementation "com.android.support.constraint:constraint-layout:${google_constraint_version}" 26 | 27 | kapt "com.android.databinding:compiler:${google_databinding_compiler_version}" 28 | 29 | //Kotlin 30 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 31 | 32 | //Dagger 2 33 | implementation "com.google.dagger:dagger:${dagger_version}" 34 | kapt "com.google.dagger:dagger-compiler:${dagger_version}" 35 | 36 | //Timber - log 37 | implementation "com.jakewharton.timber:timber:${timber_version}" 38 | 39 | //Square 40 | implementation "com.squareup.retrofit2:retrofit:${retrofit_version}" 41 | implementation "com.squareup.okhttp3:logging-interceptor:${interceptor_version}" 42 | implementation "com.squareup.retrofit2:converter-gson:${retrofit_converter_gson_version}" 43 | implementation "com.squareup.retrofit2:adapter-rxjava2:${retrofit_adapter_rxjava_version}" 44 | 45 | //rxjava 46 | implementation "io.reactivex.rxjava2:rxjava:${rxjava_version}" 47 | implementation "io.reactivex.rxjava2:rxandroid:${rxandroid_version}" 48 | 49 | //Glide 50 | implementation "com.github.bumptech.glide:glide:${glide_version}" 51 | 52 | //Others 53 | implementation "net.yslibrary.keyboardvisibilityevent:keyboardvisibilityevent:${keyboard_event_version}" 54 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Volumes/DevLiveo/DevLiveo/android-sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /app/src/androidTest/java/br/com/liveo/mvp/ApplicationTestComponentRule.kt: -------------------------------------------------------------------------------- 1 | package br.com.liveo.mvp 2 | 3 | import br.com.liveo.mvp.App 4 | import br.com.liveo.mvp.di.components.DaggerApplicationTestComponent 5 | import br.com.liveo.mvp.di.modules.HelperTestModule 6 | import org.junit.rules.TestRule 7 | import org.junit.runner.Description 8 | import org.junit.runners.model.Statement 9 | 10 | class ApplicationTestComponentRule : TestRule { 11 | 12 | override fun apply(base: Statement, description: Description): Statement { 13 | val helperTestModule = HelperTestModule() 14 | 15 | module(helperTestModule) 16 | 17 | return base 18 | } 19 | 20 | companion object { 21 | 22 | internal fun module(helperTestModule: HelperTestModule?) { 23 | 24 | val baseComponent = DaggerApplicationTestComponent.builder() 25 | .helperTestModule(helperTestModule) 26 | .build() 27 | 28 | App.application = baseComponent 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/androidTest/java/br/com/liveo/mvp/base/BaseActivityTestRule.kt: -------------------------------------------------------------------------------- 1 | package br.com.liveo.mvp.base 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.support.annotation.IdRes 6 | import android.support.annotation.StringRes 7 | import android.support.test.InstrumentationRegistry 8 | import android.support.test.espresso.Espresso.onView 9 | import android.support.test.espresso.UiController 10 | import android.support.test.espresso.ViewAction 11 | import android.support.test.espresso.action.ViewActions 12 | import android.support.test.espresso.action.ViewActions.* 13 | import android.support.test.espresso.assertion.ViewAssertions.matches 14 | import android.support.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition 15 | import android.support.test.espresso.matcher.RootMatchers 16 | import android.support.test.espresso.matcher.ViewMatchers.* 17 | import android.support.test.rule.ActivityTestRule 18 | import android.support.test.uiautomator.UiDevice 19 | import android.support.v7.widget.AppCompatImageButton 20 | import android.support.v7.widget.RecyclerView 21 | import android.support.v7.widget.Toolbar 22 | import android.view.View 23 | import android.widget.ImageButton 24 | import br.com.liveo.mvp.ApplicationTestComponentRule 25 | import br.com.liveo.mvp.R 26 | import br.com.liveo.mvp.di.modules.HelperTestModule 27 | import org.hamcrest.Matcher 28 | import org.hamcrest.Matchers.* 29 | import java.lang.ref.WeakReference 30 | 31 | /** 32 | * Created by rudsonlima on 10/3/17. 33 | */ 34 | 35 | abstract class BaseActivityTestRule { 36 | 37 | var context: WeakReference? = null 38 | 39 | protected var helperTestModule: HelperTestModule? = null 40 | 41 | private val intent: Intent 42 | get() = Intent() 43 | 44 | protected abstract val activityTestRule: ActivityTestRule<*> 45 | 46 | protected fun onInitHelperTestModule() { 47 | context = WeakReference(InstrumentationRegistry.getTargetContext()) 48 | 49 | helperTestModule = HelperTestModule(context) 50 | 51 | helperTestModule?.let { 52 | ApplicationTestComponentRule.module(it) 53 | } 54 | 55 | activityTestRule.launchActivity(intent) 56 | } 57 | 58 | protected fun assertToast(@StringRes stringResource: Int) { 59 | onView(withText(stringResource)).inRoot(RootMatchers.withDecorView( 60 | not(`is`(activityTestRule.activity.window.decorView)))).check(matches(isDisplayed())) 61 | } 62 | 63 | protected fun performAction(@IdRes resourceId: Int, viewAction: ViewAction) { 64 | onView(withId(resourceId)).perform(viewAction) 65 | } 66 | 67 | protected fun performClick(resourceId: Int) { 68 | onView(withId(resourceId)).perform(click()) 69 | } 70 | 71 | protected fun performTextClick(resourceId: Int) { 72 | onView(allOf(withText(resourceId), isDisplayed())).perform(click()) 73 | } 74 | 75 | protected fun performClick(resourceId: Int, resourceIdwithParent: Int) { 76 | onView(allOf(withId(resourceId), 77 | withParent(withId(resourceIdwithParent)), 78 | isDisplayed())).perform(click()) 79 | } 80 | 81 | protected fun performClick(resourceId: Int, resourceIdwithParentAllOf: Int, resourceIdwithParent: Int) { 82 | onView(allOf(withId(resourceId), 83 | withParent(allOf(withId(resourceIdwithParentAllOf), 84 | withParent(withId(resourceIdwithParent)))), 85 | isDisplayed())).perform(click()) 86 | } 87 | 88 | protected fun performItemClick(@IdRes resourceId: Int) { 89 | onView(allOf(withId(resourceId), 90 | isDisplayed())) 91 | .perform(actionOnItemAtPosition(0, click())) 92 | } 93 | 94 | protected fun performSwipeLeft(resourceId: Int) { 95 | onView(withId(resourceId)).perform(swipeLeft()) 96 | } 97 | 98 | protected fun performSwipeLeft(resourceId: Int, resourceIdwithParent: Int) { 99 | onView(allOf(withId(resourceId), 100 | withParent(withId(resourceIdwithParent)), 101 | isDisplayed())).perform(swipeLeft()) 102 | } 103 | 104 | protected fun performSwipeRight(resourceId: Int) { 105 | onView(withId(resourceId)).perform(swipeRight()) 106 | } 107 | 108 | protected fun performSwipeRight(resourceId: Int, resourceIdwithParent: Int) { 109 | onView(allOf(withId(resourceId), 110 | withParent(withId(resourceIdwithParent)), 111 | isDisplayed())).perform(swipeRight()) 112 | } 113 | 114 | protected fun performSwipeUp(resourceId: Int) { 115 | onView(withId(resourceId)).perform(swipeUp()) 116 | } 117 | 118 | protected fun performSwipeUp(resourceId: Int, resourceIdwithParent: Int) { 119 | onView(allOf(withId(resourceId), 120 | withParent(withId(resourceIdwithParent)), 121 | isDisplayed())).perform(swipeUp()) 122 | } 123 | 124 | protected fun performSwipeDown(resourceId: Int) { 125 | onView(withId(resourceId)).perform(swipeDown()) 126 | } 127 | 128 | protected fun performSwipeDown(resourceId: Int, resourceIdwithParent: Int) { 129 | onView(allOf(withId(resourceId), 130 | withParent(withId(resourceIdwithParent)), 131 | isDisplayed())).perform(swipeDown()) 132 | } 133 | 134 | protected fun performClickImeAction(resourceId: Int) { 135 | onView(withId(resourceId)).perform(pressImeActionButton()) 136 | } 137 | 138 | protected fun performClickImeActionScrollTo(resourceId: Int) { 139 | onView(withId(resourceId)).perform(scrollTo(), pressImeActionButton()) 140 | } 141 | 142 | protected fun performTypeText(resourceId: Int, text: String) { 143 | onView(withId(resourceId)).perform(typeText(text), 144 | closeSoftKeyboard()) 145 | } 146 | 147 | protected fun performTypeTextNotCloseSoftKeyboard(resourceId: Int, text: String) { 148 | onView(withId(resourceId)).perform(typeText(text)) 149 | } 150 | 151 | // Performs text insert where the parent necessarily is ScrollView 152 | // 153 | // View preconditions: 154 | // must be a descendant of ScrollView 155 | // must have visibility set to View.VISIBLE 156 | protected fun performTypeTextScrollTo(resourceId: Int, text: String) { 157 | onView(withId(resourceId)).perform(scrollTo(), typeText(text), 158 | closeSoftKeyboard()) 159 | } 160 | 161 | protected fun performClickScrollTo(resourceId: Int) { 162 | onView(withId(resourceId)).perform(scrollTo(), click()) 163 | } 164 | 165 | //android.R.string.ok 166 | protected fun performClickScrollToWithTextOK(resourceId: Int) { 167 | onView(allOf(withId(resourceId), withText(android.R.string.ok))).perform(scrollTo(), click()) 168 | } 169 | 170 | //android.R.string.cancel 171 | protected fun performClickScrollToWithTextCancel(resourceId: Int) { 172 | onView(allOf(withId(resourceId), withText(android.R.string.cancel))).perform(scrollTo(), click()) 173 | } 174 | 175 | protected fun performTypeTextScrollToNotCloseSoftKeyboard(resourceId: Int, text: String) { 176 | onView(withId(resourceId)).perform(scrollTo(), typeText(text)) 177 | } 178 | 179 | protected fun pressDeviceBack() { 180 | UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()).pressBack() 181 | } 182 | 183 | protected fun pressHomeBack() { 184 | UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()).pressHome() 185 | } 186 | 187 | protected fun performPressBack() { 188 | onView(isRoot()).perform(ViewActions.pressBack()) 189 | } 190 | 191 | protected fun performBackToolbar() { 192 | onView(allOf(backToolbarMatcher(), 193 | withParent(withId(R.id.toolbar)), 194 | isDisplayed())).perform(click()) 195 | } 196 | 197 | //Checks if the view is visible on the screen 198 | protected fun checkMatchesIsDisplayed(resourceId: Int) { 199 | onView(withId(resourceId)).check(matches(isDisplayed())) 200 | } 201 | 202 | protected fun checkMatchesIsDisplayed(resourceId: Int, resourceIdwithParent: Int) { 203 | onView(allOf(withId(resourceId), 204 | withParent(withId(resourceIdwithParent)))).check(matches(isDisplayed())) 205 | } 206 | 207 | //Search on screen, a view that contains the text 208 | //Checks if the view is visible on the screen 209 | protected fun checkMatchesIsDisplayed(text: String) { 210 | onView(withText(text)).check(matches(isDisplayed())) 211 | } 212 | 213 | /* Checks if the view is visible on the screen 214 | View preconditions: 215 | must be a descendant of ScrollView 216 | must have visibility set to View.VISIBLE */ 217 | protected fun checkMatchesIsDisplayedScrollTo(resourceId: Int) { 218 | onView(withId(resourceId)).perform(scrollTo(), 219 | closeSoftKeyboard()).check(matches(isDisplayed())) 220 | } 221 | 222 | //Checks if the view is not visible on the screen 223 | protected fun checkMatchesNotIsDisplayed(resourceId: Int) { 224 | onView(withId(resourceId)).check(matches(not(isDisplayed()))) 225 | } 226 | 227 | fun getString(@StringRes resId: Int): String { 228 | return activityTestRule.activity.getString(resId) 229 | } 230 | 231 | companion object { 232 | 233 | private fun backToolbarMatcher(): Matcher { 234 | return allOf( 235 | withParent(withClassName(`is`(Toolbar::class.java.name))), 236 | withClassName(anyOf( 237 | `is`(ImageButton::class.java.name), 238 | `is`(AppCompatImageButton::class.java.name) 239 | ))) 240 | } 241 | 242 | protected fun onItemClick(id: Int): BaseViewAction { 243 | return object : BaseViewAction() { 244 | override fun perform(uiController: UiController, view: View) { 245 | view.findViewById(id).performClick() 246 | } 247 | } 248 | } 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /app/src/androidTest/java/br/com/liveo/mvp/base/BaseViewAction.kt: -------------------------------------------------------------------------------- 1 | package br.com.liveo.mvp.base 2 | 3 | import android.support.test.espresso.ViewAction 4 | import android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom 5 | import android.view.View 6 | import org.hamcrest.Matcher 7 | 8 | /** 9 | * Created by Everdes Soares on 10/8/2017. 10 | */ 11 | 12 | abstract class BaseViewAction : ViewAction { 13 | override fun getConstraints(): Matcher { 14 | return isAssignableFrom(View::class.java) 15 | } 16 | 17 | override fun getDescription(): String { 18 | return "View is not type " + View::class.java 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/androidTest/java/br/com/liveo/mvp/screen/home/HomeActivityTest.kt: -------------------------------------------------------------------------------- 1 | package br.com.liveo.mvp.screen.home 2 | 3 | 4 | import android.support.test.espresso.Espresso.onView 5 | import android.support.test.espresso.action.ViewActions.click 6 | import android.support.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition 7 | import android.support.test.espresso.matcher.ViewMatchers.* 8 | import android.support.test.rule.ActivityTestRule 9 | import android.support.test.runner.AndroidJUnit4 10 | import android.support.v7.widget.RecyclerView 11 | import android.view.View 12 | import br.com.liveo.mvp.R 13 | import br.com.liveo.mvp.base.BaseActivityTestRule 14 | import org.hamcrest.Matchers.allOf 15 | import org.junit.Before 16 | import org.junit.Rule 17 | import org.junit.Test 18 | import org.junit.runner.RunWith 19 | 20 | /** 21 | * This class makes activity for [HomeActivity] 22 | * 23 | * @author Rudson Lima 24 | * @since 10/02/17 25 | */ 26 | 27 | @RunWith(AndroidJUnit4::class) 28 | class HomeActivityTest() : BaseActivityTestRule() { 29 | 30 | @get:Rule 31 | var mActivityTestRule: ActivityTestRule = ActivityTestRule( 32 | HomeActivity::class.java, true, false) 33 | 34 | override val activityTestRule: ActivityTestRule<*> 35 | get() = mActivityTestRule 36 | 37 | @Before 38 | fun setUp() { 39 | this.onInitHelperTestModule() 40 | } 41 | 42 | @Test 43 | fun onItemClick_zeroSuccess() { 44 | 45 | onView(allOf(withId(R.id.recycler_view), 46 | withParent(withId(R.id.swipe_container)), 47 | isDisplayed())).perform(actionOnItemAtPosition(0, click())) 48 | } 49 | 50 | @Test 51 | fun onItemClick_oneSuccess() { 52 | onView(allOf(withId(R.id.recycler_view), 53 | withParent(withId(R.id.swipe_container)), 54 | isDisplayed())).perform(actionOnItemAtPosition(1, click())) 55 | } 56 | 57 | @Test 58 | fun onItemClick_twoSuccess() { 59 | onView(allOf(withId(R.id.recycler_view), 60 | withParent(withId(R.id.swipe_container)), 61 | isDisplayed())).perform(actionOnItemAtPosition(2, click())) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 19 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/java/br/com/liveo/mvp/App.kt: -------------------------------------------------------------------------------- 1 | package br.com.liveo.mvp 2 | 3 | import android.app.Application 4 | import br.com.liveo.mvp.di.components.BaseApplicationComponent 5 | import br.com.liveo.mvp.di.components.DaggerApplicationComponent 6 | import br.com.liveo.mvp.di.modules.HelperModule 7 | import java.lang.ref.WeakReference 8 | 9 | /** 10 | * Created by rudsonlima on 8/31/17. 11 | */ 12 | 13 | class App : Application() { 14 | 15 | companion object { 16 | @JvmStatic 17 | lateinit var application: BaseApplicationComponent 18 | } 19 | 20 | override fun onCreate() { 21 | super.onCreate() 22 | application = DaggerApplicationComponent.builder() 23 | .helperModule(HelperModule(WeakReference(this))).build() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/br/com/liveo/mvp/base/BaseActivity.kt: -------------------------------------------------------------------------------- 1 | package br.com.liveo.mvp.base 2 | 3 | import android.databinding.DataBindingUtil 4 | import android.databinding.ViewDataBinding 5 | import android.os.Bundle 6 | import android.support.v4.content.ContextCompat 7 | import android.support.v7.app.AppCompatActivity 8 | import android.support.v7.widget.Toolbar 9 | import android.view.MenuItem 10 | import android.view.WindowManager 11 | import br.com.liveo.mvp.R 12 | 13 | /** 14 | * Created by rudsonlima on 8/29/17. 15 | */ 16 | 17 | abstract class BaseActivity : AppCompatActivity() { 18 | 19 | private var toolBarIcon: Int = -1 20 | private var toolBar: Toolbar? = null 21 | private var displayHome: Boolean = true 22 | private var toolBarTitle: Int = R.string.clear 23 | 24 | override fun onCreate(savedInstanceState: Bundle?) { 25 | super.onCreate(savedInstanceState) 26 | window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN) 27 | } 28 | 29 | protected fun bindView(layout: Int): ViewDataBinding = 30 | DataBindingUtil.setContentView(this, layout) 31 | 32 | //region Methods toolbar/appBar 33 | fun toolbar(toolBar: Toolbar?): BaseActivity { 34 | this.toolBar = toolBar 35 | return this 36 | } 37 | 38 | fun title(title: Int): BaseActivity { 39 | this.toolBarTitle = title 40 | return this 41 | } 42 | 43 | fun icon(icon: Int): BaseActivity { 44 | this.toolBarIcon = icon 45 | return this 46 | } 47 | 48 | fun displayHome(displayHome: Boolean): BaseActivity { 49 | this.displayHome = displayHome 50 | return this 51 | } 52 | 53 | fun builder(){ 54 | toolBar?.let { toolbar -> 55 | setSupportActionBar(toolBar) 56 | 57 | supportActionBar?.let { actionBar -> 58 | if (toolBarTitle != -1) { 59 | actionBar.title = getString(toolBarTitle) 60 | } 61 | 62 | actionBar.setDisplayShowHomeEnabled(displayHome) 63 | actionBar.setDisplayHomeAsUpEnabled(displayHome) 64 | if (toolBarIcon != -1 && displayHome) { 65 | toolbar.navigationIcon = ContextCompat.getDrawable(this, toolBarIcon) 66 | } 67 | } 68 | } 69 | } 70 | //endregion 71 | 72 | //region Methods Abstract 73 | abstract fun onInitView() 74 | 75 | abstract fun onInitInject() 76 | abstract fun finishActivity() 77 | //endregion 78 | 79 | override fun onOptionsItemSelected(item: MenuItem): Boolean { 80 | when (item.itemId) { 81 | android.R.id.home -> { 82 | onBackPressed() 83 | return true 84 | } 85 | } 86 | 87 | return super.onOptionsItemSelected(item) 88 | } 89 | 90 | override fun onBackPressed() { 91 | super.onBackPressed() 92 | this.finishActivity() 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /app/src/main/java/br/com/liveo/mvp/base/BaseAdapter.kt: -------------------------------------------------------------------------------- 1 | package br.com.liveo.mvp.base 2 | 3 | import android.support.v7.widget.RecyclerView 4 | import android.view.View 5 | import android.view.ViewGroup 6 | 7 | /** 8 | * Created by rudsonlima on 9/16/17. 9 | */ 10 | 11 | abstract class BaseAdapter : RecyclerView.Adapter() { 12 | 13 | protected var dataList: List? = null 14 | 15 | var onItemClick: ((View, Int, T) -> Unit)? = null 16 | 17 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder = 18 | onCreateViewHolderBase(parent, viewType) 19 | 20 | override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { 21 | this.onBindViewHolderBase(holder, position) 22 | 23 | holder.itemView.setOnClickListener { 24 | onItemClick?.invoke(it, holder.adapterPosition, 25 | getItem(holder.adapterPosition)) 26 | } 27 | } 28 | 29 | abstract fun onCreateViewHolderBase(parent: ViewGroup?, viewType: Int): RecyclerView.ViewHolder 30 | 31 | abstract fun onBindViewHolderBase(holder: RecyclerView.ViewHolder?, position: Int) 32 | 33 | override fun getItemCount(): Int { 34 | dataList?.size?.let { 35 | return it 36 | } 37 | 38 | return 0 39 | } 40 | 41 | fun getItem(index: Int): T { 42 | dataList?.get(index)?.let { 43 | return it 44 | } 45 | 46 | throw IllegalArgumentException("Item with index $index doesn't exist, dataSet is $dataList") 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/br/com/liveo/mvp/base/BaseDialog.kt: -------------------------------------------------------------------------------- 1 | package br.com.liveo.mvp.base 2 | 3 | import android.animation.Animator 4 | import android.animation.ObjectAnimator 5 | import android.animation.PropertyValuesHolder 6 | import android.os.Bundle 7 | import android.support.v7.app.AppCompatDialogFragment 8 | import android.view.View 9 | 10 | /** 11 | * Created by rudsonlima on 8/29/17. 12 | */ 13 | open class BaseDialog : AppCompatDialogFragment() { 14 | 15 | override fun onCreate(savedInstanceState: android.os.Bundle?) { 16 | super.onCreate(savedInstanceState) 17 | setStyle(android.support.v4.app.DialogFragment.STYLE_NO_TITLE, 18 | br.com.liveo.mvp.R.style.MyTheme_FloatingDialog) 19 | } 20 | 21 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 22 | super.onViewCreated(view, savedInstanceState) 23 | 24 | val dialog = dialog 25 | if (dialog != null) { 26 | dialog.requestWindowFeature(android.view.Window.FEATURE_NO_TITLE) 27 | val window = dialog.window 28 | if (window != null) { 29 | window.setSoftInputMode(android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN) 30 | 31 | window.setBackgroundDrawable(android.graphics.drawable.ColorDrawable( 32 | android.graphics.Color.TRANSPARENT)) 33 | } 34 | } 35 | } 36 | 37 | override fun onStart() { 38 | super.onStart() 39 | showAnimation() 40 | } 41 | 42 | private fun showAnimation() { 43 | if (dialog != null && dialog.window != null) { 44 | val decorView = dialog.window!!.decorView 45 | 46 | val scaleDown = ObjectAnimator.ofPropertyValuesHolder(decorView, 47 | PropertyValuesHolder.ofFloat("scaleX", 0.0f, 1.0f), 48 | PropertyValuesHolder.ofFloat("scaleY", 0.0f, 1.0f), 49 | PropertyValuesHolder.ofFloat("alpha", 0.0f, 1.0f)) 50 | scaleDown.duration = 300 51 | scaleDown.start() 52 | } 53 | } 54 | 55 | override fun onDestroy() { 56 | super.onDestroy() 57 | hideAnimation() 58 | } 59 | 60 | protected fun hideAnimation() { 61 | if (activity != null) { 62 | } 63 | 64 | if (dialog != null && dialog.window != null) { 65 | 66 | val decorView = dialog 67 | .window!! 68 | .decorView 69 | 70 | val scaleDown = ObjectAnimator.ofPropertyValuesHolder(decorView, 71 | PropertyValuesHolder.ofFloat("scaleX", 1.0f, 0.0f), 72 | PropertyValuesHolder.ofFloat("scaleY", 1.0f, 0.0f), 73 | PropertyValuesHolder.ofFloat("alpha", 1.0f, 0.0f)) 74 | scaleDown.addListener(object : Animator.AnimatorListener { 75 | override fun onAnimationEnd(animation: Animator) { 76 | if (activity != null) { 77 | dismiss() 78 | } 79 | } 80 | 81 | override fun onAnimationStart(animation: Animator) {} 82 | 83 | override fun onAnimationCancel(animation: Animator) {} 84 | 85 | override fun onAnimationRepeat(animation: Animator) {} 86 | }) 87 | scaleDown.duration = 300 88 | scaleDown.start() 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /app/src/main/java/br/com/liveo/mvp/base/BaseException.kt: -------------------------------------------------------------------------------- 1 | package br.com.liveo.mvp.base 2 | 3 | /** 4 | * Created by rudsonlima on 10/10/17. 5 | */ 6 | 7 | abstract class BaseException : Exception { 8 | var code = DEFAULT_CODE 9 | 10 | companion object { 11 | val DEFAULT_CODE = -1 12 | } 13 | 14 | abstract val stringRes: Int 15 | constructor(message: String) : super(message) {} 16 | 17 | constructor() { 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/br/com/liveo/mvp/base/BaseModel.kt: -------------------------------------------------------------------------------- 1 | package br.com.liveo.mvp.base 2 | 3 | import android.databinding.BindingAdapter 4 | import android.graphics.Bitmap 5 | import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory 6 | import android.text.TextUtils 7 | import android.widget.ImageView 8 | import android.widget.TextView 9 | import br.com.liveo.mvp.model.User 10 | import com.bumptech.glide.Glide 11 | import com.bumptech.glide.request.target.BitmapImageViewTarget 12 | 13 | /** 14 | * Created by rudsonlima on 8/29/17. 15 | */ 16 | 17 | open class BaseModel { 18 | 19 | companion object { 20 | @JvmStatic 21 | @BindingAdapter("imageLoadRounded") 22 | fun setImageLoadRounded(imageView: ImageView, urlImage: String) { 23 | if (!TextUtils.isEmpty(urlImage)) { 24 | 25 | Glide.with(imageView.context).load(urlImage).asBitmap().centerCrop().into(object : BitmapImageViewTarget(imageView) { 26 | override fun setResource(resource: Bitmap) { 27 | val circularBitmapDrawable = RoundedBitmapDrawableFactory.create(imageView.context.resources, resource) 28 | circularBitmapDrawable.isCircular = true 29 | imageView.setImageDrawable(circularBitmapDrawable) 30 | } 31 | }) 32 | } 33 | } 34 | 35 | @JvmStatic 36 | @BindingAdapter("nameWithLastName") 37 | fun setNameWithLastName(textView: TextView, user: User) { 38 | textView.text = java.lang.String.format("%s %s", user.name, user.lastName) 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/br/com/liveo/mvp/base/BasePresenter.kt: -------------------------------------------------------------------------------- 1 | package br.com.liveo.mvp.base 2 | 3 | 4 | import br.com.liveo.mvp.main.MainPresenter 5 | import br.com.liveo.mvp.main.MainView 6 | import io.reactivex.disposables.CompositeDisposable 7 | import io.reactivex.disposables.Disposable 8 | 9 | /** 10 | * Created by rudsonlima on 8/29/17. 11 | */ 12 | 13 | abstract class BasePresenter(protected val scheduler: BaseScheduler) : MainPresenter { 14 | 15 | 16 | private val mCompositeDisposable: CompositeDisposable = CompositeDisposable() 17 | 18 | protected fun addDisposable(disposable: Disposable) { 19 | this.mCompositeDisposable.add(disposable) 20 | } 21 | 22 | var mView: T? = null 23 | 24 | override val view: T? 25 | get() = this.mView 26 | 27 | override fun detachView() { 28 | this.clearDisposables() 29 | this.mView = null 30 | } 31 | 32 | override fun attach(view: T) { 33 | this.mView = view 34 | } 35 | 36 | protected fun clearDisposables() { 37 | this.mCompositeDisposable.clear() 38 | } 39 | } -------------------------------------------------------------------------------- /app/src/main/java/br/com/liveo/mvp/base/BaseScheduler.kt: -------------------------------------------------------------------------------- 1 | package br.com.liveo.mvp.base 2 | 3 | import io.reactivex.Scheduler 4 | 5 | /** 6 | * Created by rudsonlima on 8/29/17. 7 | */ 8 | interface BaseScheduler { 9 | 10 | fun computation(): Scheduler 11 | 12 | fun io(): Scheduler 13 | 14 | fun ui(): Scheduler 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/br/com/liveo/mvp/base/BaseView.kt: -------------------------------------------------------------------------------- 1 | package br.com.liveo.mvp.base 2 | 3 | import br.com.liveo.mvp.main.MainView 4 | 5 | /** 6 | * Created by rudsonlima on 8/29/17. 7 | */ 8 | 9 | interface BaseView : MainView { 10 | fun onLoading(loading: Boolean) 11 | fun onError(error: Throwable?) 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/br/com/liveo/mvp/data/local/Preferences.java: -------------------------------------------------------------------------------- 1 | package br.com.liveo.mvp.data.local; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | import android.preference.PreferenceManager; 6 | 7 | import java.lang.ref.WeakReference; 8 | 9 | public class Preferences implements PreferencesHelper { 10 | 11 | private WeakReference mContext; 12 | 13 | public Preferences(WeakReference context) { 14 | this.mContext = context; 15 | } 16 | 17 | @Override 18 | public String getToken() { 19 | return null; 20 | } 21 | 22 | @Override 23 | public void clearToken() { 24 | 25 | } 26 | 27 | @Override 28 | public void saveToken(String token) { 29 | 30 | } 31 | 32 | //region Methods Preferences 33 | private void putString(String key, String value) { 34 | SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext.get()); 35 | SharedPreferences.Editor editor = sharedPreferences.edit(); 36 | editor.putString(key, value); 37 | editor.apply(); 38 | } 39 | 40 | public String getString(String key) { 41 | SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext.get()); 42 | return sharedPreferences.getString(key, null); 43 | } 44 | 45 | private void putInt(String key, int value) { 46 | SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext.get()); 47 | SharedPreferences.Editor editor = sharedPreferences.edit(); 48 | editor.putInt(key, value); 49 | editor.apply(); 50 | } 51 | 52 | public int getInt(String key) { 53 | SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext.get()); 54 | return sharedPreferences.getInt(key, 0); 55 | } 56 | 57 | private void putBoolean(String key, boolean value) { 58 | SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext.get()); 59 | SharedPreferences.Editor editor = sharedPreferences.edit(); 60 | editor.putBoolean(key, value); 61 | editor.apply(); 62 | } 63 | 64 | private boolean getBoolean(String key) { 65 | SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext.get()); 66 | return sharedPreferences.getBoolean(key, false); 67 | } 68 | 69 | private boolean getBoolean(String key, boolean defaultValue) { 70 | SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext.get()); 71 | return sharedPreferences.getBoolean(key, defaultValue); 72 | } 73 | //endregion 74 | } 75 | -------------------------------------------------------------------------------- /app/src/main/java/br/com/liveo/mvp/data/local/PreferencesHelper.java: -------------------------------------------------------------------------------- 1 | package br.com.liveo.mvp.data.local; 2 | 3 | /** 4 | * Created by rudsonlima on 10/2/17. 5 | */ 6 | 7 | public interface PreferencesHelper { 8 | 9 | String getToken(); 10 | void clearToken(); 11 | void saveToken(String token); 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/br/com/liveo/mvp/data/local/endpoint/EndPointJson.kt: -------------------------------------------------------------------------------- 1 | package br.com.liveo.mvp.data.local.endpoint 2 | 3 | /** 4 | * Created by rudsonlima on 10/20/17. 5 | */ 6 | 7 | object EndPointJson { 8 | 9 | //region Methods Users 10 | internal val USER_JSON_SUCCESS = "{\n" + 11 | "\t\"page\": 2,\n" + 12 | "\t\"per_page\": 3,\n" + 13 | "\t\"total\": 12,\n" + 14 | "\t\"total_pages\": 4,\n" + 15 | "\t\"data\": [{\n" + 16 | "\t\t\"id\": 4,\n" + 17 | "\t\t\"first_name\": \"Eve\",\n" + 18 | "\t\t\"last_name\": \"Holt\",\n" + 19 | "\t\t\"avatar\": \"https://s3.amazonaws.com/uifaces/faces/twitter/marcoramires/128.jpg\"\n" + 20 | "\t}, {\n" + 21 | "\t\t\"id\": 5,\n" + 22 | "\t\t\"first_name\": \"Charles\",\n" + 23 | "\t\t\"last_name\": \"Morris\",\n" + 24 | "\t\t\"avatar\": \"https://s3.amazonaws.com/uifaces/faces/twitter/stephenmoon/128.jpg\"\n" + 25 | "\t}, {\n" + 26 | "\t\t\"id\": 6,\n" + 27 | "\t\t\"first_name\": \"Tracey\",\n" + 28 | "\t\t\"last_name\": \"Ramos\",\n" + 29 | "\t\t\"avatar\": \"https://s3.amazonaws.com/uifaces/faces/twitter/bigmancho/128.jpg\"\n" + 30 | "\t}]\n" + 31 | "}" 32 | 33 | internal val USER_JSON_FAIL = "{\n" + 34 | "\t\"errorMessage\": \"No records.\"\n" + 35 | "}" 36 | //endregion 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/java/br/com/liveo/mvp/data/local/endpoint/EndPointMocked.kt: -------------------------------------------------------------------------------- 1 | package br.com.liveo.mvp.data.local.endpoint 2 | 3 | import br.com.liveo.mvp.data.remote.endpoint.EndPoint 4 | import br.com.liveo.mvp.model.domain.UserResponse 5 | import com.google.gson.Gson 6 | import io.reactivex.Observable 7 | 8 | /** 9 | * Created by rudsonlima on 8/29/17. 10 | */ 11 | 12 | class EndPointMocked : EndPoint { 13 | 14 | override fun fetchUsers(page: Int): Observable { 15 | val json = if (page != -1) EndPointJson.USER_JSON_SUCCESS else 16 | EndPointJson.USER_JSON_FAIL 17 | 18 | return Observable.just(Gson().fromJson( 19 | (json), UserResponse::class.java)) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/br/com/liveo/mvp/data/remote/ApiHelper.kt: -------------------------------------------------------------------------------- 1 | package br.com.liveo.mvp.data.remote 2 | 3 | import br.com.liveo.mvp.data.remote.endpoint.EndPoint 4 | import br.com.liveo.mvp.data.remote.endpoint.EndPointHelper 5 | import br.com.liveo.mvp.model.domain.UserResponse 6 | import io.reactivex.Single 7 | 8 | /** 9 | * Created by rudsonlima on 10/9/17. 10 | */ 11 | 12 | class ApiHelper(private val mEndPoint: EndPoint) : EndPointHelper { 13 | 14 | override fun fetchUsers(page: Int): Single { 15 | 16 | try { 17 | return if (page != -1) { 18 | mEndPoint.fetchUsers(page).singleOrError() 19 | } else Single.error(Exception()) 20 | 21 | } catch (error: Exception) { 22 | return Single.error(error) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/br/com/liveo/mvp/data/remote/endpoint/EndPoint.kt: -------------------------------------------------------------------------------- 1 | package br.com.liveo.mvp.data.remote.endpoint 2 | 3 | import br.com.liveo.mvp.model.domain.UserResponse 4 | import io.reactivex.Observable 5 | import retrofit2.http.GET 6 | import retrofit2.http.Query 7 | 8 | /** 9 | * Created by rudsonlima on 8/29/17. 10 | */ 11 | 12 | interface EndPoint { 13 | 14 | @GET("api/users") 15 | fun fetchUsers(@Query("page") page: Int): Observable 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/br/com/liveo/mvp/data/remote/endpoint/EndPointHelper.kt: -------------------------------------------------------------------------------- 1 | package br.com.liveo.mvp.data.remote.endpoint 2 | 3 | import br.com.liveo.mvp.model.domain.UserResponse 4 | import io.reactivex.Single 5 | 6 | /** 7 | * Created by rudsonlima on 8/29/17. 8 | */ 9 | 10 | interface EndPointHelper { 11 | 12 | fun fetchUsers(page: Int): Single 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/br/com/liveo/mvp/data/remote/interceptor/RequestInterceptor.kt: -------------------------------------------------------------------------------- 1 | package br.com.liveo.mvp.data.remote.interceptor 2 | 3 | import android.text.TextUtils 4 | import br.com.liveo.mvp.data.local.PreferencesHelper 5 | import okhttp3.Interceptor 6 | import okhttp3.Request 7 | import okhttp3.Response 8 | import java.io.IOException 9 | import javax.inject.Inject 10 | import javax.inject.Singleton 11 | 12 | /** 13 | * Created by rudsonlima on 10/2/17. 14 | */ 15 | 16 | @Singleton 17 | class RequestInterceptor @Inject 18 | constructor(private val preferencesHelper: PreferencesHelper) : Interceptor { 19 | 20 | @Throws(IOException::class) 21 | override fun intercept(chain: Interceptor.Chain): Response { 22 | 23 | val requestBuilder: Request.Builder = chain.request().newBuilder() 24 | requestBuilder.addHeader("Content-Type", "application/json") 25 | 26 | val token = preferencesHelper.token 27 | if (!TextUtils.isEmpty(token)) { 28 | requestBuilder.addHeader("token", token) 29 | } 30 | 31 | val request = requestBuilder.build() 32 | return chain.proceed(request) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/java/br/com/liveo/mvp/data/scheduler/InjectionScheduler.kt: -------------------------------------------------------------------------------- 1 | package br.com.liveo.mvp.data.scheduler 2 | 3 | import br.com.liveo.mvp.base.BaseScheduler 4 | 5 | /** 6 | * Created by rudsonlima on 8/29/17. 7 | */ 8 | object InjectionScheduler { 9 | fun schedulerProvider(): BaseScheduler = SchedulerProvider.instance 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/br/com/liveo/mvp/data/scheduler/SchedulerProvider.kt: -------------------------------------------------------------------------------- 1 | package br.com.liveo.mvp.data.scheduler 2 | 3 | import br.com.liveo.mvp.base.BaseScheduler 4 | import io.reactivex.Scheduler 5 | import io.reactivex.android.schedulers.AndroidSchedulers 6 | import io.reactivex.schedulers.Schedulers 7 | 8 | /** 9 | * Created by rudsonlima on 8/29/17. 10 | */ 11 | 12 | class SchedulerProvider private constructor() : BaseScheduler { 13 | 14 | override fun io(): Scheduler = Schedulers.io() 15 | override fun ui(): Scheduler = AndroidSchedulers.mainThread() 16 | override fun computation(): Scheduler = Schedulers.computation() 17 | 18 | companion object { 19 | 20 | private var INSTANCE: SchedulerProvider? = null 21 | 22 | val instance: SchedulerProvider 23 | @Synchronized get() { 24 | if (INSTANCE == null) { 25 | INSTANCE = SchedulerProvider() 26 | } 27 | return INSTANCE as SchedulerProvider 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/br/com/liveo/mvp/data/scheduler/TestSchedulerProvider.kt: -------------------------------------------------------------------------------- 1 | package br.com.liveo.mvp.data.scheduler 2 | 3 | import br.com.liveo.mvp.base.BaseScheduler 4 | import io.reactivex.Scheduler 5 | import io.reactivex.schedulers.TestScheduler 6 | 7 | class TestSchedulerProvider(private val mTestScheduler: TestScheduler) : BaseScheduler { 8 | 9 | override fun computation(): Scheduler = mTestScheduler 10 | 11 | override fun io(): Scheduler = mTestScheduler 12 | 13 | override fun ui(): Scheduler = mTestScheduler 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/br/com/liveo/mvp/di/components/ApplicationComponent.kt: -------------------------------------------------------------------------------- 1 | package br.com.liveo.mvp.di.components 2 | 3 | import br.com.liveo.mvp.di.modules.HelperModule 4 | import dagger.Component 5 | import javax.inject.Singleton 6 | 7 | /** 8 | * Created by rudsonlima on 9/2/17. 9 | */ 10 | 11 | @Singleton 12 | @Component(modules = [(HelperModule::class)]) 13 | interface ApplicationComponent : BaseApplicationComponent 14 | -------------------------------------------------------------------------------- /app/src/main/java/br/com/liveo/mvp/di/components/ApplicationTestComponent.kt: -------------------------------------------------------------------------------- 1 | package br.com.liveo.mvp.di.components 2 | 3 | import br.com.liveo.mvp.di.modules.HelperTestModule 4 | import dagger.Component 5 | import javax.inject.Singleton 6 | 7 | /** 8 | * Created by rudsonlima on 9/2/17. 9 | */ 10 | 11 | @Singleton 12 | @Component(modules = [(HelperTestModule::class)]) 13 | interface ApplicationTestComponent : BaseApplicationComponent 14 | -------------------------------------------------------------------------------- /app/src/main/java/br/com/liveo/mvp/di/components/BaseApplicationComponent.kt: -------------------------------------------------------------------------------- 1 | package br.com.liveo.mvp.di.components 2 | 3 | import br.com.liveo.mvp.screen.home.di.HomeComponent 4 | import br.com.liveo.mvp.screen.home.di.HomeModule 5 | 6 | /** 7 | * Created by rudsonlima on 10/9/17. 8 | */ 9 | 10 | interface BaseApplicationComponent { 11 | fun module(homeModule: HomeModule): HomeComponent 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/br/com/liveo/mvp/di/modules/HelperModule.kt: -------------------------------------------------------------------------------- 1 | package br.com.liveo.mvp.di.modules 2 | 3 | 4 | import android.content.Context 5 | import br.com.liveo.mvp.BuildConfig 6 | import br.com.liveo.mvp.data.local.Preferences 7 | import br.com.liveo.mvp.data.local.PreferencesHelper 8 | import br.com.liveo.mvp.data.remote.ApiHelper 9 | import br.com.liveo.mvp.data.remote.endpoint.EndPoint 10 | import br.com.liveo.mvp.data.remote.endpoint.EndPointHelper 11 | import br.com.liveo.mvp.data.remote.interceptor.RequestInterceptor 12 | import dagger.Module 13 | import dagger.Provides 14 | import okhttp3.Interceptor 15 | import okhttp3.OkHttpClient 16 | import okhttp3.logging.HttpLoggingInterceptor 17 | import retrofit2.Retrofit 18 | import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory 19 | import retrofit2.converter.gson.GsonConverterFactory 20 | import java.lang.ref.WeakReference 21 | import java.util.concurrent.TimeUnit 22 | import javax.inject.Singleton 23 | 24 | /** 25 | * Created by rudsonlima on 9/4/17. 26 | */ 27 | @Module 28 | class HelperModule(private val mContext: WeakReference) { 29 | 30 | companion object { 31 | private val CONNECT_TIMEOUT_IN_MS = 300000 32 | } 33 | 34 | @Provides 35 | @Singleton 36 | internal fun provideContext(): WeakReference { 37 | return mContext 38 | } 39 | 40 | @Provides 41 | @Singleton 42 | internal fun proviveRequestInterceptor(preferencesHelper: PreferencesHelper): Interceptor { 43 | return RequestInterceptor(preferencesHelper) 44 | } 45 | 46 | @Provides 47 | @Singleton 48 | internal fun provideOkHttpClient(requestInterceptor: RequestInterceptor): OkHttpClient { 49 | val loggingInterceptor = HttpLoggingInterceptor() 50 | loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY 51 | 52 | return OkHttpClient.Builder() 53 | .connectTimeout(CONNECT_TIMEOUT_IN_MS.toLong(), TimeUnit.MILLISECONDS) 54 | .addInterceptor(loggingInterceptor) 55 | .addInterceptor(requestInterceptor) 56 | .build() 57 | } 58 | 59 | @Singleton 60 | @Provides 61 | internal fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit { 62 | return Retrofit.Builder() 63 | .baseUrl(BuildConfig.BASE_URL) 64 | .addConverterFactory(GsonConverterFactory.create()) 65 | .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) 66 | .client(okHttpClient) 67 | .build() 68 | } 69 | 70 | @Singleton 71 | @Provides 72 | internal fun provideEndPoint(retrofit: Retrofit): EndPoint { 73 | return retrofit.create(EndPoint::class.java) 74 | } 75 | 76 | @Singleton 77 | @Provides 78 | internal fun provideEndPointHelper(endPoint: EndPoint): EndPointHelper { 79 | return ApiHelper(endPoint) 80 | } 81 | 82 | @Provides 83 | @Singleton 84 | internal fun providePreferencesHelper(context: WeakReference): PreferencesHelper { 85 | return Preferences(context) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /app/src/main/java/br/com/liveo/mvp/di/modules/HelperTestModule.kt: -------------------------------------------------------------------------------- 1 | package br.com.liveo.mvp.di.modules 2 | 3 | 4 | import android.content.Context 5 | import br.com.liveo.mvp.data.local.Preferences 6 | import br.com.liveo.mvp.data.local.PreferencesHelper 7 | import br.com.liveo.mvp.data.local.endpoint.EndPointMocked 8 | import br.com.liveo.mvp.data.remote.ApiHelper 9 | import br.com.liveo.mvp.data.remote.endpoint.EndPoint 10 | import br.com.liveo.mvp.data.remote.endpoint.EndPointHelper 11 | import dagger.Module 12 | import dagger.Provides 13 | import java.lang.ref.WeakReference 14 | import javax.inject.Singleton 15 | 16 | /** 17 | * Created by rudsonlima on 9/4/17. 18 | */ 19 | @Module 20 | class HelperTestModule(context: WeakReference?) { 21 | 22 | private val endPoint: EndPoint 23 | lateinit var preferencesHelper: PreferencesHelper 24 | 25 | constructor() : this(null) 26 | 27 | init { 28 | this.endPoint = EndPointMocked() 29 | 30 | context?.let { 31 | preferencesHelper = Preferences(it) 32 | } 33 | } 34 | 35 | @Singleton 36 | @Provides 37 | internal fun providePreferencesHelper(): PreferencesHelper { 38 | return this.preferencesHelper 39 | } 40 | 41 | @Singleton 42 | @Provides 43 | internal fun provideEndPoint(): EndPoint { 44 | return this.endPoint 45 | } 46 | 47 | @Singleton 48 | @Provides 49 | internal fun provideEndPointHelper(endPoint: EndPoint): EndPointHelper { 50 | return ApiHelper(endPoint) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/java/br/com/liveo/mvp/di/scope/ActivityScoped.kt: -------------------------------------------------------------------------------- 1 | package br.com.liveo.mvp.di.scope 2 | 3 | import javax.inject.Scope 4 | 5 | /** 6 | * This is a Dagger scope for identify each activity scope 7 | 8 | * Created by rudsonlima on 8/31/17. 9 | */ 10 | 11 | @Scope 12 | @Retention(AnnotationRetention.RUNTIME) 13 | annotation class ActivityScoped 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/java/br/com/liveo/mvp/di/scope/FragmentScoped.kt: -------------------------------------------------------------------------------- 1 | package br.com.liveo.mvp.di.scope 2 | 3 | import javax.inject.Scope 4 | 5 | /** 6 | * This is a Dagger scope for identify each fragment scope 7 | * 8 | * Created by rudsonlima on 8/31/17. 9 | */ 10 | @Scope 11 | @Retention(AnnotationRetention.RUNTIME) 12 | @Target(AnnotationTarget.CLASS, AnnotationTarget.FILE, AnnotationTarget.FUNCTION, 13 | AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER) 14 | annotation class FragmentScoped 15 | -------------------------------------------------------------------------------- /app/src/main/java/br/com/liveo/mvp/di/scope/LocalScoped.kt: -------------------------------------------------------------------------------- 1 | package br.com.liveo.mvp.di.scope 2 | 3 | import javax.inject.Qualifier 4 | 5 | /** 6 | * This is a Dagger scope to identify LocalScoped repository 7 | * 8 | * Created by rudsonlima on 8/31/17. 9 | */ 10 | 11 | @Qualifier 12 | @MustBeDocumented 13 | @Retention(AnnotationRetention.RUNTIME) 14 | annotation class LocalScoped 15 | -------------------------------------------------------------------------------- /app/src/main/java/br/com/liveo/mvp/di/scope/RemoteScoped.kt: -------------------------------------------------------------------------------- 1 | package br.com.liveo.mvp.di.scope 2 | 3 | import javax.inject.Qualifier 4 | 5 | /** 6 | * This is a Dagger scope to identify RemoteScoped repository 7 | * 8 | * Created by rudsonlima on 8/31/17. 9 | */ 10 | 11 | @Qualifier 12 | @MustBeDocumented 13 | @Retention(AnnotationRetention.RUNTIME) 14 | annotation class RemoteScoped 15 | -------------------------------------------------------------------------------- /app/src/main/java/br/com/liveo/mvp/extension/ActivityExtension.kt: -------------------------------------------------------------------------------- 1 | package br.com.liveo.mvp.extension 2 | 3 | import android.app.Activity 4 | import android.content.Intent 5 | import android.os.Bundle 6 | import android.support.v4.app.ActivityCompat 7 | import android.support.v4.app.ActivityOptionsCompat 8 | import br.com.liveo.mvp.R 9 | 10 | /** 11 | * Created by rudsonlima on 3/11/18. 12 | */ 13 | 14 | enum class ActivityAnimation { 15 | TRANSLATE_LEFT, TRANSLATE_RIGHT, TRANSLATE_UP, TRANSLATE_DOWN, TRANSLATE_FADE 16 | } 17 | 18 | inline fun Activity.launchActivity(animation: ActivityAnimation? = 19 | ActivityAnimation.TRANSLATE_LEFT) { 20 | val intent = Intent(this, T::class.java) 21 | startActivity(intent) 22 | 23 | val animations = getAnimation(animation) 24 | overridePendingTransition(animations[0], animations[1]) 25 | } 26 | 27 | inline fun Activity.launchActivity(extras: Bundle? = null, 28 | animation: ActivityAnimation? = 29 | ActivityAnimation.TRANSLATE_LEFT) { 30 | val intent = Intent(this, T::class.java) 31 | 32 | extras?.let { 33 | intent.putExtras(it) 34 | } 35 | 36 | startActivity(intent) 37 | 38 | val animations = getAnimation(animation) 39 | overridePendingTransition(animations[0], animations[1]) 40 | } 41 | 42 | inline fun Activity.launchActivity(extras: Bundle? = null, 43 | intentFlags: Int, 44 | animation: ActivityAnimation? = 45 | ActivityAnimation.TRANSLATE_LEFT) { 46 | val intent = Intent(this, T::class.java) 47 | intent.flags = intentFlags 48 | 49 | extras?.let { 50 | intent.putExtras(it) 51 | } 52 | 53 | startActivity(intent) 54 | 55 | val animations = getAnimation(animation) 56 | overridePendingTransition(animations[0], animations[1]) 57 | } 58 | 59 | inline fun Activity.launchActivity( 60 | extras: Bundle? = null, activityOptions: ActivityOptionsCompat, 61 | animation: ActivityAnimation? = 62 | ActivityAnimation.TRANSLATE_LEFT) { 63 | 64 | val intent = Intent(this, T::class.java) 65 | 66 | extras?.let { 67 | intent.putExtras(it) 68 | } 69 | 70 | ActivityCompat.startActivity(this, intent, activityOptions.toBundle()) 71 | 72 | val animations = getAnimation(animation) 73 | overridePendingTransition(animations[0], animations[1]) 74 | } 75 | 76 | inline fun Activity.launchActivityForResult( 77 | requestCode: Int, animation: ActivityAnimation? = 78 | ActivityAnimation.TRANSLATE_LEFT) { 79 | 80 | val intent = Intent(this, T::class.java) 81 | 82 | startActivityForResult(intent, requestCode) 83 | 84 | val animations = getAnimation(animation) 85 | overridePendingTransition(animations[0], animations[1]) 86 | } 87 | 88 | inline fun Activity.launchActivityForResult( 89 | requestCode: Int, extras: Bundle? = null, animation: ActivityAnimation? = 90 | ActivityAnimation.TRANSLATE_LEFT) { 91 | 92 | val intent = Intent(this, T::class.java) 93 | 94 | extras?.let { 95 | intent.putExtras(it) 96 | } 97 | 98 | startActivityForResult(intent, requestCode) 99 | 100 | val animations = getAnimation(animation) 101 | overridePendingTransition(animations[0], animations[1]) 102 | } 103 | 104 | inline fun Activity.launchActivityForResult( 105 | requestCode: Int, extras: Bundle? = null, 106 | activityOptions: ActivityOptionsCompat, 107 | animation: ActivityAnimation? = 108 | ActivityAnimation.TRANSLATE_LEFT) { 109 | 110 | val intent = Intent(this, T::class.java) 111 | 112 | extras?.let { 113 | intent.putExtras(it) 114 | } 115 | 116 | ActivityCompat.startActivityForResult(this, intent, requestCode, 117 | activityOptions.toBundle()) 118 | 119 | val animations = getAnimation(animation) 120 | overridePendingTransition(animations[0], animations[1]) 121 | } 122 | 123 | fun Activity.finishActivity(animation: ActivityAnimation? = ActivityAnimation.TRANSLATE_RIGHT) { 124 | finish() 125 | val animations = getAnimation(animation) 126 | overridePendingTransition(animations[0], animations[1]) 127 | } 128 | 129 | fun getAnimation(animation: ActivityAnimation?): IntArray { 130 | val exitAnim: Int 131 | val enterAnim: Int 132 | 133 | when (animation) { 134 | 135 | ActivityAnimation.TRANSLATE_UP -> { 136 | enterAnim = R.anim.translate_slide_up 137 | exitAnim = R.anim.translate_no_change 138 | } 139 | 140 | ActivityAnimation.TRANSLATE_DOWN -> { 141 | enterAnim = R.anim.translate_no_change 142 | exitAnim = R.anim.translate_slide_down 143 | } 144 | 145 | ActivityAnimation.TRANSLATE_RIGHT -> { 146 | enterAnim = R.anim.translate_right_enter 147 | exitAnim = R.anim.translate_right_exit 148 | } 149 | 150 | ActivityAnimation.TRANSLATE_FADE -> { 151 | enterAnim = R.anim.translate_fade_in 152 | exitAnim = R.anim.translate_fade_out 153 | } 154 | 155 | else -> { 156 | enterAnim = R.anim.translate_left_enter 157 | exitAnim = R.anim.translate_left_exit 158 | } 159 | } 160 | 161 | return intArrayOf(enterAnim, exitAnim) 162 | } -------------------------------------------------------------------------------- /app/src/main/java/br/com/liveo/mvp/extension/ViewExtension.kt: -------------------------------------------------------------------------------- 1 | package br.com.liveo.mvp.extension 2 | 3 | import android.content.Context 4 | import android.support.design.widget.Snackbar 5 | import android.view.View 6 | import android.view.View.GONE 7 | import android.view.View.VISIBLE 8 | import android.view.inputmethod.InputMethodManager 9 | import android.widget.Toast 10 | 11 | /** 12 | * Created by rudsonlima on 25/04/18. 13 | */ 14 | inline fun View.snack(messageResourceId: Int, length: Int = Snackbar.LENGTH_LONG, f: Snackbar.() -> Unit = {}) { 15 | this.snack(context.getString(messageResourceId), length, f) 16 | } 17 | 18 | inline fun View.snack(message: String, length: Int = Snackbar.LENGTH_LONG, f: Snackbar.() -> Unit = {}) { 19 | val snack = Snackbar.make(this, message, length) 20 | snack.f() 21 | snack.show() 22 | } 23 | 24 | fun Snackbar.action(actionLabelResourceId: Int, color: Int? = null, listener: (View) -> Unit) { 25 | val actionLabel = context.getString(actionLabelResourceId) 26 | setAction(actionLabel, listener) 27 | color?.let { setActionTextColor(it) } 28 | } 29 | 30 | fun Context.toastShort(text: CharSequence) = Toast.makeText(this, text, Toast.LENGTH_SHORT).show() 31 | 32 | fun Context.toastShort(resId: Int) = Toast.makeText(this, resId, Toast.LENGTH_SHORT).show() 33 | 34 | fun Context.toastLong(text: CharSequence) = Toast.makeText(this, text, Toast.LENGTH_LONG).show() 35 | 36 | fun Context.toastLong(resId: Int) = Toast.makeText(this, resId, Toast.LENGTH_LONG).show() 37 | 38 | fun View.hideSoftKeyboard(context: Context) { 39 | val inputMethodManager = context.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager 40 | inputMethodManager?.hideSoftInputFromWindow(this.windowToken, 0) 41 | } 42 | 43 | fun View.visible(isVisible: Boolean) { 44 | this.visibility = if (isVisible) VISIBLE else GONE 45 | } -------------------------------------------------------------------------------- /app/src/main/java/br/com/liveo/mvp/main/MainPresenter.kt: -------------------------------------------------------------------------------- 1 | package br.com.liveo.mvp.main 2 | 3 | import br.com.liveo.mvp.screen.home.HomeContract 4 | 5 | /** 6 | * Created by rudsonlima on 8/29/17. 7 | */ 8 | 9 | interface MainPresenter { 10 | fun attach(view: T) 11 | fun detachView() 12 | 13 | val view: T? 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/br/com/liveo/mvp/main/MainView.kt: -------------------------------------------------------------------------------- 1 | package br.com.liveo.mvp.main 2 | 3 | /** 4 | * Created by rudsonlima on 8/29/17. 5 | */ 6 | 7 | interface MainView 8 | -------------------------------------------------------------------------------- /app/src/main/java/br/com/liveo/mvp/model/User.kt: -------------------------------------------------------------------------------- 1 | package br.com.liveo.mvp.model 2 | 3 | import android.os.Parcel 4 | import android.os.Parcelable 5 | import br.com.liveo.mvp.base.BaseModel 6 | import com.google.gson.annotations.SerializedName 7 | 8 | /** 9 | * Created by rudsonlima on 8/29/17. 10 | */ 11 | 12 | class User(var id: Int, 13 | @SerializedName("first_name") 14 | var name: String, 15 | @SerializedName("last_name") 16 | var lastName: String, 17 | var avatar: String) : BaseModel(), Parcelable { 18 | 19 | constructor(source: Parcel) : this( 20 | source.readInt(), 21 | source.readString(), 22 | source.readString(), 23 | source.readString() 24 | ) 25 | 26 | override fun describeContents() = 0 27 | 28 | override fun writeToParcel(dest: Parcel, flags: Int) = with(dest) { 29 | writeInt(id) 30 | writeString(name) 31 | writeString(lastName) 32 | writeString(avatar) 33 | } 34 | 35 | companion object { 36 | @JvmField 37 | val CREATOR: Parcelable.Creator = object : Parcelable.Creator { 38 | override fun createFromParcel(source: Parcel): User = User(source) 39 | override fun newArray(size: Int): Array = arrayOfNulls(size) 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/br/com/liveo/mvp/model/domain/UserResponse.kt: -------------------------------------------------------------------------------- 1 | package br.com.liveo.mvp.model.domain 2 | 3 | import br.com.liveo.mvp.model.User 4 | import com.google.gson.annotations.SerializedName 5 | 6 | /** 7 | * Created by rudsonlima on 8/29/17. 8 | */ 9 | 10 | class UserResponse( 11 | 12 | var page: Int, 13 | var total: Int, 14 | 15 | @SerializedName("per_page") 16 | var perPage: Int, 17 | 18 | @SerializedName("total_pages") 19 | var totalPages: Int, 20 | 21 | @SerializedName("data") 22 | var list: List) 23 | -------------------------------------------------------------------------------- /app/src/main/java/br/com/liveo/mvp/screen/home/HomeActivity.kt: -------------------------------------------------------------------------------- 1 | package br.com.liveo.mvp.screen.home 2 | 3 | import android.os.Bundle 4 | import android.support.v4.widget.SwipeRefreshLayout 5 | import android.support.v7.widget.LinearLayoutManager 6 | import br.com.liveo.mvp.App 7 | import br.com.liveo.mvp.R 8 | import br.com.liveo.mvp.base.BaseActivity 9 | import br.com.liveo.mvp.databinding.ActivityHomeBinding 10 | import br.com.liveo.mvp.di.scope.ActivityScoped 11 | import br.com.liveo.mvp.extension.toastShort 12 | import br.com.liveo.mvp.model.domain.UserResponse 13 | import br.com.liveo.mvp.screen.home.di.HomeModule 14 | import javax.inject.Inject 15 | 16 | /** 17 | * Created by rudsonlima on 8/29/17. 18 | */ 19 | @ActivityScoped 20 | class HomeActivity : BaseActivity(), HomeContract.View { 21 | 22 | private var mBinding: ActivityHomeBinding? = null 23 | 24 | @Inject 25 | lateinit var mHomePresenter: HomePresenter 26 | 27 | override fun onCreate(savedInstanceState: Bundle?) { 28 | super.onCreate(savedInstanceState) 29 | this.onInitInject() 30 | } 31 | 32 | override fun onPostCreate(savedInstanceState: Bundle?) { 33 | super.onPostCreate(savedInstanceState) 34 | this.fetchUsers() 35 | } 36 | 37 | override fun onInitInject() { 38 | App.application.module(HomeModule()).inject(this) 39 | 40 | this.onInitView() 41 | } 42 | 43 | override fun onInitView() { 44 | mBinding = this.bindView(R.layout.activity_home) as ActivityHomeBinding 45 | 46 | this.toolbar(mBinding?.includeToolbar?.toolbar). 47 | icon(R.drawable.ic_arrow_back). 48 | title(R.string.app_name). 49 | builder() 50 | 51 | mBinding?.recyclerView?.layoutManager = LinearLayoutManager(this) 52 | 53 | mBinding?.swipeContainer?.setOnRefreshListener(onRefresh) 54 | mBinding?.swipeContainer?.setColorSchemeResources(R.color.accent, R.color.accent, 55 | R.color.accent, R.color.accent) 56 | } 57 | 58 | private val onRefresh = SwipeRefreshLayout.OnRefreshListener { fetchUsers() } 59 | 60 | private fun fetchUsers() { 61 | this.mHomePresenter.attach(this) 62 | this.mHomePresenter.fetchUsers() 63 | } 64 | 65 | override fun onLoading(loading: Boolean) { 66 | mBinding?.swipeContainer?.isRefreshing = loading 67 | } 68 | 69 | override fun onError(error: Throwable?) {} 70 | 71 | override val page: Int 72 | get() = 2 73 | 74 | override fun onUserResponse(userResponse: UserResponse) { 75 | val adapter = HomeAdapter(userResponse) 76 | 77 | adapter.onItemClick = { _, _, user -> 78 | this.toastShort(user.name) 79 | } 80 | 81 | mBinding?.recyclerView?.adapter = adapter 82 | } 83 | 84 | override fun onDestroy() { 85 | super.onDestroy() 86 | mHomePresenter.detachView() 87 | } 88 | 89 | override fun finishActivity() { 90 | finish() 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /app/src/main/java/br/com/liveo/mvp/screen/home/HomeAdapter.kt: -------------------------------------------------------------------------------- 1 | package br.com.liveo.mvp.screen.home 2 | 3 | import android.support.v7.widget.RecyclerView 4 | import android.view.LayoutInflater 5 | import android.view.ViewGroup 6 | import br.com.liveo.mvp.BR 7 | import br.com.liveo.mvp.R 8 | import br.com.liveo.mvp.base.BaseAdapter 9 | import br.com.liveo.mvp.model.User 10 | import br.com.liveo.mvp.model.domain.UserResponse 11 | 12 | /** 13 | * Created by rudsonlima on 8/29/17. 14 | */ 15 | 16 | class HomeAdapter(userResponse: UserResponse) : BaseAdapter() { 17 | 18 | init { 19 | this.dataList = userResponse.list 20 | } 21 | 22 | override fun onCreateViewHolderBase(parent: ViewGroup?, viewType: Int): RecyclerView.ViewHolder { 23 | return HomeViewHolder(LayoutInflater.from(parent?.context).inflate( 24 | R.layout.activity_home_item, parent, false)) 25 | } 26 | 27 | override fun onBindViewHolderBase(holder: RecyclerView.ViewHolder?, position: Int) { 28 | val user = dataList?.get(position) 29 | 30 | (holder as HomeViewHolder).binding?.setVariable(BR.user, user) 31 | holder.binding?.executePendingBindings() 32 | 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/java/br/com/liveo/mvp/screen/home/HomeContract.kt: -------------------------------------------------------------------------------- 1 | package br.com.liveo.mvp.screen.home 2 | 3 | import br.com.liveo.mvp.base.BaseView 4 | import br.com.liveo.mvp.main.MainPresenter 5 | import br.com.liveo.mvp.model.domain.UserResponse 6 | import io.reactivex.Single 7 | 8 | /** 9 | * Created by rudsonlima on 8/29/17. 10 | */ 11 | 12 | interface HomeContract { 13 | 14 | interface View : BaseView { 15 | val page: Int 16 | 17 | fun onUserResponse(userResponse: UserResponse) 18 | } 19 | 20 | interface Presenter : MainPresenter { 21 | fun fetchUsers() 22 | } 23 | 24 | interface Repository { 25 | fun fetchUsers(page: Int): Single 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/br/com/liveo/mvp/screen/home/HomePresenter.kt: -------------------------------------------------------------------------------- 1 | package br.com.liveo.mvp.screen.home 2 | 3 | import br.com.liveo.mvp.base.BasePresenter 4 | import br.com.liveo.mvp.base.BaseScheduler 5 | import javax.inject.Inject 6 | 7 | /** 8 | * This class makes module for {@link HomeModule} 9 | * 10 | * @author Rudson Lima 11 | * @since 10/02/17 12 | */ 13 | 14 | class HomePresenter @Inject 15 | constructor(val repository: HomeContract.Repository, scheduler: BaseScheduler) : 16 | BasePresenter(scheduler), HomeContract.Presenter { 17 | 18 | override val view: HomeContract.View? 19 | get() = super.view 20 | 21 | override fun fetchUsers() { 22 | this.mView?.let { 23 | it.onLoading(true) 24 | 25 | this.addDisposable(this.repository.fetchUsers(it.page) 26 | .subscribeOn(this.scheduler.io()) 27 | .observeOn(this.scheduler.ui()) 28 | .subscribe({ response -> 29 | it.onLoading(false) 30 | 31 | if (response != null) 32 | it.onUserResponse(response) else it.onError(null) 33 | }, { error -> 34 | it.onLoading(false) 35 | it.onError(error) 36 | })) 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/br/com/liveo/mvp/screen/home/HomeRepository.kt: -------------------------------------------------------------------------------- 1 | package br.com.liveo.mvp.screen.home 2 | 3 | import br.com.liveo.mvp.data.remote.endpoint.EndPointHelper 4 | import br.com.liveo.mvp.model.domain.UserResponse 5 | import io.reactivex.Single 6 | import javax.inject.Inject 7 | 8 | /** 9 | * Created by rudsonlima on 9/4/17. 10 | */ 11 | class HomeRepository @Inject 12 | constructor(private val endPointHelper: EndPointHelper) : HomeContract.Repository { 13 | override fun fetchUsers(page: Int): Single = endPointHelper.fetchUsers(page) 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/br/com/liveo/mvp/screen/home/HomeViewHolder.kt: -------------------------------------------------------------------------------- 1 | package br.com.liveo.mvp.screen.home 2 | 3 | import android.databinding.DataBindingUtil 4 | import android.support.v7.widget.RecyclerView 5 | import android.view.View 6 | import br.com.liveo.mvp.databinding.ActivityHomeItemBinding 7 | 8 | /** 9 | * Created by rudsonlima on 8/29/17. 10 | */ 11 | 12 | class HomeViewHolder(view: View) : RecyclerView.ViewHolder(view) { 13 | private val mBinding: ActivityHomeItemBinding? = 14 | DataBindingUtil.bind(view) 15 | 16 | val binding: ActivityHomeItemBinding? 17 | get() = mBinding 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/br/com/liveo/mvp/screen/home/di/HomeComponent.kt: -------------------------------------------------------------------------------- 1 | package br.com.liveo.mvp.screen.home.di 2 | 3 | import br.com.liveo.mvp.di.scope.ActivityScoped 4 | import br.com.liveo.mvp.screen.home.HomeActivity 5 | import dagger.Subcomponent 6 | 7 | /** 8 | * This class makes Component for {@link ApplicationComponent} 9 | * 10 | * @author Rudson Lima 11 | * @since 10/02/17 12 | */ 13 | @ActivityScoped 14 | @Subcomponent(modules = [(HomeModule::class)]) 15 | interface HomeComponent { 16 | fun inject(homeActivity: HomeActivity) 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/br/com/liveo/mvp/screen/home/di/HomeModule.kt: -------------------------------------------------------------------------------- 1 | package br.com.liveo.mvp.screen.home.di 2 | 3 | import android.support.annotation.NonNull 4 | import br.com.liveo.mvp.data.remote.endpoint.EndPointHelper 5 | import br.com.liveo.mvp.di.scope.ActivityScoped 6 | import br.com.liveo.mvp.screen.home.HomeContract 7 | import br.com.liveo.mvp.screen.home.HomeRepository 8 | import br.com.liveo.mvp.screen.home.HomePresenter 9 | import br.com.liveo.mvp.base.BaseScheduler 10 | import br.com.liveo.mvp.data.scheduler.SchedulerProvider 11 | import dagger.Module 12 | import dagger.Provides 13 | 14 | /** 15 | * This class makes Subcomponent for {@link HomeComponent} 16 | * 17 | * @author Rudson Lima 18 | * @since 10/02/17 19 | */ 20 | 21 | @Module 22 | class HomeModule { 23 | 24 | @Provides 25 | @ActivityScoped 26 | fun provideHomeRepository(endPointHelper: EndPointHelper): HomeContract.Repository = 27 | HomeRepository(endPointHelper) 28 | 29 | @Provides 30 | @ActivityScoped 31 | fun provideHomePresenter(@NonNull repository: HomeContract.Repository, 32 | @NonNull scheduler: BaseScheduler): HomeContract.Presenter = 33 | HomePresenter(repository, scheduler) 34 | 35 | @Provides 36 | fun provideScheduleProvider(): BaseScheduler = SchedulerProvider.instance 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/java/br/com/liveo/mvp/screen/login/LoginActivity.kt: -------------------------------------------------------------------------------- 1 | package br.com.liveo.mvp.screen.login 2 | 3 | import android.os.Bundle 4 | import android.support.v7.app.AppCompatActivity 5 | 6 | /** 7 | * Created by rudsonlima on 9/1/17. 8 | */ 9 | 10 | class LoginActivity : AppCompatActivity(), LoginContract.View { 11 | 12 | override fun onCreate(savedInstanceState: Bundle?) { 13 | super.onCreate(savedInstanceState) 14 | } 15 | 16 | override fun onPostCreate(savedInstanceState: Bundle?) { 17 | super.onPostCreate(savedInstanceState) 18 | } 19 | 20 | override fun onLoading(loading: Boolean) { 21 | 22 | } 23 | 24 | override fun onError(error: Throwable?) { 25 | 26 | } 27 | 28 | override val email: String 29 | get() = "rudsonlive@gmail.com" 30 | override val password: String 31 | get() = "123456" 32 | 33 | 34 | override fun onLoginSuccess() { 35 | 36 | } 37 | 38 | override fun onLoginFailed(exception: Throwable) { 39 | 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/br/com/liveo/mvp/screen/login/LoginContract.kt: -------------------------------------------------------------------------------- 1 | package br.com.liveo.mvp.screen.login 2 | 3 | import br.com.liveo.mvp.base.BaseView 4 | import br.com.liveo.mvp.main.MainPresenter 5 | 6 | /** 7 | * Created by rudsonlima on 8/29/17. 8 | */ 9 | 10 | interface LoginContract { 11 | 12 | interface View : BaseView { 13 | 14 | val email: String 15 | val password: String 16 | fun onLoginSuccess() 17 | fun onLoginFailed(exception: Throwable) 18 | } 19 | 20 | interface Presenter : MainPresenter { 21 | fun toDoLogin() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/br/com/liveo/mvp/screen/login/LoginPresenter.kt: -------------------------------------------------------------------------------- 1 | package br.com.liveo.mvp.screen.login 2 | 3 | import br.com.liveo.mvp.base.BasePresenter 4 | import br.com.liveo.mvp.base.BaseScheduler 5 | import javax.inject.Inject 6 | 7 | /** 8 | * Created by rudsonlima on 8/29/17. 9 | */ 10 | class LoginPresenter @Inject 11 | constructor(scheduler: BaseScheduler) : BasePresenter(scheduler), LoginContract.Presenter { 12 | 13 | override fun toDoLogin() { 14 | 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/br/com/liveo/mvp/util/Constant.kt: -------------------------------------------------------------------------------- 1 | package br.com.liveo.mvp.util 2 | 3 | /** 4 | * Created by rudsonlima on 8/29/17. 5 | */ 6 | 7 | object Constant { 8 | 9 | val TAG = "4Adviser" 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/br/com/liveo/mvp/util/DateUtil.kt: -------------------------------------------------------------------------------- 1 | package br.com.liveo.mvp.util 2 | 3 | import android.content.Context 4 | import br.com.liveo.mvp.R 5 | import timber.log.Timber 6 | import java.text.ParseException 7 | import java.text.SimpleDateFormat 8 | import java.util.* 9 | 10 | /** 11 | * Created by rudsonlima on 8/29/17. 12 | */ 13 | object DateUtil { 14 | const val DATE_FORMAT_LOCAL = "dd/MM/yyyy" 15 | const val API_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss" 16 | 17 | private val apiDateFormat = SimpleDateFormat(API_DATE_FORMAT, Locale.getDefault()) 18 | private val shortDateFormat = SimpleDateFormat("dd/MM", Locale.getDefault()) 19 | private val shortTimeFormat = SimpleDateFormat("HH:mm", Locale.getDefault()) 20 | private val birthdayFormat = SimpleDateFormat("dd/MM/yyyy", Locale.getDefault()) 21 | private val fullDateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.getDefault()) 22 | private val weekFullDateFormat = SimpleDateFormat("EEE, d 'de' MMM yyyy", Locale.getDefault()) 23 | 24 | fun date(dateAsString: String?, pattern: String): Date? { 25 | 26 | dateAsString?.let { 27 | try { 28 | val simpleDateFormat = SimpleDateFormat(pattern, Locale.getDefault()) 29 | return simpleDateFormat.parse(it) 30 | } catch (e: ParseException) { 31 | e.printStackTrace() 32 | } 33 | } 34 | return null 35 | } 36 | 37 | fun formatDateToString(date: Date): String { 38 | return SimpleDateFormat("dd/MM/yyyy", Locale.getDefault()).format(date) 39 | } 40 | 41 | fun formatShortDate(dateAsString: String): String { 42 | try { 43 | val date = apiDateFormat.parse(dateAsString) 44 | return formatShortDate(date) 45 | } catch (e: Exception) { 46 | Timber.e(e, "Failed to parse string as date") 47 | } 48 | 49 | return dateAsString 50 | } 51 | 52 | fun formatShortTime(dateAsString: String): String { 53 | try { 54 | val date = apiDateFormat.parse(dateAsString) 55 | return formatShortTime(date) 56 | } catch (e: Exception) { 57 | Timber.e(e, "Failed to parse string as time") 58 | } 59 | 60 | return dateAsString 61 | } 62 | 63 | fun formatDateToApi(date: Date?): String { 64 | // TODO Do not accept optional date 65 | if (date == null) return "" 66 | return apiDateFormat.format(date) 67 | } 68 | 69 | fun getDateFromApi(dateAsString: String): Date { 70 | try { 71 | return apiDateFormat.parse(dateAsString) 72 | } catch (e: Exception) { 73 | Timber.e(e, "Failed to parse string as time") 74 | } 75 | 76 | return Date() 77 | } 78 | 79 | fun formatShortTime(date: Date): String { 80 | return shortTimeFormat.format(date) 81 | } 82 | 83 | fun formatShortDate(date: Date): String { 84 | return shortDateFormat.format(date) 85 | } 86 | 87 | fun formatWeekFullDate(date: Date): String { 88 | return weekFullDateFormat.format(date) 89 | } 90 | 91 | fun formatBirthday(date: Date): String { 92 | return birthdayFormat.format(date) 93 | } 94 | 95 | fun formatDateInFull(context: Context, date: Date): String { 96 | val calendar = Calendar.getInstance(Locale.getDefault()) 97 | calendar.time = date 98 | 99 | val day = calendar.get(Calendar.DAY_OF_MONTH) 100 | val month = calendar.get(Calendar.MONTH) 101 | val year = calendar.get(Calendar.YEAR) 102 | val dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK) 103 | 104 | val dayOfWeekString = when (dayOfWeek) { 105 | Calendar.SUNDAY -> "Dom" 106 | Calendar.MONDAY -> "Seg" 107 | Calendar.TUESDAY -> "Ter" 108 | Calendar.WEDNESDAY -> "Qua" 109 | Calendar.THURSDAY -> "Qui" 110 | Calendar.FRIDAY -> "Sex" 111 | Calendar.SATURDAY -> "Sáb" 112 | else -> "" 113 | } 114 | 115 | val monthString = when (month) { 116 | Calendar.JANUARY -> "jan" 117 | Calendar.FEBRUARY -> "fev" 118 | Calendar.MARCH -> "mar" 119 | Calendar.APRIL -> "abr" 120 | Calendar.MAY -> "mai" 121 | Calendar.JUNE -> "jun" 122 | Calendar.JULY -> "jul" 123 | Calendar.AUGUST -> "ago" 124 | Calendar.SEPTEMBER -> "set" 125 | Calendar.OCTOBER -> "out" 126 | Calendar.NOVEMBER -> "nov" 127 | Calendar.DECEMBER -> "dez" 128 | else -> "" 129 | } 130 | 131 | return context.getString(R.string.date_in_full, dayOfWeekString, day, monthString, year) 132 | } 133 | 134 | fun getMonthInFull(context: Context, month: Int): String { 135 | val months = context.resources.getStringArray(R.array.date_month_in_full) 136 | 137 | return if (months.size > month) months[month] else "" 138 | } 139 | 140 | fun addDaysToDate(date: Date, days: Int): Date { 141 | val calendar = Calendar.getInstance() 142 | calendar.time = date 143 | 144 | calendar.add(Calendar.DAY_OF_YEAR, days) 145 | 146 | calendar.set(Calendar.HOUR_OF_DAY, 0) 147 | calendar.set(Calendar.MINUTE, 0) 148 | calendar.set(Calendar.SECOND, 0) 149 | 150 | return Date(calendar.timeInMillis) 151 | } 152 | 153 | fun subtractYearsFromDate(date: Date, years: Int): Date { 154 | val calendar = Calendar.getInstance() 155 | calendar.time = date 156 | 157 | calendar.add(Calendar.YEAR, -years) 158 | 159 | return Date(calendar.timeInMillis) 160 | } 161 | 162 | /** 163 | * From 0 to 6, monday to sunday 164 | */ 165 | fun getDayOfWeek(date: Date): Int { 166 | val calendar = Calendar.getInstance() 167 | calendar.time = date 168 | 169 | return (calendar.get(Calendar.DAY_OF_WEEK) + 5) % 7 170 | } 171 | 172 | fun getHour(date: Date): Int { 173 | val calendar = Calendar.getInstance() 174 | calendar.time = date 175 | 176 | return calendar.get(Calendar.HOUR_OF_DAY) 177 | } 178 | 179 | fun getMinute(date: Date): Int { 180 | val calendar = Calendar.getInstance() 181 | calendar.time = date 182 | 183 | return calendar.get(Calendar.MINUTE) 184 | } 185 | 186 | fun isTimeInPeriod(chosenHour: Int, chosenMinute: Int, 187 | startHour: Int, startMinute: Int, 188 | endHour: Int, endMinute: Int): Boolean { 189 | if (((chosenHour > startHour) || 190 | (chosenHour == startHour && chosenMinute >= startMinute)) && 191 | ((chosenHour < endHour) || 192 | (chosenHour == endHour && chosenMinute <= endMinute))) { 193 | return true 194 | } 195 | 196 | return false 197 | } 198 | } -------------------------------------------------------------------------------- /app/src/main/java/br/com/liveo/mvp/util/Util.kt: -------------------------------------------------------------------------------- 1 | package br.com.liveo.mvp.util 2 | 3 | /** 4 | * Created by rudsonlima on 9/18/17. 5 | */ 6 | -------------------------------------------------------------------------------- /app/src/main/java/br/com/liveo/mvp/util/view/DatePickerDialogFragment.kt: -------------------------------------------------------------------------------- 1 | package br.com.liveo.mvp.util.view 2 | 3 | import android.app.DatePickerDialog 4 | import android.app.Dialog 5 | import android.os.Bundle 6 | import android.text.TextUtils 7 | import android.widget.DatePicker 8 | import br.com.liveo.mvp.base.BaseDialog 9 | import br.com.liveo.mvp.util.DateUtil 10 | import java.util.* 11 | 12 | class DatePickerDialogFragment : BaseDialog(), DatePickerDialog.OnDateSetListener { 13 | 14 | companion object { 15 | 16 | private var listener: OnDateCallback? = null 17 | private var disableFutureDate: Boolean = false 18 | 19 | private var sCalendar: Calendar? = null 20 | 21 | val calendar: Calendar? 22 | get() { 23 | if (sCalendar == null) { 24 | sCalendar = Calendar.getInstance() 25 | } 26 | 27 | return sCalendar 28 | } 29 | } 30 | 31 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { 32 | 33 | var year = 0 34 | var month = 0 35 | var day = 0 36 | 37 | calendar?.let { 38 | year = it.get(Calendar.YEAR) 39 | month = it.get(Calendar.MONTH) 40 | day = it.get(Calendar.DAY_OF_MONTH) 41 | } 42 | 43 | val datePickerDialog = DatePickerDialog(activity, this, 44 | year, month, day) 45 | 46 | if (disableFutureDate) { 47 | datePickerDialog.datePicker.maxDate = System.currentTimeMillis() 48 | } 49 | 50 | return datePickerDialog 51 | } 52 | 53 | override fun onDateSet(view: DatePicker, year: Int, month: Int, day: Int) { 54 | listener?.let { 55 | sCalendar?.let { 56 | sCalendar?.set(year, month, day) 57 | listener?.onDate(DateUtil.formatDateToString(it.time)) 58 | } 59 | } 60 | } 61 | 62 | interface OnDateCallback { 63 | fun onDate(dateFormatted: String) 64 | } 65 | 66 | class Builder { 67 | private var dateAsString: String? = null 68 | 69 | fun date(date: String): Builder { 70 | dateAsString = date 71 | return this 72 | } 73 | 74 | fun callback(OnDateCallback: OnDateCallback): Builder { 75 | listener = OnDateCallback 76 | return this 77 | } 78 | 79 | fun disableFutureDate(): Builder { 80 | disableFutureDate = true 81 | return this 82 | } 83 | 84 | fun create(): DatePickerDialogFragment { 85 | val fragment = DatePickerDialogFragment() 86 | 87 | if (!TextUtils.isEmpty(dateAsString)) { 88 | calendar?.time = DateUtil.date(dateAsString, DateUtil.DATE_FORMAT_LOCAL) 89 | } 90 | 91 | return fragment 92 | } 93 | } 94 | } -------------------------------------------------------------------------------- /app/src/main/java/br/com/liveo/mvp/util/view/TimePickerDialogFragment.kt: -------------------------------------------------------------------------------- 1 | package br.com.liveo.mvp.util.view 2 | 3 | import android.app.Dialog 4 | import android.app.DialogFragment 5 | import android.app.FragmentManager 6 | import android.app.TimePickerDialog 7 | import android.os.Bundle 8 | import android.widget.TimePicker 9 | import java.util.* 10 | 11 | /** 12 | * Created by rudsonlima on 25/04/18. 13 | */ 14 | class TimePickerDialogFragment : DialogFragment(), TimePickerDialog.OnTimeSetListener { 15 | 16 | private var componentTag: String? = null 17 | var onTimeListener: OnTimeListener? = null 18 | var selectedDate: Date? = null 19 | 20 | interface OnTimeListener { 21 | fun onTimeListener(hour: Int, minute: Int, tag: String) 22 | } 23 | 24 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { 25 | val calendar = Calendar.getInstance() 26 | 27 | selectedDate?.let { 28 | calendar.timeInMillis = it.time 29 | } 30 | 31 | val hourOfDay = calendar.get(Calendar.HOUR_OF_DAY) 32 | val minute = calendar.get(Calendar.MINUTE) 33 | val is24HView = true 34 | 35 | return TimePickerDialog(activity, this, hourOfDay, minute, is24HView) 36 | } 37 | 38 | override fun onTimeSet(view: TimePicker, hourOfDay: Int, minute: Int) { 39 | onTimeListener?.onTimeListener(hourOfDay, minute, componentTag.orEmpty()) 40 | } 41 | 42 | override fun show(manager: FragmentManager, tag: String) { 43 | super.show(manager, tag) 44 | componentTag = tag 45 | } 46 | } -------------------------------------------------------------------------------- /app/src/main/res-screen/base/anim/translate_fade_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | -------------------------------------------------------------------------------- /app/src/main/res-screen/base/anim/translate_fade_out.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res-screen/base/anim/translate_left_enter.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 8 | -------------------------------------------------------------------------------- /app/src/main/res-screen/base/anim/translate_left_exit.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 8 | -------------------------------------------------------------------------------- /app/src/main/res-screen/base/anim/translate_no_change.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res-screen/base/anim/translate_right_enter.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 8 | -------------------------------------------------------------------------------- /app/src/main/res-screen/base/anim/translate_right_exit.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 8 | -------------------------------------------------------------------------------- /app/src/main/res-screen/base/anim/translate_slide_down.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 8 | -------------------------------------------------------------------------------- /app/src/main/res-screen/base/anim/translate_slide_up.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res-screen/base/layout/appbar_and_toolbar.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 11 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res-screen/home/layout/activity_home.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 9 | 10 | 13 | 14 | 20 | 21 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/res-screen/home/layout/activity_home_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | 17 | 18 | 28 | 29 | 41 | 42 | -------------------------------------------------------------------------------- /app/src/main/res-screen/splash/layout/activity_splash.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_arrow_back.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zirouan/android-kotlin-mvp/2f1c93503669bda1572d3df20e65ded70b5426e6/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zirouan/android-kotlin-mvp/2f1c93503669bda1572d3df20e65ded70b5426e6/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zirouan/android-kotlin-mvp/2f1c93503669bda1572d3df20e65ded70b5426e6/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zirouan/android-kotlin-mvp/2f1c93503669bda1572d3df20e65ded70b5426e6/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zirouan/android-kotlin-mvp/2f1c93503669bda1572d3df20e65ded70b5426e6/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zirouan/android-kotlin-mvp/2f1c93503669bda1572d3df20e65ded70b5426e6/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zirouan/android-kotlin-mvp/2f1c93503669bda1572d3df20e65ded70b5426e6/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zirouan/android-kotlin-mvp/2f1c93503669bda1572d3df20e65ded70b5426e6/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zirouan/android-kotlin-mvp/2f1c93503669bda1572d3df20e65ded70b5426e6/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zirouan/android-kotlin-mvp/2f1c93503669bda1572d3df20e65ded70b5426e6/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values-v21/theme.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #00BCD4 4 | #0097A7 5 | #B2EBF2 6 | 7 | #03A9F4 8 | 9 | #212121 10 | #757575 11 | 12 | #FFFFFF 13 | #BDBDBD 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 16dp 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | LiveoMVP 4 | Settings 5 | 6 | %1$s, %2$d de %3$s de %4$d 7 | 8 | janeiro 9 | fevereiro 10 | março 11 | abril 12 | maio 13 | junho 14 | julho 15 | agosto 16 | setembro 17 | outubro 18 | novembro 19 | dezembro 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 13 | 14 | 18 | 19 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/test/java/br/com/liveo/mvp/screen/home/HomePresenterTest.kt: -------------------------------------------------------------------------------- 1 | package br.com.liveo.mvp.screen.home 2 | 3 | import br.com.liveo.mvp.data.scheduler.TestSchedulerProvider 4 | import br.com.liveo.mvp.model.domain.UserResponse 5 | import io.reactivex.Single 6 | import io.reactivex.schedulers.TestScheduler 7 | import org.junit.After 8 | import org.junit.Assert 9 | import org.junit.Before 10 | import org.junit.Test 11 | import org.junit.runner.RunWith 12 | import org.junit.runners.JUnit4 13 | import org.mockito.Mock 14 | import org.mockito.Mockito.* 15 | import org.mockito.MockitoAnnotations 16 | import org.mockito.Mockito.`when` as _when 17 | 18 | /** 19 | * This class makes tests for {@link HomePresenter} 20 | * 21 | * @author Rudson Lima 22 | * @since 09/24/17 23 | */ 24 | @RunWith(JUnit4::class) 25 | class HomePresenterTest { 26 | 27 | lateinit var mTestScheduler: TestScheduler 28 | lateinit var mPresenter: HomeContract.Presenter 29 | 30 | @Mock 31 | var mView: HomeContract.View? = null 32 | 33 | @Mock 34 | lateinit var mUserResponse: UserResponse 35 | 36 | @Mock 37 | lateinit var mRepository: HomeContract.Repository 38 | 39 | @Before 40 | fun setUp() { 41 | MockitoAnnotations.initMocks(this) 42 | 43 | this.mView?.let { 44 | 45 | _when(mView?.page).thenReturn(2) 46 | _when(mRepository.fetchUsers(2)).thenReturn(Single.just(mUserResponse)) 47 | 48 | mTestScheduler = TestScheduler() 49 | mPresenter = HomePresenter(mRepository, TestSchedulerProvider(mTestScheduler)) 50 | mPresenter.attach(it) 51 | } 52 | } 53 | 54 | @After 55 | fun tearDown() { 56 | mPresenter.detachView() 57 | } 58 | 59 | @Test 60 | fun fetchUsers_sucess() { 61 | mPresenter.fetchUsers() 62 | verify(mRepository, times(1)).fetchUsers(2) 63 | } 64 | 65 | @Test 66 | fun fetchUsers_returning_loadingSuccess_forView() { 67 | this.mView?.let { 68 | mPresenter.fetchUsers() 69 | 70 | verify(it, times(1)).page 71 | verify(it, times(1)).onLoading(true) 72 | 73 | mTestScheduler.triggerActions() 74 | 75 | verify(it, times(1)).onLoading(false) 76 | } 77 | } 78 | 79 | @Test 80 | fun fetchUsers_returningSuccess_forView() { 81 | this.mView?.let { 82 | mPresenter.fetchUsers() 83 | 84 | mTestScheduler.triggerActions() 85 | 86 | verify(it, times(1)).onUserResponse(mUserResponse) 87 | verify(it, never()).onError(null) 88 | } 89 | } 90 | 91 | @Test 92 | fun fetchUsers_returningFailing_forView() { 93 | this.mView?.let { 94 | val throwable = Throwable() 95 | _when(mRepository.fetchUsers(2)).thenReturn(Single.error(throwable)) 96 | 97 | mPresenter.fetchUsers() 98 | 99 | mTestScheduler.triggerActions() 100 | 101 | verify(it).onError(throwable) 102 | verify(it, times(1)).onLoading(false) 103 | verify(it, never()).onUserResponse(mUserResponse) 104 | } 105 | } 106 | 107 | @Test 108 | fun attach_isNotNull_sucess() { 109 | Assert.assertNotNull(mPresenter.view) 110 | } 111 | 112 | @Test 113 | fun detachView_isNull_sucess() { 114 | Assert.assertNotNull(mPresenter.view) 115 | 116 | mPresenter.detachView() 117 | Assert.assertNull(mPresenter.view) 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /app/src/test/java/br/com/liveo/mvp/screen/home/HomeRepositoryTest.kt: -------------------------------------------------------------------------------- 1 | package br.com.liveo.mvp.screen.home 2 | 3 | import br.com.liveo.mvp.data.remote.endpoint.EndPointHelper 4 | import br.com.liveo.mvp.model.domain.UserResponse 5 | import io.reactivex.Single 6 | import io.reactivex.observers.TestObserver 7 | import org.junit.Before 8 | import org.junit.Test 9 | import org.mockito.Mock 10 | import org.mockito.Mockito.verify 11 | import org.mockito.MockitoAnnotations 12 | import org.mockito.Mockito.`when` as _when 13 | 14 | /** 15 | * This class makes tests for [HomeRepository] 16 | * 17 | * @author Rudson Lima 18 | * @since 09/24/17 19 | */ 20 | 21 | class HomeRepositoryTest { 22 | 23 | val PAGE: Int = 2 24 | 25 | @Mock 26 | var mEndPointHelper: EndPointHelper? = null 27 | 28 | @Mock 29 | lateinit var mUserResponse: UserResponse 30 | 31 | @Mock 32 | lateinit var mRepository: HomeContract.Repository 33 | 34 | @Before 35 | fun setUp() { 36 | MockitoAnnotations.initMocks(this) 37 | 38 | mEndPointHelper?.let { 39 | mRepository = HomeRepository(it) 40 | _when(mRepository.fetchUsers(PAGE)).thenReturn(Single.just(mUserResponse)) 41 | } 42 | } 43 | 44 | @Test 45 | fun fetchUsers_sucess() { 46 | mRepository.fetchUsers(2) 47 | verify(mEndPointHelper)?.fetchUsers(2) 48 | } 49 | 50 | @Test 51 | fun fetchUsers_noErros_sucess() { 52 | val subscriber = TestObserver.create() 53 | mEndPointHelper?.fetchUsers(2)?.subscribe(subscriber) 54 | subscriber.onNext(mUserResponse) 55 | subscriber.assertNoErrors() 56 | subscriber.assertComplete() 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /app/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker: -------------------------------------------------------------------------------- 1 | mock-maker-inline -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext.kotlin_version = '1.2.41' 5 | repositories { 6 | google() 7 | jcenter() 8 | } 9 | dependencies { 10 | classpath "com.android.tools.build:gradle:${tools_build_gradle}" 11 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 12 | 13 | // NOTE: Do not place your application dependencies here; they belong 14 | // in the individual module build.gradle files 15 | } 16 | } 17 | 18 | allprojects { 19 | repositories { 20 | google() 21 | jcenter() 22 | } 23 | } 24 | 25 | task clean(type: Delete) { 26 | delete rootProject.buildDir 27 | } 28 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | 19 | #build.gradle - project 20 | tools_build_gradle=3.1.2 21 | 22 | #Test 23 | junit_version=4.12 24 | mockito_version=2.10.0 25 | 26 | #UI Test 27 | espresso_core_version=3.0.1 28 | uiautomator_v18_version=2.1.2 29 | espresso_contrib_version=3.0.1 30 | 31 | #Google 32 | #Library 33 | google_support_version=27.1.1 34 | google_constraint_version=1.1.0 35 | google_play_services_version=11.8.0 36 | google_databinding_compiler_version=3.1.2 37 | 38 | #Fabric 39 | fabric_tools=1.+ 40 | crashlytics_version:2.6.8@aar 41 | 42 | #Square 43 | #Library Retrofit (Consuming APIs) 44 | retrofit_version=2.3.0 45 | interceptor_version=3.8.0 46 | retrofit_converter_gson_version=2.3.0 47 | retrofit_adapter_rxjava_version=2.3.0 48 | 49 | #Timber = log 50 | timber_version=4.7.0 51 | 52 | #Dagger 2 53 | dagger_version=2.10 54 | 55 | #Rxjava 56 | #Library 57 | rxjava_version=2.1.1 58 | rxandroid_version=2.0.2 59 | 60 | #Glide 61 | #Library 62 | glide_version=3.7.0 63 | 64 | #Others 65 | #Library event keyboard 66 | keyboard_event_version=2.1.0 67 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | --------------------------------------------------------------------------------