├── .github └── FUNDING.yml ├── data ├── src │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── res │ │ │ └── values │ │ │ │ └── strings.xml │ │ └── java │ │ │ └── com │ │ │ └── mctech │ │ │ └── data │ │ │ ├── quotation │ │ │ ├── model │ │ │ │ ├── QuotationSourceResponse.kt │ │ │ │ ├── QuotationAuthorResponse.kt │ │ │ │ ├── RandomQuotationEmbeddedResponse.kt │ │ │ │ └── RandomQuotationResponse.kt │ │ │ ├── datasource │ │ │ │ ├── QuotationDataSource.kt │ │ │ │ ├── QuotationCacheDataSource.kt │ │ │ │ ├── QuotationCacheDataSourceImpl.kt │ │ │ │ └── QuotationRemoteDataSourceImpl.kt │ │ │ ├── api │ │ │ │ └── QuotationAPI.kt │ │ │ └── repository │ │ │ │ └── QuotationRepository.kt │ │ │ ├── networkRequestHandler.kt │ │ │ ├── di │ │ │ └── dataModule.kt │ │ │ └── auth │ │ │ └── AuthRepository.kt │ └── test │ │ └── java │ │ └── com │ │ └── mctech │ │ └── data │ │ └── quotation │ │ ├── QuotationRepositoryTest.kt │ │ ├── QuotationRemoteDataSourceImplTest.kt │ │ └── QuotationCacheDataSourceImplTest.kt └── build.gradle ├── domain ├── src │ ├── test │ │ ├── resources │ │ │ └── mockito-extensions │ │ │ │ └── org.mockito.plugins.MockMaker │ │ └── kotlin │ │ │ └── com │ │ │ └── mctech │ │ │ └── domain │ │ │ ├── validation │ │ │ ├── PasswordlValidatorTest.kt │ │ │ └── EmailValidatorTest.kt │ │ │ ├── model │ │ │ ├── AuthRequestTest.kt │ │ │ └── RegisterUserTest.kt │ │ │ ├── TestDataFactory.kt │ │ │ └── interaction │ │ │ ├── auth │ │ │ ├── CheckAuthSessionUseCaseTest.kt │ │ │ ├── AuthenticationUseCaseTest.kt │ │ │ └── RegisterUserUseCaseTest.kt │ │ │ └── quotation │ │ │ └── GetRandomQuotationCaseTest.kt │ └── main │ │ └── java │ │ └── com │ │ └── mctech │ │ └── domain │ │ ├── errors │ │ ├── NetworkException.kt │ │ ├── QuotationException.kt │ │ └── AuthException.kt │ │ ├── interaction │ │ ├── Result.kt │ │ ├── auth │ │ │ ├── CheckAuthSessionUseCase.kt │ │ │ ├── AuthenticationUseCase.kt │ │ │ └── RegisterUserUseCase.kt │ │ └── quotation │ │ │ └── GetRandomQuotationCase.kt │ │ ├── validation │ │ ├── PasswordlValidator.kt │ │ └── EmailValidator.kt │ │ ├── model │ │ ├── User.kt │ │ ├── Quotation.kt │ │ ├── AuthRequest.kt │ │ └── RegisterUser.kt │ │ └── services │ │ ├── QuotationService.kt │ │ └── AuthService.kt └── build.gradle ├── features ├── feature-onboarding │ ├── src │ │ ├── test │ │ │ ├── resources │ │ │ │ └── mockito-extensions │ │ │ │ │ └── org.mockito.plugins.MockMaker │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── mctech │ │ │ │ └── features │ │ │ │ └── onboarding │ │ │ │ └── OnboardingViewModelTest.kt │ │ └── main │ │ │ ├── java │ │ │ └── com │ │ │ │ └── mctech │ │ │ │ └── features │ │ │ │ └── onboarding │ │ │ │ ├── state │ │ │ │ └── OnBoardingNavigationState.kt │ │ │ │ ├── di │ │ │ │ └── onboardingModule.kt │ │ │ │ ├── OnboardingViewModel.kt │ │ │ │ └── OnboardingActivity.kt │ │ │ ├── AndroidManifest.xml │ │ │ └── res │ │ │ └── layout │ │ │ └── activity_on_boarding.xml │ ├── README.md │ └── build.gradle ├── feature-navigation │ ├── src │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── mctech │ │ │ │ └── features │ │ │ │ └── navigation │ │ │ │ ├── UnsupportedNavigation.kt │ │ │ │ ├── Screen.kt │ │ │ │ └── Navigator.kt │ │ └── test │ │ │ └── java │ │ │ └── com │ │ │ └── mctech │ │ │ └── features │ │ │ └── navigation │ │ │ └── NavigatorTest.kt │ └── build.gradle ├── feature-quotation-list │ ├── src │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ └── res │ │ │ └── values │ │ │ └── strings.xml │ └── build.gradle ├── feature-login │ ├── README.md │ ├── src │ │ ├── main │ │ │ ├── res │ │ │ │ ├── values │ │ │ │ │ ├── dimens.xml │ │ │ │ │ └── strings.xml │ │ │ │ ├── navigation │ │ │ │ │ └── login_nav_graph.xml │ │ │ │ └── layout │ │ │ │ │ ├── activity_login.xml │ │ │ │ │ ├── fragment_sign_in.xml │ │ │ │ │ └── fragment_sign_up.xml │ │ │ ├── java │ │ │ │ └── com │ │ │ │ │ └── mctech │ │ │ │ │ └── features │ │ │ │ │ └── login │ │ │ │ │ ├── state │ │ │ │ │ ├── LoginState.kt │ │ │ │ │ └── LoginErrorStateResources.kt │ │ │ │ │ ├── di │ │ │ │ │ └── loginModule.kt │ │ │ │ │ ├── interaction │ │ │ │ │ └── LoginUserInteraction.kt │ │ │ │ │ ├── LoginActivity.kt │ │ │ │ │ ├── LoginViewModel.kt │ │ │ │ │ ├── LoginSignInFragment.kt │ │ │ │ │ └── LoginSignUpFragment.kt │ │ │ └── AndroidManifest.xml │ │ └── test │ │ │ └── java │ │ │ └── com │ │ │ └── mctech │ │ │ └── features │ │ │ └── login │ │ │ └── LoginViewModelTest.kt │ └── build.gradle ├── feature-quotation-filtering │ ├── src │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ └── res │ │ │ └── values │ │ │ └── strings.xml │ └── build.gradle └── feature-quotation-random │ ├── src │ ├── main │ │ ├── res │ │ │ ├── values │ │ │ │ └── strings.xml │ │ │ └── layout │ │ │ │ └── activity_random_joke.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── mctech │ │ │ │ └── feature │ │ │ │ └── random_joke │ │ │ │ ├── interaction │ │ │ │ └── RandomQuotationInteraction.kt │ │ │ │ ├── di │ │ │ │ └── randomQuotationModule.kt │ │ │ │ ├── RandomQuotationViewModel.kt │ │ │ │ └── RandomQuotationActivity.kt │ │ └── AndroidManifest.xml │ └── test │ │ └── java │ │ └── com │ │ └── mctech │ │ └── feature │ │ └── random_joke │ │ └── RandomQuotationViewModelTest.kt │ └── build.gradle ├── libraries ├── library-logger │ ├── README.md │ ├── build.gradle │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── com │ │ └── mctech │ │ └── libraries │ │ └── logger │ │ ├── Logger.kt │ │ └── MutedLogger.kt ├── library-app-theme │ ├── src │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ └── res │ │ │ └── values │ │ │ ├── dimens.xml │ │ │ ├── colors.xml │ │ │ └── styles.xml │ ├── README.md │ └── build.gradle ├── library-shared-feature-arq-testing │ ├── src │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ └── java │ │ │ └── com │ │ │ └── mctech │ │ │ └── test │ │ │ └── arq │ │ │ ├── extentions │ │ │ ├── listAssertionExtention.kt │ │ │ └── livedataTestExtentions.kt │ │ │ ├── BaseViewModelTest.kt │ │ │ └── rules │ │ │ ├── KoinModuleTestRule.kt │ │ │ └── CoroutinesMainTestRule.kt │ └── build.gradle ├── library-shared-feature-arq │ ├── src │ │ ├── main │ │ │ ├── java │ │ │ │ └── com │ │ │ │ │ └── mctech │ │ │ │ │ └── feature │ │ │ │ │ └── arq │ │ │ │ │ ├── UserInteraction.kt │ │ │ │ │ ├── extentions │ │ │ │ │ ├── editTextExtentions.kt │ │ │ │ │ ├── viewExtentions.kt │ │ │ │ │ └── baseActivityExtention.kt │ │ │ │ │ ├── ComponentState.kt │ │ │ │ │ ├── components │ │ │ │ │ ├── TextViewAdapter.kt │ │ │ │ │ └── ViewAdapter.kt │ │ │ │ │ ├── BaseActivity.kt │ │ │ │ │ ├── BaseFragment.kt │ │ │ │ │ └── BaseViewModel.kt │ │ │ ├── res │ │ │ │ └── values │ │ │ │ │ └── strings.xml │ │ │ └── AndroidManifest.xml │ │ └── test │ │ │ └── java │ │ │ └── com │ │ │ └── mctech │ │ │ └── feature │ │ │ └── arq │ │ │ └── BaseViewModelArqTest.kt │ └── build.gradle ├── library-analytics │ ├── README.md │ ├── build.gradle │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── com │ │ └── mctech │ │ └── libraries │ │ └── analytics │ │ ├── AnalyticsHelper.kt │ │ ├── MutedAnalyticsHelper.kt │ │ └── FirebaseAnalyticsHelper.kt └── library-networking │ ├── build.gradle │ └── src │ ├── main │ └── java │ │ └── com │ │ └── mctech │ │ └── library │ │ └── networking │ │ ├── secureRequest.kt │ │ ├── RetrofitBuilder.kt │ │ ├── NetworkError.kt │ │ └── NetworkErrorTransformer.kt │ └── test │ └── java │ └── com │ └── mctech │ └── library │ └── networking │ ├── SecureRequestKtTest.kt │ └── NetworkErrorTransformerTest.kt ├── app ├── src │ └── main │ │ ├── res │ │ ├── values │ │ │ └── strings.xml │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher_round.png │ │ └── mipmap-xxxhdpi │ │ │ └── ic_launcher_round.png │ │ ├── java │ │ └── com │ │ │ └── mctech │ │ │ └── kotlinlearning │ │ │ ├── di │ │ │ └── modules │ │ │ │ ├── loggingModule.kt │ │ │ │ ├── analyticsModule.kt │ │ │ │ ├── useCaseModules.kt │ │ │ │ ├── navigatorModule.kt │ │ │ │ └── networkingModule.kt │ │ │ ├── platform │ │ │ └── logger │ │ │ │ └── LogcatLogger.kt │ │ │ └── App.kt │ │ └── AndroidManifest.xml ├── proguard-rules.pro └── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── settings.gradle ├── .circleci └── config.yml ├── gradle.properties ├── .gitignore ├── README.md ├── gradlew.bat ├── gradlew └── LICENSE /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | ko_fi: mayconcardoso 2 | -------------------------------------------------------------------------------- /data/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /domain/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker: -------------------------------------------------------------------------------- 1 | mock-maker-inline -------------------------------------------------------------------------------- /features/feature-onboarding/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker: -------------------------------------------------------------------------------- 1 | mock-maker-inline -------------------------------------------------------------------------------- /libraries/library-logger/README.md: -------------------------------------------------------------------------------- 1 | ## Logger 2 | 3 | This is a library to manage the application logging. 4 | -------------------------------------------------------------------------------- /data/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | data 3 | 4 | -------------------------------------------------------------------------------- /features/feature-navigation/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /features/feature-onboarding/README.md: -------------------------------------------------------------------------------- 1 | ## Logger 2 | 3 | This is a library to manage the application OnBoarding. 4 | -------------------------------------------------------------------------------- /features/feature-quotation-list/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /features/feature-login/README.md: -------------------------------------------------------------------------------- 1 | ## Login 2 | 3 | This is a library to manage the application authentication overall. 4 | -------------------------------------------------------------------------------- /libraries/library-app-theme/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /libraries/library-shared-feature-arq-testing/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Kotlin Learning 3 | 4 | -------------------------------------------------------------------------------- /features/feature-quotation-filtering/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /libraries/library-app-theme/README.md: -------------------------------------------------------------------------------- 1 | ## App Theme 2 | 3 | This is a library to manage the application theme and style. 4 | -------------------------------------------------------------------------------- /libraries/library-logger/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'kotlin' 2 | 3 | dependencies { 4 | implementation globalDependencies.kotlinStdLib 5 | } -------------------------------------------------------------------------------- /libraries/library-logger/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /features/feature-quotation-list/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | feature-joke-list 3 | 4 | -------------------------------------------------------------------------------- /libraries/library-shared-feature-arq/src/main/java/com/mctech/feature/arq/UserInteraction.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.feature.arq 2 | 3 | interface UserInteraction -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayconCardoso/Modularized-Kotlin-Clean-Architecture-Showcase/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /features/feature-quotation-filtering/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | feature-joke-filtering 3 | 4 | -------------------------------------------------------------------------------- /libraries/library-analytics/README.md: -------------------------------------------------------------------------------- 1 | ## Analytics 2 | 3 | This is a library to manage the application analytics; It has two implementation of it: Firebase and Muted 4 | -------------------------------------------------------------------------------- /libraries/library-shared-feature-arq/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | library-shared-feature-arq 3 | 4 | -------------------------------------------------------------------------------- /libraries/library-app-theme/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12dp 4 | -------------------------------------------------------------------------------- /libraries/library-shared-feature-arq/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayconCardoso/Modularized-Kotlin-Clean-Architecture-Showcase/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayconCardoso/Modularized-Kotlin-Clean-Architecture-Showcase/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayconCardoso/Modularized-Kotlin-Clean-Architecture-Showcase/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayconCardoso/Modularized-Kotlin-Clean-Architecture-Showcase/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayconCardoso/Modularized-Kotlin-Clean-Architecture-Showcase/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /domain/src/main/java/com/mctech/domain/errors/NetworkException.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.domain.errors 2 | 3 | class NetworkException : Exception("Algo inesperado aconteceu enquanto tentava conectar no servidor. Tente novamente em alguns segundos.") -------------------------------------------------------------------------------- /libraries/library-analytics/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | 5 | dependencies { 6 | implementation globalDependencies.firebaseCore 7 | } 8 | -------------------------------------------------------------------------------- /data/src/main/java/com/mctech/data/quotation/model/QuotationSourceResponse.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.data.quotation.model 2 | 3 | /** 4 | * @author MAYCON CARDOSO on 2019-11-19. 5 | */ 6 | data class QuotationSourceResponse( 7 | val url : String 8 | ) -------------------------------------------------------------------------------- /libraries/library-shared-feature-arq/src/main/java/com/mctech/feature/arq/extentions/editTextExtentions.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.feature.arq.extentions 2 | 3 | import android.widget.EditText 4 | 5 | fun EditText.getValue() = this.text.toString().trim() 6 | -------------------------------------------------------------------------------- /domain/src/main/java/com/mctech/domain/interaction/Result.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.domain.interaction 2 | 3 | sealed class Result { 4 | data class Success(val result: T) : Result() 5 | data class Failure(val throwable: Throwable) : Result() 6 | } -------------------------------------------------------------------------------- /domain/src/main/java/com/mctech/domain/validation/PasswordlValidator.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.domain.validation 2 | 3 | 4 | object PasswordlValidator{ 5 | operator fun invoke(password : String?) = password?.let { 6 | password.length >= 6 7 | } ?: false 8 | } -------------------------------------------------------------------------------- /data/src/main/java/com/mctech/data/quotation/model/QuotationAuthorResponse.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.data.quotation.model 2 | 3 | /** 4 | * @author MAYCON CARDOSO on 2019-11-19. 5 | */ 6 | data class QuotationAuthorResponse( 7 | val id : String, 8 | val name : String 9 | ) -------------------------------------------------------------------------------- /features/feature-login/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /features/feature-navigation/src/main/java/com/mctech/features/navigation/UnsupportedNavigation.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.features.navigation 2 | 3 | data class UnsupportedNavigation(val destination: Screen) : RuntimeException( 4 | "Cannot navigate to this destination -> $destination" 5 | ) -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Sep 11 22:03:41 BRT 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip 7 | -------------------------------------------------------------------------------- /libraries/library-app-theme/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | 5 | dependencies { 6 | implementation globalDependencies.appCompact 7 | implementation globalDependencies.materialDesign 8 | } -------------------------------------------------------------------------------- /data/src/main/java/com/mctech/data/quotation/datasource/QuotationDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.data.quotation.datasource 2 | 3 | import com.mctech.domain.services.QuotationService 4 | 5 | /** 6 | * @author MAYCON CARDOSO on 2019-09-30. 7 | */ 8 | interface QuotationDataSource : QuotationService -------------------------------------------------------------------------------- /features/feature-quotation-random/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | feature-random-joke 3 | Mostrar outra 4 | Random Quotation 5 | 6 | -------------------------------------------------------------------------------- /domain/src/main/java/com/mctech/domain/model/User.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.domain.model 2 | 3 | /** 4 | * @author MAYCON CARDOSO on 2019-07-25. 5 | */ 6 | data class User( 7 | var id: String? = "", 8 | val name: String, 9 | val email: String?, 10 | var profilePicture: String? = "" 11 | ) -------------------------------------------------------------------------------- /features/feature-onboarding/src/main/java/com/mctech/features/onboarding/state/OnBoardingNavigationState.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.features.onboarding.state 2 | 3 | sealed class OnBoardingNavigationState { 4 | object Authorized : OnBoardingNavigationState() 5 | object Unauthorized : OnBoardingNavigationState() 6 | } -------------------------------------------------------------------------------- /libraries/library-app-theme/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #008577 4 | #00574B 5 | #D81B60 6 | #FBFBFB 7 | 8 | -------------------------------------------------------------------------------- /domain/src/main/java/com/mctech/domain/errors/QuotationException.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.domain.errors 2 | 3 | /** 4 | * @author MAYCON CARDOSO on 2019-09-30. 5 | */ 6 | sealed class QuotationException : RuntimeException() { 7 | object UnknownQuotationException : QuotationException() 8 | object ConnectionIssueException : QuotationException() 9 | } -------------------------------------------------------------------------------- /data/src/test/java/com/mctech/data/quotation/QuotationRepositoryTest.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.data.quotation 2 | 3 | import org.junit.Test 4 | 5 | /** 6 | * @author MAYCON CARDOSO on 2019-09-30. 7 | */ 8 | class QuotationRepositoryTest { 9 | 10 | @Test 11 | fun getRandom() { 12 | } 13 | 14 | @Test 15 | fun getByTag() { 16 | } 17 | } -------------------------------------------------------------------------------- /domain/src/main/java/com/mctech/domain/services/QuotationService.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.domain.services 2 | 3 | import com.mctech.domain.model.Quotation 4 | 5 | /** 6 | * @author MAYCON CARDOSO on 2019-09-30. 7 | */ 8 | interface QuotationService { 9 | suspend fun getRandom(): Quotation 10 | suspend fun getByTag(tag: String, page : Int?): List 11 | } -------------------------------------------------------------------------------- /features/feature-onboarding/src/main/java/com/mctech/features/onboarding/di/onboardingModule.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.features.onboarding.di 2 | 3 | import com.mctech.features.onboarding.OnboardingViewModel 4 | import org.koin.androidx.viewmodel.dsl.viewModel 5 | import org.koin.dsl.module 6 | 7 | val onboardingModule = module { 8 | viewModel { OnboardingViewModel(get()) } 9 | } -------------------------------------------------------------------------------- /data/src/test/java/com/mctech/data/quotation/QuotationRemoteDataSourceImplTest.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.data.quotation 2 | 3 | import org.junit.Test 4 | 5 | /** 6 | * @author MAYCON CARDOSO on 2019-09-30. 7 | */ 8 | class QuotationRemoteDataSourceImplTest { 9 | 10 | @Test 11 | fun getRandom() { 12 | } 13 | 14 | @Test 15 | fun getByTag() { 16 | } 17 | } -------------------------------------------------------------------------------- /data/src/main/java/com/mctech/data/quotation/datasource/QuotationCacheDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.data.quotation.datasource 2 | 3 | import com.mctech.domain.model.Quotation 4 | 5 | interface QuotationCacheDataSource { 6 | suspend fun saveByTag(tag: String, page: Int?) 7 | suspend fun getRandom(): Quotation? 8 | suspend fun getByTag(tag: String, page: Int?): List? 9 | } -------------------------------------------------------------------------------- /domain/src/main/java/com/mctech/domain/interaction/auth/CheckAuthSessionUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.domain.interaction.auth 2 | 3 | import com.mctech.domain.interaction.Result 4 | import com.mctech.domain.services.AuthService 5 | 6 | class CheckAuthSessionUseCase(private val authService: AuthService) { 7 | suspend fun execute() = Result.Success(authService.fetchLoggedUser() != null) 8 | } 9 | -------------------------------------------------------------------------------- /domain/src/main/java/com/mctech/domain/model/Quotation.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.domain.model 2 | 3 | import java.util.* 4 | 5 | /** 6 | * @author MAYCON CARDOSO on 2019-09-30. 7 | */ 8 | data class Quotation( 9 | val id : String, 10 | val description: String, 11 | val date: Date, 12 | val tag: List, 13 | val author: String, 14 | val twitterLink: String 15 | ) -------------------------------------------------------------------------------- /libraries/library-logger/src/main/java/com/mctech/libraries/logger/Logger.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.libraries.logger 2 | 3 | /** 4 | * @author MAYCON CARDOSO on 2019-07-22. 5 | */ 6 | interface Logger { 7 | fun v(message: String) 8 | fun d(message: String) 9 | fun i(message: String) 10 | fun w(message: String) 11 | fun e(message: String) 12 | fun e(e: Throwable) 13 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mctech/kotlinlearning/di/modules/loggingModule.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.kotlinlearning.di.modules 2 | 3 | import com.mctech.kotlinlearning.platform.logger.LogcatLogger 4 | import com.mctech.libraries.logger.Logger 5 | import org.koin.dsl.module 6 | 7 | /** 8 | * @author MAYCON CARDOSO on 2019-09-05. 9 | */ 10 | val loggingModule = module{ 11 | single{LogcatLogger as Logger} 12 | } -------------------------------------------------------------------------------- /features/feature-login/src/main/java/com/mctech/features/login/state/LoginState.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.features.login.state 2 | 3 | import com.mctech.domain.errors.AuthException 4 | 5 | sealed class LoginState { 6 | object Loading : LoginState() 7 | object Unauthenticated : LoginState() 8 | object Authenticated : LoginState() 9 | data class Error(val error : AuthException) : LoginState() 10 | } -------------------------------------------------------------------------------- /libraries/library-shared-feature-arq/src/main/java/com/mctech/feature/arq/ComponentState.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.feature.arq 2 | 3 | sealed class ComponentState { 4 | object Initializing : ComponentState() 5 | object Loading : ComponentState() 6 | data class Error(val reason: Throwable) : ComponentState() 7 | data class Success(val result: T) : ComponentState() 8 | } -------------------------------------------------------------------------------- /features/feature-login/src/main/java/com/mctech/features/login/di/loginModule.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.features.login.di 2 | 3 | import com.mctech.features.login.LoginViewModel 4 | import org.koin.androidx.viewmodel.dsl.viewModel 5 | import org.koin.dsl.module 6 | 7 | val loginModule = module { 8 | viewModel { 9 | LoginViewModel( 10 | get(), 11 | get() 12 | ) 13 | } 14 | } -------------------------------------------------------------------------------- /libraries/library-analytics/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /features/feature-quotation-random/src/main/java/com/mctech/feature/random_joke/interaction/RandomQuotationInteraction.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.feature.random_joke.interaction 2 | 3 | import com.mctech.feature.arq.UserInteraction 4 | 5 | /** 6 | * @author MAYCON CARDOSO on 2019-11-19. 7 | */ 8 | sealed class RandomQuotationInteraction : UserInteraction { 9 | object LoadRandomQuotation : RandomQuotationInteraction() 10 | } -------------------------------------------------------------------------------- /data/src/test/java/com/mctech/data/quotation/QuotationCacheDataSourceImplTest.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.data.quotation 2 | 3 | import org.junit.Test 4 | 5 | /** 6 | * @author MAYCON CARDOSO on 2019-09-30. 7 | */ 8 | class QuotationCacheDataSourceImplTest { 9 | 10 | @Test 11 | fun saveByTag() { 12 | } 13 | 14 | @Test 15 | fun getRandom() { 16 | } 17 | 18 | @Test 19 | fun getByTag() { 20 | } 21 | } -------------------------------------------------------------------------------- /features/feature-navigation/src/main/java/com/mctech/features/navigation/Screen.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.features.navigation 2 | 3 | sealed class Screen { 4 | object Splash : Screen() 5 | object Login : Screen() 6 | object Dashboard : Screen() 7 | 8 | override fun toString() = when (this) { 9 | Splash -> "Spash Screen" 10 | Login -> "Login Screen" 11 | Dashboard -> "Dashboard Screen" 12 | } 13 | } -------------------------------------------------------------------------------- /data/src/main/java/com/mctech/data/quotation/model/RandomQuotationEmbeddedResponse.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.data.quotation.model 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | /** 6 | * @author MAYCON CARDOSO on 2019-11-06. 7 | */ 8 | class RandomQuotationEmbeddedResponse( 9 | @SerializedName("author") 10 | val author: List, 11 | 12 | @SerializedName("source") 13 | val source: List 14 | ) -------------------------------------------------------------------------------- /libraries/library-analytics/src/main/java/com/mctech/libraries/analytics/AnalyticsHelper.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.libraries.analytics 2 | 3 | import android.app.Activity 4 | 5 | /** 6 | * @author MAYCON CARDOSO on 2019-07-24. 7 | */ 8 | interface AnalyticsHelper{ 9 | fun sendScreenView(screenName: String, activity: Activity) 10 | fun logUiEvent(itemId: String, action: String) 11 | fun setUserSignedIn(isSignedIn: Boolean) 12 | fun setUserRegistered(isRegistered: Boolean) 13 | } -------------------------------------------------------------------------------- /libraries/library-networking/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'kotlin' 2 | 3 | dependencies { 4 | implementation globalDependencies.kotlinStdLib 5 | implementation globalDependencies.kotlinCoroutinesCore 6 | 7 | implementation globalDependencies.okHttp 8 | implementation globalDependencies.retrofit 9 | implementation globalDependencies.retrofitGsonConverter 10 | 11 | testImplementation globalTestDependencies.jUnit 12 | testImplementation globalTestDependencies.assertJ 13 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mctech/kotlinlearning/di/modules/analyticsModule.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.kotlinlearning.di.modules 2 | 3 | import com.mctech.libraries.analytics.AnalyticsHelper 4 | import com.mctech.libraries.analytics.FirebaseAnalyticsHelper 5 | import org.koin.dsl.module 6 | 7 | /** 8 | * @author MAYCON CARDOSO on 2019-09-05. 9 | */ 10 | val analyticsModule = module { 11 | single { 12 | FirebaseAnalyticsHelper( 13 | get() 14 | ) as AnalyticsHelper 15 | } 16 | } -------------------------------------------------------------------------------- /features/feature-login/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /libraries/library-logger/src/main/java/com/mctech/libraries/logger/MutedLogger.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.libraries.logger 2 | 3 | 4 | /** 5 | * @author MAYCON CARDOSO on 2019-07-22. 6 | */ 7 | class MutedLogger : Logger { 8 | override fun v(message: String) = Unit 9 | override fun d(message: String) = Unit 10 | override fun i(message: String) = Unit 11 | override fun w(message: String) = Unit 12 | override fun e(message: String) = Unit 13 | override fun e(e: Throwable) = Unit 14 | } -------------------------------------------------------------------------------- /libraries/library-shared-feature-arq/src/main/java/com/mctech/feature/arq/components/TextViewAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.feature.arq.components 2 | 3 | import android.widget.TextView 4 | import androidx.databinding.BindingAdapter 5 | import java.text.SimpleDateFormat 6 | import java.util.* 7 | 8 | @BindingAdapter("android:text") 9 | fun formatDate(view: TextView, date: Date?) { 10 | if(date == null) return 11 | 12 | view.text = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(date) 13 | } 14 | -------------------------------------------------------------------------------- /domain/src/main/java/com/mctech/domain/validation/EmailValidator.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.domain.validation 2 | 3 | import java.util.regex.Pattern 4 | 5 | 6 | object EmailValidator{ 7 | private val pattern = Pattern.compile( 8 | "^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}])|(([a-zA-Z\\-0-9]+\\.)+[a-zA-Z]{2,}))$" 9 | ) 10 | 11 | operator fun invoke(email : String?) = email?.let { 12 | return pattern.matcher(email).matches() 13 | } ?: false 14 | 15 | 16 | } -------------------------------------------------------------------------------- /domain/src/main/java/com/mctech/domain/services/AuthService.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.domain.services 2 | 3 | import com.mctech.domain.model.AuthRequest 4 | import com.mctech.domain.model.RegisterUser 5 | import com.mctech.domain.model.User 6 | 7 | /** 8 | * @author MAYCON CARDOSO on 2019-07-25. 9 | */ 10 | interface AuthService { 11 | suspend fun fetchLoggedUser(): User? 12 | suspend fun registerUser(registerUser: RegisterUser) : Boolean 13 | suspend fun login(user: AuthRequest) : Boolean 14 | suspend fun logout() 15 | } -------------------------------------------------------------------------------- /features/feature-quotation-random/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /features/feature-quotation-random/src/main/java/com/mctech/feature/random_joke/di/randomQuotationModule.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.feature.random_joke.di 2 | 3 | import com.mctech.feature.random_joke.RandomQuotationViewModel 4 | import org.koin.androidx.viewmodel.dsl.viewModel 5 | import org.koin.dsl.module 6 | 7 | /** 8 | * @author MAYCON CARDOSO on 2019-09-30. 9 | */ 10 | val randomQuotationModel = module { 11 | viewModel { 12 | RandomQuotationViewModel( 13 | getRandomCase = get() 14 | ) 15 | } 16 | } -------------------------------------------------------------------------------- /libraries/library-shared-feature-arq/src/main/java/com/mctech/feature/arq/BaseActivity.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.feature.arq 2 | 3 | import androidx.appcompat.app.AppCompatActivity 4 | import com.mctech.features.navigation.Navigator 5 | import org.koin.android.ext.android.inject 6 | import org.koin.core.parameter.parametersOf 7 | 8 | /** 9 | * @author MAYCON CARDOSO on 2019-09-05. 10 | */ 11 | abstract class BaseActivity : AppCompatActivity() { 12 | val navigator: Navigator by inject { parametersOf(this) } 13 | } -------------------------------------------------------------------------------- /features/feature-navigation/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | 5 | dependencies { 6 | implementation globalDependencies.appCompact 7 | 8 | implementation globalDependencies.koin 9 | 10 | testImplementation globalTestDependencies.jUnit 11 | testImplementation globalTestDependencies.assertJ 12 | testImplementation globalTestDependencies.robolectric 13 | testImplementation globalTestDependencies.mockitoKotlin 14 | } 15 | -------------------------------------------------------------------------------- /features/feature-onboarding/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /data/src/main/java/com/mctech/data/quotation/datasource/QuotationCacheDataSourceImpl.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.data.quotation.datasource 2 | 3 | import com.mctech.domain.model.Quotation 4 | 5 | /** 6 | * @author MAYCON CARDOSO on 2019-09-30. 7 | */ 8 | class QuotationCacheDataSourceImpl : QuotationCacheDataSource { 9 | override suspend fun saveByTag(tag: String, page: Int?) { 10 | } 11 | 12 | override suspend fun getRandom(): Quotation? = null 13 | 14 | override suspend fun getByTag(tag: String, page: Int?): List? = null 15 | 16 | } -------------------------------------------------------------------------------- /libraries/library-analytics/src/main/java/com/mctech/libraries/analytics/MutedAnalyticsHelper.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.libraries.analytics 2 | 3 | import android.app.Activity 4 | 5 | /** 6 | * @author MAYCON CARDOSO on 2019-07-24. 7 | */ 8 | class MutedAnalyticsHelper : AnalyticsHelper { 9 | override fun sendScreenView(screenName: String, activity: Activity) {} 10 | override fun logUiEvent(itemId: String, action: String) {} 11 | override fun setUserSignedIn(isSignedIn: Boolean) {} 12 | override fun setUserRegistered(isRegistered: Boolean) {} 13 | } -------------------------------------------------------------------------------- /libraries/library-shared-feature-arq-testing/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | 5 | dependencies { 6 | implementation project(path: submodulesLibraries.logger) 7 | implementation globalDependencies.lifeCycleLiveData 8 | 9 | api globalTestDependencies.jUnit 10 | api globalTestDependencies.assertJ 11 | api globalTestDependencies.koinTest 12 | api globalTestDependencies.testArqCor 13 | api globalTestDependencies.coroutines 14 | api globalTestDependencies.mockitoKotlin 15 | } -------------------------------------------------------------------------------- /domain/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'kotlin' 2 | 3 | dependencies { 4 | implementation globalDependencies.kotlinStdLib 5 | implementation globalDependencies.kotlinCoroutinesCore 6 | 7 | testImplementation globalTestDependencies.jUnit 8 | testImplementation globalTestDependencies.assertJ 9 | 10 | testImplementation globalTestDependencies.jUnit 11 | testImplementation globalTestDependencies.assertJ 12 | testImplementation globalTestDependencies.coroutines 13 | testImplementation globalTestDependencies.mockitoKotlin 14 | testImplementation globalTestDependencies.testArqCor 15 | } -------------------------------------------------------------------------------- /libraries/library-shared-feature-arq-testing/src/main/java/com/mctech/test/arq/extentions/listAssertionExtention.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.test.arq.extentions 2 | 3 | import org.assertj.core.api.Assertions 4 | 5 | /** 6 | * @author MAYCON CARDOSO on 2019-09-25. 7 | */ 8 | fun List.assertEmpty() = assertCount(0) 9 | fun List.assertCount(count : Int) = Assertions.assertThat(size).isEqualTo(count) 10 | fun List.assertAtPosition(position : Int) = Assertions.assertThat(get(position)) 11 | fun List.assertFirst() = assertAtPosition(0) 12 | fun List.assertLast() = assertAtPosition(size - 1) 13 | -------------------------------------------------------------------------------- /features/feature-login/src/main/java/com/mctech/features/login/interaction/LoginUserInteraction.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.features.login.interaction 2 | 3 | import com.mctech.domain.model.AuthRequest 4 | import com.mctech.domain.model.RegisterUser 5 | import com.mctech.feature.arq.UserInteraction 6 | 7 | sealed class LoginUserInteraction : UserInteraction { 8 | data class NavigateToSignUn(val authRequest: AuthRequest) : LoginUserInteraction() 9 | data class TryLogin(val authRequest: AuthRequest) : LoginUserInteraction() 10 | data class TryRegisterUser(val registerUser: RegisterUser) : LoginUserInteraction() 11 | } -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | include ':data' 3 | include ':domain' 4 | 5 | include ':libraries:library-logger' 6 | include ':libraries:library-analytics' 7 | include ':libraries:library-app-theme' 8 | include ':libraries:library-networking' 9 | include ':libraries:library-shared-feature-arq' 10 | include ':libraries:library-shared-feature-arq-testing' 11 | 12 | include ':features:feature-onboarding' 13 | include ':features:feature-login' 14 | include ':features:feature-quotation-filtering' 15 | include ':features:feature-quotation-random' 16 | include ':features:feature-quotation-list' 17 | include ':features:feature-navigation' -------------------------------------------------------------------------------- /domain/src/main/java/com/mctech/domain/model/AuthRequest.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.domain.model 2 | 3 | import com.mctech.domain.errors.AuthException 4 | import com.mctech.domain.validation.EmailValidator 5 | 6 | /** 7 | * @author MAYCON CARDOSO on 2019-07-25. 8 | */ 9 | data class AuthRequest( 10 | val type: AuthRequestType = AuthRequestType.EMAIL, 11 | val email: String, 12 | var password: String 13 | ) { 14 | fun validateOrThrow() { 15 | if (!EmailValidator(email)) 16 | throw AuthException.InvalidEmailFormatException 17 | } 18 | } 19 | 20 | enum class AuthRequestType { 21 | EMAIL 22 | } 23 | 24 | -------------------------------------------------------------------------------- /libraries/library-shared-feature-arq/src/main/java/com/mctech/feature/arq/components/ViewAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.feature.arq.components 2 | 3 | import android.view.View 4 | import android.view.View.GONE 5 | import android.view.View.VISIBLE 6 | import androidx.databinding.BindingAdapter 7 | 8 | @set:BindingAdapter("gone") 9 | var View.gone 10 | get() = visibility == GONE 11 | set(value) { 12 | visibility = if (value) GONE else VISIBLE 13 | } 14 | 15 | @set:BindingAdapter("visible") 16 | var View.visible 17 | get() = visibility == VISIBLE 18 | set(value) { 19 | visibility = if (value) VISIBLE else GONE 20 | } -------------------------------------------------------------------------------- /libraries/library-shared-feature-arq-testing/src/main/java/com/mctech/test/arq/BaseViewModelTest.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.test.arq 2 | 3 | import com.mctech.test.arq.rules.CoroutinesMainTestRule 4 | import com.mctech.test.arq.rules.KoinModuleTestRule 5 | import kotlinx.coroutines.ExperimentalCoroutinesApi 6 | import org.junit.Rule 7 | import org.koin.test.AutoCloseKoinTest 8 | 9 | /** 10 | * @author MAYCON CARDOSO on 2019-09-25. 11 | */ 12 | @ExperimentalCoroutinesApi 13 | abstract class BaseViewModelTest : AutoCloseKoinTest() { 14 | @get:Rule 15 | val koinRule = KoinModuleTestRule() 16 | @get:Rule 17 | val coroutinesTestRule = CoroutinesMainTestRule() 18 | } -------------------------------------------------------------------------------- /data/src/main/java/com/mctech/data/quotation/model/RandomQuotationResponse.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.data.quotation.model 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import java.util.* 5 | 6 | /** 7 | * @author MAYCON CARDOSO on 2019-11-06. 8 | */ 9 | class RandomQuotationResponse( 10 | 11 | @SerializedName("quote_id") 12 | val id: String, 13 | 14 | @SerializedName("appeared_at") 15 | val date: Date, 16 | 17 | @SerializedName("tags") 18 | val tags: List, 19 | 20 | @SerializedName("value") 21 | val description: String, 22 | 23 | @SerializedName("_embedded") 24 | val embedded: RandomQuotationEmbeddedResponse 25 | 26 | ) -------------------------------------------------------------------------------- /libraries/library-shared-feature-arq/src/main/java/com/mctech/feature/arq/extentions/viewExtentions.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.feature.arq.extentions 2 | 3 | import android.view.View 4 | 5 | 6 | fun View.show() { 7 | this.visibility = View.VISIBLE 8 | } 9 | 10 | fun View.hide() { 11 | this.visibility = View.GONE 12 | } 13 | 14 | fun View.setVisibilityByState(visible: Boolean) { 15 | if (visible) show() 16 | else hide() 17 | } 18 | 19 | fun View.enable() { 20 | this.isEnabled = true 21 | } 22 | 23 | fun View.disable() { 24 | this.isEnabled = false 25 | } 26 | 27 | fun View.enableByState(enabled: Boolean) { 28 | if (enabled) enable() 29 | else disable() 30 | } -------------------------------------------------------------------------------- /domain/src/main/java/com/mctech/domain/errors/AuthException.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.domain.errors 2 | 3 | sealed class AuthException : RuntimeException(){ 4 | object UserNotFoundException : AuthException() 5 | object EmptyFormValueException : AuthException() 6 | object WrongCredentialsException : AuthException() 7 | object NoAuthSessionFoundException : AuthException() 8 | object PasswordUnderSixCharactersException : AuthException() 9 | object PasswordsDoNotMatchException : AuthException() 10 | object UnknownAuthException : AuthException() 11 | 12 | object AlreadyRegisteredUserException : AuthException() 13 | object InvalidEmailFormatException : AuthException() 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mctech/kotlinlearning/di/modules/useCaseModules.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.kotlinlearning.di.modules 2 | 3 | import com.mctech.domain.interaction.auth.AuthenticationUseCase 4 | import com.mctech.domain.interaction.auth.CheckAuthSessionUseCase 5 | import com.mctech.domain.interaction.auth.RegisterUserUseCase 6 | import com.mctech.domain.interaction.quotation.GetRandomQuotationCase 7 | import org.koin.dsl.module 8 | 9 | val useCaseModules = module { 10 | // Auth 11 | factory { CheckAuthSessionUseCase(get()) } 12 | factory { RegisterUserUseCase(get()) } 13 | factory { AuthenticationUseCase(get()) } 14 | 15 | // Quotation 16 | factory { GetRandomQuotationCase(get()) } 17 | } -------------------------------------------------------------------------------- /libraries/library-shared-feature-arq-testing/src/main/java/com/mctech/test/arq/rules/KoinModuleTestRule.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.test.arq.rules 2 | 3 | import com.mctech.libraries.logger.Logger 4 | import com.mctech.libraries.logger.MutedLogger 5 | import org.junit.rules.TestWatcher 6 | import org.junit.runner.Description 7 | import org.koin.core.context.startKoin 8 | import org.koin.dsl.module 9 | 10 | class KoinModuleTestRule : TestWatcher() { 11 | override fun starting(description: Description?) { 12 | super.starting(description) 13 | startKoin { 14 | modules(module { 15 | single { MutedLogger() as Logger } 16 | }) 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /libraries/library-networking/src/main/java/com/mctech/library/networking/secureRequest.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.library.networking 2 | 3 | import kotlinx.coroutines.Dispatchers 4 | import kotlinx.coroutines.withContext 5 | 6 | /** 7 | * This method helps the application to avoid unexpected crashes during some network request. 8 | * Basically, if there is any issue on the request, we can transform the error into 9 | * another one that the app know about. 10 | */ 11 | suspend fun secureRequest(target: suspend () -> T): T = withContext(Dispatchers.IO){ 12 | try { 13 | target.invoke() 14 | } catch (incoming: Throwable) { 15 | throw NetworkErrorTransformer.transform(incoming) 16 | } 17 | } -------------------------------------------------------------------------------- /data/src/main/java/com/mctech/data/quotation/api/QuotationAPI.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.data.quotation.api 2 | 3 | import com.mctech.data.BuildConfig 4 | import com.mctech.data.quotation.model.RandomQuotationResponse 5 | import retrofit2.http.GET 6 | import retrofit2.http.Headers 7 | 8 | /** 9 | * @author MAYCON CARDOSO on 2019-11-06. 10 | */ 11 | interface QuotationAPI { 12 | companion object { 13 | const val API_KEY = BuildConfig.ApiKey 14 | } 15 | 16 | @Headers( 17 | "x-rapidapi-host:matchilling-tronald-dump-v1.p.rapidapi.com", 18 | "accept:application/hal+json", 19 | "x-rapidapi-key:$API_KEY" 20 | ) 21 | @GET("random/quote") 22 | suspend fun getRandom(): RandomQuotationResponse 23 | } -------------------------------------------------------------------------------- /features/feature-navigation/src/main/java/com/mctech/features/navigation/Navigator.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.features.navigation 2 | 3 | import android.content.Intent 4 | import androidx.fragment.app.FragmentActivity 5 | 6 | class Navigator( 7 | private val host: FragmentActivity, 8 | private val links: Map> 9 | ) { 10 | fun navigateTo(destination: Screen, finishHost : Boolean = false) { 11 | val next = Intent(host, find(destination)) 12 | host.startActivity(next) 13 | 14 | if(finishHost) host.finish() 15 | } 16 | 17 | private fun find(target: Screen) = 18 | links[target] 19 | ?.let { it } 20 | ?: throw UnsupportedNavigation(target) 21 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mctech/kotlinlearning/di/modules/navigatorModule.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.kotlinlearning.di.modules 2 | 3 | import androidx.fragment.app.FragmentActivity 4 | import com.mctech.feature.random_joke.RandomQuotationActivity 5 | import com.mctech.features.login.LoginActivity 6 | import com.mctech.features.navigation.Navigator 7 | import com.mctech.features.navigation.Screen 8 | import org.koin.dsl.module 9 | 10 | 11 | val navigatorModule = module { 12 | single { 13 | mapOf>( 14 | Screen.Login to LoginActivity::class.java, 15 | Screen.Dashboard to RandomQuotationActivity::class.java 16 | ) 17 | } 18 | 19 | factory { (view: FragmentActivity) -> Navigator(view, get()) } 20 | } -------------------------------------------------------------------------------- /libraries/library-networking/src/main/java/com/mctech/library/networking/RetrofitBuilder.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.library.networking 2 | 3 | import com.google.gson.GsonBuilder 4 | import okhttp3.OkHttpClient 5 | import retrofit2.Retrofit 6 | import retrofit2.converter.gson.GsonConverterFactory 7 | 8 | /** 9 | * @author MAYCON CARDOSO on 2019-09-28. 10 | */ 11 | object RetrofitBuilder { 12 | 13 | operator fun invoke(apiURL: String, httpClient: OkHttpClient) = 14 | with(Retrofit.Builder()) { 15 | val gson = GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ss").create() 16 | 17 | baseUrl(apiURL) 18 | client(httpClient) 19 | addConverterFactory(GsonConverterFactory.create(gson)) 20 | build() 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 18 | 19 | -------------------------------------------------------------------------------- /libraries/library-shared-feature-arq/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-kapt' 3 | apply plugin: 'kotlin-android' 4 | apply plugin: 'kotlin-android-extensions' 5 | 6 | dependencies { 7 | implementation project(path: submodulesFeatures.navigation) 8 | implementation project(path: submodulesLibraries.logger) 9 | implementation globalDependencies.appCompact 10 | 11 | implementation globalDependencies.lifeCycleViewModel 12 | implementation globalDependencies.lifeCycleLiveRuntime 13 | implementation globalDependencies.lifeCycleLiveData 14 | implementation globalDependencies.koin 15 | implementation globalDependencies.koinScope 16 | 17 | 18 | testImplementation project(path: submodulesTest.sharedFeatureArq) 19 | } -------------------------------------------------------------------------------- /libraries/library-shared-feature-arq/src/main/java/com/mctech/feature/arq/BaseFragment.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.feature.arq 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.annotation.LayoutRes 8 | import androidx.fragment.app.Fragment 9 | import androidx.lifecycle.ViewModel 10 | 11 | /** 12 | * @author MAYCON CARDOSO on 2019-09-05. 13 | */ 14 | abstract class BaseFragment : Fragment() { 15 | @LayoutRes 16 | abstract fun getLayoutId(): Int 17 | 18 | override fun onCreateView( 19 | inflater: LayoutInflater, container: ViewGroup?, 20 | savedInstanceState: Bundle? 21 | ) : View = inflater.inflate(getLayoutId(), container, false) 22 | } 23 | -------------------------------------------------------------------------------- /data/src/main/java/com/mctech/data/networkRequestHandler.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.data 2 | 3 | import com.mctech.domain.errors.NetworkException 4 | import com.mctech.library.networking.secureRequest 5 | 6 | 7 | // Called just to handle a secure request returning the response or throwing an error known by the app. 8 | suspend fun networkRequestHandler(target: suspend () -> T) = try { 9 | // Call method who map all networking exception when it happen 10 | secureRequest(target) 11 | } catch (error: Exception) { 12 | throw NetworkException() 13 | } 14 | 15 | suspend fun networkRequestSilentErrorHandler(target: suspend () -> T) = try { 16 | // Call method who map all networking exception when it happen 17 | secureRequest(target) 18 | } catch (error: Exception) { 19 | error.printStackTrace() 20 | null 21 | } -------------------------------------------------------------------------------- /data/src/main/java/com/mctech/data/quotation/datasource/QuotationRemoteDataSourceImpl.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.data.quotation.datasource 2 | 3 | import com.mctech.data.quotation.api.QuotationAPI 4 | import com.mctech.domain.model.Quotation 5 | 6 | /** 7 | * @author MAYCON CARDOSO on 2019-09-30. 8 | */ 9 | class QuotationRemoteDataSourceImpl(private val api: QuotationAPI) : 10 | QuotationDataSource { 11 | override suspend fun getRandom() = api.getRandom().let { 12 | Quotation( 13 | it.id, 14 | it.description, 15 | it.date, 16 | it.tags, 17 | it.embedded.author[0].name, 18 | it.embedded.source[0].url 19 | ) 20 | } 21 | 22 | override suspend fun getByTag(tag: String, page: Int?): List { 23 | TODO() 24 | } 25 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /domain/src/test/kotlin/com/mctech/domain/validation/PasswordlValidatorTest.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.domain.validation 2 | 3 | import org.junit.Assert.assertFalse 4 | import org.junit.Assert.assertTrue 5 | import org.junit.Test 6 | 7 | /** 8 | * @author MAYCON CARDOSO on 2019-09-21. 9 | */ 10 | class PasswordlValidatorTest { 11 | 12 | @Test 13 | fun `should validate`() { 14 | assertTrue(PasswordlValidator("dsa@asio@90190")) 15 | assertTrue(PasswordlValidator("123456")) 16 | assertTrue(PasswordlValidator("1234567")) 17 | } 18 | 19 | @Test 20 | fun `should fail when password under 5 characters`() { 21 | assertFalse(PasswordlValidator("1")) 22 | assertFalse(PasswordlValidator("12")) 23 | assertFalse(PasswordlValidator("123")) 24 | assertFalse(PasswordlValidator("1234")) 25 | } 26 | } -------------------------------------------------------------------------------- /libraries/library-networking/src/test/java/com/mctech/library/networking/SecureRequestKtTest.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.library.networking 2 | 3 | import kotlinx.coroutines.runBlocking 4 | import org.assertj.core.api.Assertions.assertThat 5 | import org.junit.Test 6 | 7 | /** 8 | * @author MAYCON CARDOSO on 2019-09-28. 9 | */ 10 | class SecureRequestKtTest { 11 | 12 | @Test 13 | fun `should return a known network error`() { 14 | runBlocking { 15 | 16 | val result = runCatching { 17 | secureRequest(suspend { 18 | throw Throwable() 19 | }) 20 | }.exceptionOrNull() 21 | 22 | assertThat(result) 23 | .isExactlyInstanceOf( 24 | NetworkError.UnknownNetworkingError::class.java 25 | ) 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /domain/src/test/kotlin/com/mctech/domain/model/AuthRequestTest.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.domain.model 2 | 3 | import com.mctech.domain.TestDataFactory 4 | import com.mctech.domain.errors.AuthException 5 | import org.assertj.core.api.Assertions 6 | import org.junit.Test 7 | 8 | /** 9 | * @author MAYCON CARDOSO on 2019-09-21. 10 | */ 11 | class AuthRequestTest { 12 | @Test 13 | fun `should throw when email fail`() { 14 | val request = TestDataFactory.createAuthRequest("") 15 | Assertions.assertThatThrownBy { request.validateOrThrow() } 16 | .isEqualTo( 17 | AuthException.InvalidEmailFormatException 18 | ) 19 | } 20 | 21 | @Test 22 | fun `should validate`() { 23 | val request = TestDataFactory.createAuthRequest("maycon.santos.cardoso@gmail.com") 24 | request.validateOrThrow() 25 | } 26 | } -------------------------------------------------------------------------------- /domain/src/main/java/com/mctech/domain/interaction/quotation/GetRandomQuotationCase.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.domain.interaction.quotation 2 | 3 | import com.mctech.domain.errors.QuotationException 4 | import com.mctech.domain.interaction.Result 5 | import com.mctech.domain.model.Quotation 6 | import com.mctech.domain.services.QuotationService 7 | 8 | /** 9 | * @author MAYCON CARDOSO on 2019-09-30. 10 | */ 11 | class GetRandomQuotationCase(private val quotationService: QuotationService) { 12 | suspend fun execute(): Result { 13 | return try { 14 | Result.Success(quotationService.getRandom()) 15 | } catch (exception: Exception) { 16 | Result.Failure( 17 | if (exception is QuotationException) exception 18 | else QuotationException.UnknownQuotationException 19 | ) 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mctech/kotlinlearning/di/modules/networkingModule.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.kotlinlearning.di.modules 2 | 3 | import com.mctech.library.networking.RetrofitBuilder 4 | import okhttp3.OkHttpClient 5 | import okhttp3.logging.HttpLoggingInterceptor 6 | import org.koin.dsl.module 7 | import retrofit2.Retrofit 8 | 9 | /** 10 | * @author MAYCON CARDOSO on 2019-09-05. 11 | */ 12 | val networkingModule = module { 13 | single { 14 | val logger = HttpLoggingInterceptor().apply { 15 | level = HttpLoggingInterceptor.Level.BODY 16 | } 17 | 18 | OkHttpClient.Builder() 19 | .addInterceptor(logger) 20 | .build() 21 | } 22 | 23 | single { 24 | RetrofitBuilder( 25 | apiURL = "https://matchilling-tronald-dump-v1.p.rapidapi.com", 26 | httpClient = get() 27 | ) as Retrofit 28 | } 29 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mctech/kotlinlearning/platform/logger/LogcatLogger.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.kotlinlearning.platform.logger 2 | 3 | import android.util.Log 4 | import com.mctech.libraries.logger.Logger 5 | 6 | /** 7 | * @author MAYCON CARDOSO on 2019-07-22. 8 | */ 9 | internal object LogcatLogger : Logger { 10 | const val TAG = "com.mctech.logger" 11 | 12 | override fun v(message: String) { 13 | Log.v(TAG, message) 14 | } 15 | 16 | override fun d(message: String) { 17 | Log.d(TAG, message) 18 | } 19 | 20 | override fun i(message: String) { 21 | Log.i(TAG, message) 22 | } 23 | 24 | override fun w(message: String) { 25 | Log.w(TAG, message) 26 | } 27 | 28 | override fun e(message: String) { 29 | Log.e(TAG, message) 30 | } 31 | 32 | override fun e(e: Throwable) { 33 | Log.e(TAG, e.message, e) 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /domain/src/main/java/com/mctech/domain/model/RegisterUser.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.domain.model 2 | 3 | import com.mctech.domain.errors.AuthException 4 | import com.mctech.domain.validation.EmailValidator 5 | import com.mctech.domain.validation.PasswordlValidator 6 | 7 | /** 8 | * @author MAYCON CARDOSO on 2019-07-25. 9 | */ 10 | data class RegisterUser( 11 | val user: User, 12 | val password: String, 13 | val passwordConfirmation : String 14 | ) { 15 | fun validateOrThrow() { 16 | if (user.name.isEmpty()) 17 | throw AuthException.EmptyFormValueException 18 | 19 | if (!EmailValidator(user.email)) 20 | throw AuthException.InvalidEmailFormatException 21 | 22 | if (!PasswordlValidator(password)) 23 | throw AuthException.PasswordUnderSixCharactersException 24 | 25 | if (password != passwordConfirmation) 26 | throw AuthException.PasswordsDoNotMatchException 27 | } 28 | } -------------------------------------------------------------------------------- /features/feature-onboarding/src/main/res/layout/activity_on_boarding.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 17 | 18 | 22 | -------------------------------------------------------------------------------- /features/feature-login/src/main/java/com/mctech/features/login/LoginActivity.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.features.login 2 | 3 | import android.os.Bundle 4 | import com.mctech.feature.arq.BaseActivity 5 | import com.mctech.feature.arq.extentions.bindData 6 | import com.mctech.features.login.state.LoginState 7 | import com.mctech.features.navigation.Screen 8 | import org.koin.androidx.viewmodel.ext.android.viewModel 9 | 10 | class LoginActivity : BaseActivity() { 11 | private val loginViewModel: LoginViewModel by viewModel() 12 | 13 | override fun onCreate(savedInstanceState: Bundle?) { 14 | super.onCreate(savedInstanceState) 15 | setContentView(R.layout.activity_login) 16 | 17 | bindData(loginViewModel.loginScreenState) { 18 | when (it) { 19 | is LoginState.Authenticated -> navigator.navigateTo( 20 | destination = Screen.Dashboard, 21 | finishHost = true 22 | ) 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /domain/src/main/java/com/mctech/domain/interaction/auth/AuthenticationUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.domain.interaction.auth 2 | 3 | import com.mctech.domain.errors.AuthException 4 | import com.mctech.domain.interaction.Result 5 | import com.mctech.domain.model.AuthRequest 6 | import com.mctech.domain.model.User 7 | import com.mctech.domain.services.AuthService 8 | 9 | class AuthenticationUseCase(private val authService: AuthService) { 10 | suspend fun execute(authRequest: AuthRequest): Result { 11 | try { 12 | authRequest.validateOrThrow() 13 | 14 | if (authService.login(authRequest)) { 15 | return Result.Success(authService.fetchLoggedUser()!!) 16 | } 17 | } catch (exception: Throwable) { 18 | return Result.Failure( 19 | if (exception is AuthException) exception 20 | else AuthException.UnknownAuthException 21 | ) 22 | } 23 | 24 | return Result.Failure(AuthException.UnknownAuthException) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /libraries/library-shared-feature-arq-testing/src/main/java/com/mctech/test/arq/extentions/livedataTestExtentions.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.test.arq.extentions 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.lifecycle.Observer 5 | import kotlinx.coroutines.runBlocking 6 | 7 | fun LiveData.collectValuesForTesting(assertion: (List) -> Unit) { 8 | test { assertion(it) } 9 | } 10 | 11 | fun LiveData.test( 12 | scenario: suspend () -> Unit = {}, 13 | action: () -> Unit = {}, 14 | assertion: (List) -> Unit 15 | ) { 16 | val emittedValues = mutableListOf() 17 | val observer = Observer { 18 | emittedValues.add(it) 19 | } 20 | 21 | try { 22 | runBlocking { 23 | scenario() 24 | observeForever(observer) 25 | action() 26 | assertion(emittedValues) 27 | } 28 | } finally { 29 | removeObserver(observer) 30 | } 31 | } 32 | 33 | fun LiveData.assertNoValue() { 34 | collectValuesForTesting { it.assertEmpty() } 35 | } -------------------------------------------------------------------------------- /libraries/library-shared-feature-arq-testing/src/main/java/com/mctech/test/arq/rules/CoroutinesMainTestRule.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.test.arq.rules 2 | 3 | import androidx.arch.core.executor.testing.InstantTaskExecutorRule 4 | import kotlinx.coroutines.Dispatchers 5 | import kotlinx.coroutines.ExperimentalCoroutinesApi 6 | import kotlinx.coroutines.test.TestCoroutineDispatcher 7 | import kotlinx.coroutines.test.resetMain 8 | import kotlinx.coroutines.test.setMain 9 | import org.junit.runner.Description 10 | 11 | @ExperimentalCoroutinesApi 12 | class CoroutinesMainTestRule( 13 | val testDispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher() 14 | ) : InstantTaskExecutorRule() { 15 | 16 | override fun starting(description: Description?) { 17 | super.starting(description) 18 | Dispatchers.setMain(testDispatcher) 19 | } 20 | 21 | override fun finished(description: Description?) { 22 | super.finished(description) 23 | Dispatchers.resetMain() 24 | testDispatcher.cleanupTestCoroutines() 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /features/feature-onboarding/src/main/java/com/mctech/features/onboarding/OnboardingViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.features.onboarding 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.lifecycle.liveData 5 | import com.mctech.domain.interaction.auth.CheckAuthSessionUseCase 6 | import com.mctech.feature.arq.BaseViewModel 7 | import com.mctech.feature.arq.ComponentState 8 | import com.mctech.features.onboarding.state.OnBoardingNavigationState 9 | 10 | class OnboardingViewModel(private val checkAuthSessionUseCase: CheckAuthSessionUseCase) : BaseViewModel() { 11 | 12 | val userFlowState: LiveData> = liveData { 13 | emit(ComponentState.Loading) 14 | val isUserLogged = checkAuthSessionUseCase.execute().result 15 | emit( 16 | ComponentState.Success( 17 | if (isUserLogged) 18 | OnBoardingNavigationState.Authorized 19 | else 20 | OnBoardingNavigationState.Unauthorized 21 | ) 22 | ) 23 | } 24 | } -------------------------------------------------------------------------------- /domain/src/main/java/com/mctech/domain/interaction/auth/RegisterUserUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.domain.interaction.auth 2 | 3 | import com.mctech.domain.errors.AuthException 4 | import com.mctech.domain.interaction.Result 5 | import com.mctech.domain.model.RegisterUser 6 | import com.mctech.domain.model.User 7 | import com.mctech.domain.services.AuthService 8 | 9 | class RegisterUserUseCase(private val authService: AuthService) { 10 | suspend fun execute(registerUser: RegisterUser): Result { 11 | try { 12 | registerUser.validateOrThrow() 13 | 14 | // Register user 15 | if (authService.registerUser(registerUser)) { 16 | return Result.Success(authService.fetchLoggedUser()!!) 17 | } 18 | } catch (exception: Throwable) { 19 | return Result.Failure( 20 | if (exception is AuthException) exception 21 | else AuthException.UnknownAuthException 22 | ) 23 | } 24 | 25 | return Result.Failure(AuthException.UnknownAuthException) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /features/feature-onboarding/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | 5 | dependencies { 6 | implementation project(path: submodulesPlatform.domain) 7 | implementation project(path: submodulesFeatures.navigation) 8 | implementation project(path: submodulesLibraries.sharedFeatureArq) 9 | implementation project(path: submodulesLibraries.appTheme) 10 | 11 | implementation globalDependencies.koin 12 | implementation globalDependencies.koinViewModel 13 | 14 | implementation globalDependencies.kotlinCoroutinesCore 15 | implementation globalDependencies.kotlinCoroutinesAndroid 16 | 17 | implementation globalDependencies.lifeCycleLiveRuntime 18 | implementation globalDependencies.lifeCycleLiveExtentions 19 | implementation globalDependencies.lifeCycleViewModel 20 | implementation globalDependencies.lifeCycleLiveData 21 | 22 | implementation globalDependencies.appCompact 23 | 24 | 25 | testImplementation project(path: submodulesTest.sharedFeatureArq) 26 | } 27 | -------------------------------------------------------------------------------- /libraries/library-networking/src/main/java/com/mctech/library/networking/NetworkError.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.library.networking 2 | 3 | /** 4 | * @author MAYCON CARDOSO on 2019-09-28. 5 | */ 6 | sealed class NetworkError : Throwable() { 7 | object ClientException : NetworkError() 8 | object RemoteException : NetworkError() 9 | 10 | object HostUnreachable : NetworkError() 11 | object OperationTimeout : NetworkError() 12 | object ConnectionSpike : NetworkError() 13 | object UnknownNetworkingError : NetworkError() 14 | 15 | override fun toString() = 16 | when (this) { 17 | ClientException -> "Issue originated from client" 18 | RemoteException -> "Issue incoming from server" 19 | 20 | HostUnreachable -> "Cannot reach remote host" 21 | OperationTimeout -> "Networking operation timed out" 22 | ConnectionSpike -> "In-flight networking operation interrupted" 23 | UnknownNetworkingError -> "Fatal networking exception" 24 | } 25 | } -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | working_directory: ~/code 5 | docker: 6 | - image: circleci/android:api-28 7 | environment: 8 | JVM_OPTS: -Xmx3200m 9 | steps: 10 | - checkout 11 | - restore_cache: 12 | key: jars-{{ checksum "build.gradle" }}-{{ checksum "app/build.gradle" }} 13 | 14 | - run: 15 | name: Store Google Service File 16 | command: echo $GOOGLE_SERVICES > app/google-services.json 17 | 18 | - run: 19 | name: Store Security API Key 20 | command: echo $SECURITY_API_KEY > securityKeys.properties 21 | 22 | - run: 23 | name: Download Dependencies 24 | command: ./gradlew androidDependencies 25 | 26 | - save_cache: 27 | paths: 28 | - ~/.gradle 29 | key: jars-{{ checksum "build.gradle" }}-{{ checksum "app/build.gradle" }} 30 | 31 | - run: 32 | name: Run Tests 33 | command: ./gradlew test 34 | 35 | - store_artifacts: 36 | path: app/build/reports 37 | destination: reports 38 | 39 | - store_test_results: 40 | path: app/build/test-results -------------------------------------------------------------------------------- /features/feature-quotation-list/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | 5 | dependencies { 6 | implementation project(path: submodulesPlatform.domain) 7 | implementation project(path: submodulesFeatures.navigation) 8 | implementation project(path: submodulesLibraries.sharedFeatureArq) 9 | implementation project(path: submodulesLibraries.appTheme) 10 | 11 | implementation globalDependencies.koin 12 | implementation globalDependencies.koinViewModel 13 | 14 | implementation globalDependencies.kotlinCoroutinesCore 15 | implementation globalDependencies.kotlinCoroutinesAndroid 16 | 17 | implementation globalDependencies.lifeCycleLiveRuntime 18 | implementation globalDependencies.lifeCycleLiveExtentions 19 | implementation globalDependencies.lifeCycleViewModel 20 | implementation globalDependencies.lifeCycleLiveData 21 | 22 | implementation globalDependencies.appCompact 23 | implementation globalDependencies.constraintlayout 24 | implementation globalDependencies.materialDesign 25 | 26 | 27 | testImplementation project(path: submodulesTest.sharedFeatureArq) 28 | } -------------------------------------------------------------------------------- /features/feature-quotation-filtering/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | 5 | dependencies { 6 | implementation project(path: submodulesPlatform.domain) 7 | implementation project(path: submodulesFeatures.navigation) 8 | implementation project(path: submodulesLibraries.sharedFeatureArq) 9 | implementation project(path: submodulesLibraries.appTheme) 10 | 11 | implementation globalDependencies.koin 12 | implementation globalDependencies.koinViewModel 13 | 14 | implementation globalDependencies.kotlinCoroutinesCore 15 | implementation globalDependencies.kotlinCoroutinesAndroid 16 | 17 | implementation globalDependencies.lifeCycleLiveRuntime 18 | implementation globalDependencies.lifeCycleLiveExtentions 19 | implementation globalDependencies.lifeCycleViewModel 20 | implementation globalDependencies.lifeCycleLiveData 21 | 22 | implementation globalDependencies.appCompact 23 | implementation globalDependencies.constraintlayout 24 | implementation globalDependencies.materialDesign 25 | 26 | 27 | testImplementation project(path: submodulesTest.sharedFeatureArq) 28 | } -------------------------------------------------------------------------------- /features/feature-quotation-random/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | 5 | dependencies { 6 | implementation project(path: submodulesPlatform.domain) 7 | implementation project(path: submodulesFeatures.navigation) 8 | implementation project(path: submodulesLibraries.sharedFeatureArq) 9 | implementation project(path: submodulesLibraries.appTheme) 10 | 11 | implementation globalDependencies.koin 12 | implementation globalDependencies.koinViewModel 13 | 14 | implementation globalDependencies.kotlinCoroutinesCore 15 | implementation globalDependencies.kotlinCoroutinesAndroid 16 | 17 | implementation globalDependencies.lifeCycleLiveRuntime 18 | implementation globalDependencies.lifeCycleLiveExtentions 19 | implementation globalDependencies.lifeCycleViewModel 20 | implementation globalDependencies.lifeCycleLiveData 21 | 22 | implementation globalDependencies.appCompact 23 | implementation globalDependencies.constraintlayout 24 | implementation globalDependencies.materialDesign 25 | 26 | 27 | testImplementation project(path: submodulesTest.sharedFeatureArq) 28 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official 22 | -------------------------------------------------------------------------------- /features/feature-login/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Sign in 3 | Name 4 | Email 5 | Password 6 | Sign in 7 | Sign up 8 | "Welcome!" 9 | 10 | Não foi possível autenticar no momento! 11 | A senha deve possuir ao menos 5 caracteres 12 | Usuário não encontrado 13 | O formato do e-mail é invalido 14 | Usuário ou senha inválida 15 | Você deve informar seu usuário e senha 16 | Confirm password 17 | O endereço de e-mail informado já está cadastrado 18 | As senhas informadas são diferentes 19 | 20 | -------------------------------------------------------------------------------- /data/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | 5 | android { 6 | def props = new Properties() 7 | file("../securityKeys.properties").withInputStream { props.load(it) } 8 | 9 | buildTypes { 10 | debug{ 11 | buildConfigField "String", "ApiKey", props.getProperty("apiKey") 12 | } 13 | release { 14 | buildConfigField "String", "ApiKey", props.getProperty("apiKey") 15 | } 16 | } 17 | } 18 | 19 | dependencies { 20 | implementation project(path: submodulesPlatform.domain) 21 | implementation project(path: submodulesLibraries.networking) 22 | 23 | implementation globalDependencies.kotlinStdLib 24 | implementation globalDependencies.kotlinCoroutinesCore 25 | implementation globalDependencies.kotlinCoroutinesAndroid 26 | implementation globalDependencies.kotlinCoroutinesPlayService 27 | 28 | implementation globalDependencies.koin 29 | 30 | implementation globalDependencies.firebaseAuth 31 | implementation globalDependencies.retrofit 32 | implementation globalDependencies.retrofitGsonConverter 33 | 34 | 35 | testImplementation globalTestDependencies.jUnit 36 | testImplementation globalTestDependencies.assertJ 37 | } -------------------------------------------------------------------------------- /domain/src/test/kotlin/com/mctech/domain/TestDataFactory.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.domain 2 | 3 | import com.mctech.domain.model.AuthRequest 4 | import com.mctech.domain.model.Quotation 5 | import com.mctech.domain.model.RegisterUser 6 | import com.mctech.domain.model.User 7 | import java.util.* 8 | 9 | object TestDataFactory{ 10 | fun createAuthRequest(email: String) = AuthRequest( 11 | email = email, 12 | password = "" 13 | ) 14 | 15 | fun createRegisterUserRequest( 16 | email: String = "", 17 | name : String = "", 18 | password : String = "", 19 | passwordConfirmation : String = "" 20 | ) = RegisterUser( 21 | user = User( 22 | name = name, 23 | email = email 24 | ), 25 | password = password, 26 | passwordConfirmation = passwordConfirmation 27 | ) 28 | 29 | fun createQuotation( 30 | id : String = "", 31 | tag : String = "", 32 | description: String = "", 33 | date : Date = Date(), 34 | author : String = "", 35 | twitterLink : String = "" 36 | ) = Quotation( 37 | id = id, 38 | description = description, 39 | date = date, 40 | author = author, 41 | twitterLink = twitterLink, 42 | tag = listOf(tag) 43 | ) 44 | } -------------------------------------------------------------------------------- /features/feature-login/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | 5 | dependencies { 6 | implementation project(path: submodulesPlatform.domain) 7 | implementation project(path: submodulesFeatures.navigation) 8 | implementation project(path: submodulesLibraries.sharedFeatureArq) 9 | implementation project(path: submodulesLibraries.appTheme) 10 | 11 | implementation globalDependencies.koin 12 | implementation globalDependencies.koinViewModel 13 | 14 | implementation globalDependencies.kotlinCoroutinesCore 15 | implementation globalDependencies.kotlinCoroutinesAndroid 16 | 17 | implementation globalDependencies.lifeCycleLiveRuntime 18 | implementation globalDependencies.lifeCycleLiveExtentions 19 | implementation globalDependencies.lifeCycleViewModel 20 | implementation globalDependencies.lifeCycleLiveData 21 | 22 | implementation globalDependencies.navigationFragment 23 | implementation globalDependencies.navigationFragmentUi 24 | 25 | implementation globalDependencies.appCompact 26 | implementation globalDependencies.constraintlayout 27 | implementation globalDependencies.materialDesign 28 | 29 | 30 | testImplementation project(path: submodulesTest.sharedFeatureArq) 31 | } -------------------------------------------------------------------------------- /features/feature-login/src/main/res/navigation/login_nav_graph.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 20 | 21 | 25 | -------------------------------------------------------------------------------- /features/feature-login/src/main/res/layout/activity_login.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 16 | 17 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the ART/Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | 16 | # Gradle files 17 | .gradle/ 18 | build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | 29 | # Android Studio Navigation editor temp files 30 | .navigation/ 31 | 32 | # Android Studio captures folder 33 | captures/ 34 | 35 | # IntelliJ 36 | *.iml 37 | .idea/workspace.xml 38 | .idea/tasks.xml 39 | .idea/gradle.xml 40 | .idea/assetWizardSettings.xml 41 | .idea/dictionaries 42 | .idea/libraries 43 | .idea/caches 44 | .idea/* 45 | 46 | # Keystore files 47 | # Uncomment the following line if you do not want to check your keystore files in. 48 | #*.jks 49 | 50 | # External native build folder generated in Android Studio 2.2 and later 51 | .externalNativeBuild 52 | 53 | # Google Services (e.g. APIs or Firebase) 54 | google-services.json 55 | 56 | # Freeline 57 | freeline.py 58 | freeline/ 59 | freeline_project_description.json 60 | 61 | # fastlane 62 | fastlane/report.xml 63 | fastlane/Preview.html 64 | fastlane/screenshots 65 | fastlane/test_output 66 | fastlane/readme.md 67 | 68 | # Security 69 | securityKeys.properties 70 | -------------------------------------------------------------------------------- /app/src/main/java/com/mctech/kotlinlearning/App.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.kotlinlearning 2 | 3 | import androidx.multidex.MultiDexApplication 4 | import com.mctech.data.di.dataModule 5 | import com.mctech.feature.random_joke.di.randomQuotationModel 6 | import com.mctech.features.login.di.loginModule 7 | import com.mctech.features.onboarding.di.onboardingModule 8 | import com.mctech.kotlinlearning.di.modules.* 9 | import org.koin.android.ext.koin.androidContext 10 | import org.koin.android.ext.koin.androidLogger 11 | import org.koin.core.context.startKoin 12 | 13 | class App : MultiDexApplication(){ 14 | override fun onCreate() { 15 | super.onCreate() 16 | initDependencyInjection() 17 | } 18 | 19 | private fun initDependencyInjection() { 20 | startKoin{ 21 | androidLogger() 22 | androidContext(this@App) 23 | modules(listOf( 24 | // Platform 25 | dataModule, 26 | useCaseModules, 27 | 28 | // Libraries 29 | loggingModule, 30 | analyticsModule, 31 | networkingModule, 32 | 33 | // Features 34 | navigatorModule, 35 | onboardingModule, 36 | loginModule, 37 | randomQuotationModel 38 | )) 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /data/src/main/java/com/mctech/data/di/dataModule.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.data.di 2 | 3 | import com.google.firebase.auth.FirebaseAuth 4 | import com.mctech.data.auth.AuthRepository 5 | import com.mctech.data.quotation.api.QuotationAPI 6 | import com.mctech.data.quotation.datasource.QuotationCacheDataSource 7 | import com.mctech.data.quotation.datasource.QuotationCacheDataSourceImpl 8 | import com.mctech.data.quotation.datasource.QuotationDataSource 9 | import com.mctech.data.quotation.datasource.QuotationRemoteDataSourceImpl 10 | import com.mctech.data.quotation.repository.QuotationRepository 11 | import com.mctech.domain.services.AuthService 12 | import com.mctech.domain.services.QuotationService 13 | import org.koin.dsl.module 14 | import retrofit2.Retrofit 15 | 16 | val dataModule = module { 17 | // Auth 18 | single { FirebaseAuth.getInstance() } 19 | single { 20 | AuthRepository( 21 | firebaseAuth = get() 22 | ) 23 | } 24 | 25 | // Quotation 26 | single { QuotationCacheDataSourceImpl() } 27 | single { 28 | val retrofit = get() 29 | val api = retrofit.create(QuotationAPI::class.java) 30 | 31 | QuotationRemoteDataSourceImpl(api) 32 | } 33 | single { 34 | QuotationRepository( 35 | cacheDataSource = get(), 36 | remoteDataSource = get() 37 | ) 38 | } 39 | } -------------------------------------------------------------------------------- /libraries/library-shared-feature-arq/src/main/java/com/mctech/feature/arq/BaseViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.feature.arq 2 | 3 | import androidx.annotation.VisibleForTesting 4 | import androidx.lifecycle.ViewModel 5 | import androidx.lifecycle.viewModelScope 6 | import com.mctech.libraries.logger.Logger 7 | import kotlinx.coroutines.launch 8 | import org.koin.core.context.GlobalContext.get 9 | import java.lang.reflect.Modifier.PROTECTED 10 | 11 | /** 12 | * @author MAYCON CARDOSO on 2019-09-05. 13 | */ 14 | open class BaseViewModel : ViewModel() { 15 | val logger: Logger by get().koin.inject() 16 | 17 | private var userFlowInteraction = mutableListOf() 18 | 19 | fun interact(userInteraction: UserInteraction) { 20 | viewModelScope.launch { 21 | suspendedInteraction(userInteraction) 22 | } 23 | } 24 | 25 | suspend fun suspendedInteraction(userInteraction: UserInteraction) { 26 | userFlowInteraction.add(userInteraction) 27 | handleUserInteraction(userInteraction) 28 | } 29 | 30 | @VisibleForTesting(otherwise = PROTECTED) 31 | open suspend fun handleUserInteraction(interaction: UserInteraction) = Unit 32 | 33 | fun reprocessLastInteraction() { 34 | viewModelScope.launch { 35 | handleUserInteraction(userFlowInteraction.last()) 36 | } 37 | } 38 | 39 | override fun onCleared() { 40 | userFlowInteraction.clear() 41 | super.onCleared() 42 | } 43 | } -------------------------------------------------------------------------------- /domain/src/test/kotlin/com/mctech/domain/validation/EmailValidatorTest.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.domain.validation 2 | 3 | import org.junit.Assert.assertFalse 4 | import org.junit.Assert.assertTrue 5 | import org.junit.Test 6 | 7 | /** 8 | * @author MAYCON CARDOSO on 2019-09-21. 9 | */ 10 | class EmailValidatorTest { 11 | 12 | @Test 13 | fun `should validate`() { 14 | assertTrue(EmailValidator("maycon.santos.cardoso@gmail.com")) 15 | assertTrue(EmailValidator("maycon.santos.cardoso@gmail.com.br")) 16 | assertTrue(EmailValidator("maycon.1994_teste@gmail.com.br")) 17 | } 18 | 19 | @Test 20 | fun `should fail when email empty`() { 21 | assertFalse(EmailValidator("")) 22 | } 23 | 24 | @Test 25 | fun `should fail when email null`() { 26 | assertFalse(EmailValidator(null)) 27 | } 28 | 29 | @Test 30 | fun `should fail when email without at`() { 31 | assertFalse(EmailValidator("teste.teste.com.br")) 32 | assertFalse(EmailValidator("testetesteteste")) 33 | } 34 | 35 | @Test 36 | fun `should fail when email double at`() { 37 | assertFalse(EmailValidator("teste@@teste.com")) 38 | assertFalse(EmailValidator("teste@teste@teste.com")) 39 | } 40 | 41 | @Test 42 | fun `should fail when email double dot`() { 43 | assertFalse(EmailValidator("teste@teste..com")) 44 | } 45 | @Test 46 | fun `should fail when email dot in the end`() { 47 | assertFalse(EmailValidator("teste@teste.com.")) 48 | } 49 | } -------------------------------------------------------------------------------- /features/feature-onboarding/src/main/java/com/mctech/features/onboarding/OnboardingActivity.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.features.onboarding 2 | 3 | import android.os.Bundle 4 | import com.mctech.feature.arq.BaseActivity 5 | import com.mctech.feature.arq.ComponentState 6 | import com.mctech.feature.arq.extentions.bindState 7 | import com.mctech.features.navigation.Screen 8 | import com.mctech.features.onboarding.state.OnBoardingNavigationState 9 | import org.koin.androidx.viewmodel.ext.android.viewModel 10 | 11 | /** 12 | * @author MAYCON CARDOSO on 2019-07-23. 13 | */ 14 | class OnboardingActivity : BaseActivity() { 15 | private val viewModelAgent: OnboardingViewModel by viewModel() 16 | 17 | override fun onCreate(savedInstanceState: Bundle?) { 18 | super.onCreate(savedInstanceState) 19 | setContentView(R.layout.activity_on_boarding) 20 | 21 | bindState(viewModelAgent.userFlowState){ 22 | when(it){ 23 | is ComponentState.Success -> navigate(it.result) 24 | } 25 | } 26 | } 27 | 28 | private fun navigate(result: OnBoardingNavigationState) { 29 | when(result){ 30 | is OnBoardingNavigationState.Unauthorized -> navigator.navigateTo( 31 | destination = Screen.Login, 32 | finishHost = true 33 | ) 34 | is OnBoardingNavigationState.Authorized -> navigator.navigateTo( 35 | destination = Screen.Dashboard, 36 | finishHost = true 37 | ) 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /data/src/main/java/com/mctech/data/quotation/repository/QuotationRepository.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.data.quotation.repository 2 | 3 | import com.mctech.data.quotation.datasource.QuotationCacheDataSource 4 | import com.mctech.data.quotation.datasource.QuotationDataSource 5 | import com.mctech.domain.errors.QuotationException 6 | import com.mctech.domain.model.Quotation 7 | import com.mctech.domain.services.QuotationService 8 | import com.mctech.library.networking.NetworkError 9 | 10 | /** 11 | * @author MAYCON CARDOSO on 2019-09-30. 12 | */ 13 | class QuotationRepository( 14 | private val cacheDataSource: QuotationCacheDataSource, 15 | private val remoteDataSource: QuotationDataSource 16 | ) : QuotationService { 17 | 18 | override suspend fun getRandom(): Quotation { 19 | return try { 20 | remoteDataSource.getRandom() 21 | } catch (exception: Exception) { 22 | throw QuotationException.UnknownQuotationException 23 | } 24 | } 25 | 26 | override suspend fun getByTag(tag: String, page: Int?): List { 27 | return try { 28 | remoteDataSource.getByTag(tag, page).apply { 29 | cacheDataSource.saveByTag(tag, page) 30 | } 31 | } catch (exception: Exception) { 32 | if (exception is NetworkError) { 33 | return cacheDataSource.getByTag(tag, page) 34 | ?: throw QuotationException.UnknownQuotationException 35 | } 36 | throw QuotationException.UnknownQuotationException 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /features/feature-navigation/src/test/java/com/mctech/features/navigation/NavigatorTest.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.features.navigation 2 | 3 | import android.content.Intent 4 | import androidx.fragment.app.FragmentActivity 5 | import com.nhaarman.mockitokotlin2.argumentCaptor 6 | import com.nhaarman.mockitokotlin2.mock 7 | import com.nhaarman.mockitokotlin2.verify 8 | import org.assertj.core.api.Assertions.assertThat 9 | import org.assertj.core.api.Assertions.assertThatThrownBy 10 | import org.junit.Before 11 | import org.junit.Test 12 | import org.junit.runner.RunWith 13 | import org.robolectric.RobolectricTestRunner 14 | 15 | @RunWith(RobolectricTestRunner::class) 16 | class NavigatorTest{ 17 | private lateinit var navigator: Navigator 18 | private val mockActivity = mock() 19 | private val links = mapOf>( 20 | Screen.Login to FragmentActivity::class.java 21 | ) 22 | 23 | @Before 24 | fun `before each test`() { 25 | navigator = Navigator(mockActivity, links) 26 | } 27 | 28 | @Test 29 | fun `should navigate to supported screen`() { 30 | navigator.navigateTo(Screen.Login) 31 | argumentCaptor().apply { 32 | verify(mockActivity).startActivity(capture()) 33 | assertThat(firstValue).isNotNull() 34 | } 35 | } 36 | 37 | @Test fun `should throw when navigating to unsupported screen`() { 38 | assertThatThrownBy { navigator.navigateTo(Screen.Splash) } 39 | .isEqualTo( 40 | UnsupportedNavigation(Screen.Splash) 41 | ) 42 | } 43 | } -------------------------------------------------------------------------------- /features/feature-quotation-random/src/main/java/com/mctech/feature/random_joke/RandomQuotationViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.feature.random_joke 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.lifecycle.MutableLiveData 5 | import com.mctech.domain.interaction.Result 6 | import com.mctech.domain.interaction.quotation.GetRandomQuotationCase 7 | import com.mctech.domain.model.Quotation 8 | import com.mctech.feature.arq.BaseViewModel 9 | import com.mctech.feature.arq.ComponentState 10 | import com.mctech.feature.arq.UserInteraction 11 | import com.mctech.feature.random_joke.interaction.RandomQuotationInteraction 12 | 13 | /** 14 | * @author MAYCON CARDOSO on 2019-09-30. 15 | */ 16 | class RandomQuotationViewModel( 17 | private val getRandomCase: GetRandomQuotationCase 18 | ) : BaseViewModel(){ 19 | private val _quotationState = MutableLiveData>(ComponentState.Initializing) 20 | val quotationState : LiveData> = _quotationState 21 | 22 | override suspend fun handleUserInteraction(interaction: UserInteraction) { 23 | when(interaction){ 24 | is RandomQuotationInteraction.LoadRandomQuotation -> { 25 | fetchQuotation() 26 | } 27 | } 28 | } 29 | 30 | private suspend fun fetchQuotation() { 31 | _quotationState.value = ComponentState.Loading 32 | 33 | when(val result = getRandomCase.execute()){ 34 | is Result.Failure -> { 35 | _quotationState.value = ComponentState.Error(result.throwable) 36 | } 37 | is Result.Success -> { 38 | _quotationState.value = ComponentState.Success(result.result) 39 | } 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /libraries/library-networking/src/main/java/com/mctech/library/networking/NetworkErrorTransformer.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.library.networking 2 | 3 | import retrofit2.HttpException 4 | import java.io.IOException 5 | import java.net.ConnectException 6 | import java.net.NoRouteToHostException 7 | import java.net.SocketTimeoutException 8 | import java.net.UnknownHostException 9 | 10 | /** 11 | * @author MAYCON CARDOSO on 2019-09-28. 12 | * 13 | * Transform any exception into a NetworkError in order to avoid any crash on the app. 14 | */ 15 | object NetworkErrorTransformer { 16 | fun transform(incoming: Throwable) = when (incoming) { 17 | is HttpException -> { 18 | translateHttpExceptionUsingStatusCode(incoming.code()) 19 | } 20 | is SocketTimeoutException -> { 21 | NetworkError.OperationTimeout 22 | } 23 | is UnknownHostException, 24 | is ConnectException, 25 | is NoRouteToHostException -> { 26 | NetworkError.HostUnreachable 27 | } 28 | else -> { 29 | resolveOtherException(incoming) 30 | } 31 | } 32 | 33 | private fun resolveOtherException(incoming: Throwable) = if(isRequestCanceled(incoming)){ 34 | NetworkError.ConnectionSpike 35 | } else{ 36 | NetworkError.UnknownNetworkingError 37 | } 38 | 39 | private fun isRequestCanceled(throwable: Throwable) = 40 | throwable is IOException && 41 | throwable.message?.contentEquals("Canceled") ?: false 42 | 43 | private fun translateHttpExceptionUsingStatusCode(code: Int) = 44 | when (code) { 45 | in 400..499 -> NetworkError.ClientException 46 | else -> NetworkError.RemoteException 47 | } 48 | } -------------------------------------------------------------------------------- /features/feature-login/src/main/java/com/mctech/features/login/state/LoginErrorStateResources.kt: -------------------------------------------------------------------------------- 1 | package com.mctech.features.login.state 2 | 3 | import com.mctech.domain.errors.AuthException 4 | import com.mctech.features.login.R 5 | 6 | data class LoginErrorStateResources(val message: Int) { 7 | companion object { 8 | operator fun invoke(error: AuthException) = 9 | when (error) { 10 | is AuthException.EmptyFormValueException -> LoginErrorStateResources( 11 | R.string.auth_form_empty 12 | ) 13 | is AuthException.UserNotFoundException -> LoginErrorStateResources( 14 | R.string.auth_user_not_found 15 | ) 16 | is AuthException.InvalidEmailFormatException -> LoginErrorStateResources( 17 | R.string.auth_email_bad_format 18 | ) 19 | is AuthException.PasswordUnderSixCharactersException -> LoginErrorStateResources( 20 | R.string.auth_invalid_password 21 | ) 22 | is AuthException.WrongCredentialsException -> LoginErrorStateResources( 23 | R.string.auth_wrong_credentials 24 | ) 25 | is AuthException.AlreadyRegisteredUserException -> LoginErrorStateResources( 26 | R.string.auth_user_already_registered 27 | ) 28 | is AuthException.PasswordsDoNotMatchException -> LoginErrorStateResources( 29 | R.string.auth_password_dont_match 30 | ) 31 | else -> LoginErrorStateResources( 32 | R.string.auth_unknown_error 33 | ) 34 | } 35 | } 36 | } 37 | 38 | fun LoginState.Error.toStateResource() = LoginErrorStateResources(error) -------------------------------------------------------------------------------- /libraries/library-app-theme/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 15 | 23 | 24 | 28 | 29 |